mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-07 02:28:43 +02:00
Compare commits
86 Commits
feature/fa
...
feature/41
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2363f2fb21 | ||
|
|
f32f36fc74 | ||
|
|
5157f511fc | ||
|
|
3196e105a1 | ||
|
|
b9d865f881 | ||
|
|
2e39a650eb | ||
|
|
c753b57265 | ||
|
|
583ab98210 | ||
|
|
e4f6a02de6 | ||
|
|
a5b5518533 | ||
|
|
88d743b1cc | ||
|
|
7ac4bc95bb | ||
|
|
fac455da48 | ||
|
|
b01627f39d | ||
|
|
766c9f0e7e | ||
|
|
d1414a3f34 | ||
|
|
6386a302b2 | ||
|
|
7071712a0a | ||
|
|
31454b2946 | ||
|
|
e55ac6002a | ||
|
|
e9c6549847 | ||
|
|
4aad555649 | ||
|
|
93ee565d29 | ||
|
|
6c097a128b | ||
|
|
eea3f78cec | ||
|
|
6c4caf64c8 | ||
|
|
88f0013330 | ||
|
|
aa31da34e5 | ||
|
|
71464f6d0e | ||
|
|
c7eea3f51f | ||
|
|
5a77159ece | ||
|
|
2c265aab44 | ||
|
|
fe28319d22 | ||
|
|
7f6f5c0c3b | ||
|
|
682ab44dea | ||
|
|
6aef59e42d | ||
|
|
86024107af | ||
|
|
754f87fd0c | ||
|
|
972e894bc5 | ||
|
|
86a2db7e7f | ||
|
|
4c31081de2 | ||
|
|
23c95ebbf3 | ||
|
|
e77f21a0b5 | ||
|
|
35e1f080b8 | ||
|
|
812c1f6bb4 | ||
|
|
9adb7f20ce | ||
|
|
c7e54c66c8 | ||
|
|
f6855b0d2b | ||
|
|
1ceef602f0 | ||
|
|
93cc29f4d9 | ||
|
|
c9317542c8 | ||
|
|
aed8337c51 | ||
|
|
af4a5e1b8d | ||
|
|
e7c09c83f0 | ||
|
|
56103d1952 | ||
|
|
301c308fec | ||
|
|
b827c68187 | ||
|
|
8ba28adf68 | ||
|
|
34a26d33a1 | ||
|
|
b0a55e057b | ||
|
|
5ac9b05b2d | ||
|
|
63e582a07f | ||
|
|
470076daa2 | ||
|
|
f6d92a189b | ||
|
|
c788c93542 | ||
|
|
a773f0d8a2 | ||
|
|
29c6b73d93 | ||
|
|
5b52ca4776 | ||
|
|
8ddaf1b731 | ||
|
|
964ce44577 | ||
|
|
cea6c557ce | ||
|
|
99819527db | ||
|
|
965273009c | ||
|
|
7bee616b1b | ||
|
|
f5302133d9 | ||
|
|
bf2aba1b06 | ||
|
|
c04beea38c | ||
|
|
93d88cca37 | ||
|
|
0997750816 | ||
|
|
427c8b0794 | ||
|
|
06008ed8eb | ||
|
|
374b71c017 | ||
|
|
fba78e7d9b | ||
|
|
551abc861e | ||
|
|
78f51d40f6 | ||
|
|
8bfc54e6b4 |
@@ -299,6 +299,7 @@ abstract class AbstractPreferencesExtension implements iPreferencesExtension
|
||||
*
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @deprecated
|
||||
*/
|
||||
interface iApplicationUIExtension
|
||||
{
|
||||
@@ -441,6 +442,7 @@ interface iApplicationUIExtension
|
||||
* @api
|
||||
* @package Extensibility
|
||||
* @since 2.7.0
|
||||
* @deprecated
|
||||
*/
|
||||
abstract class AbstractApplicationUIExtension implements iApplicationUIExtension
|
||||
{
|
||||
@@ -2092,4 +2094,4 @@ class RestUtils
|
||||
interface iModuleExtension
|
||||
{
|
||||
public function __construct();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
@@ -42,6 +42,7 @@ use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
|
||||
|
||||
|
||||
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
|
||||
|
||||
define('HILIGHT_CLASS_CRITICAL', 'red');
|
||||
@@ -676,33 +677,26 @@ HTML
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
// n:n links => must be allowed to modify the linking class AND read the target class in order to edit the linkedset
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass,
|
||||
UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ))
|
||||
{
|
||||
UR_ACTION_MODIFY) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) {
|
||||
$iFlags |= OPT_ATT_READONLY;
|
||||
}
|
||||
// n:n links => must be allowed to read the linking class AND the target class in order to display the linkedset
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass,
|
||||
UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ))
|
||||
{
|
||||
UR_ACTION_READ) || !UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ)) {
|
||||
$iFlags |= OPT_ATT_HIDDEN;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// 1:n links => must be allowed to modify the linked class in order to edit the linkedset
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_MODIFY))
|
||||
{
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_MODIFY)) {
|
||||
$iFlags |= OPT_ATT_READONLY;
|
||||
}
|
||||
// 1:n links => must be allowed to read the linked class in order to display the linkedset
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_READ))
|
||||
{
|
||||
if (!UserRights::IsActionAllowed($sLinkedClass, UR_ACTION_READ)) {
|
||||
$iFlags |= OPT_ATT_HIDDEN;
|
||||
}
|
||||
}
|
||||
// Non-readable/hidden linkedset... don't display anything
|
||||
if ($iFlags & OPT_ATT_HIDDEN)
|
||||
{
|
||||
if ($iFlags & OPT_ATT_HIDDEN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -711,17 +705,13 @@ HTML
|
||||
|
||||
$aArgs = array('this' => $this);
|
||||
$bReadOnly = ($iFlags & (OPT_ATT_READONLY | OPT_ATT_SLAVE));
|
||||
if ($bEditMode && (!$bReadOnly))
|
||||
{
|
||||
if ($bEditMode && (!$bReadOnly)) {
|
||||
$sInputId = $this->m_iFormId.'_'.$sAttCode;
|
||||
|
||||
if ($oAttDef->IsIndirect())
|
||||
{
|
||||
if ($oAttDef->IsIndirect()) {
|
||||
$oLinkingAttDef = MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
|
||||
$sTargetClass = $oLinkingAttDef->GetTargetClass();
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sTargetClass = $sLinkedClass;
|
||||
}
|
||||
|
||||
@@ -731,7 +721,7 @@ HTML
|
||||
|
||||
$sDisplayValue = ''; // not used
|
||||
$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode,
|
||||
$oAttDef, $oLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$oAttDef, $oOrmLinkSet, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
|
||||
$this->AddToFieldsMap($sAttCode, $sInputId);
|
||||
$oPage->add($sHTMLValue);
|
||||
}
|
||||
@@ -2376,8 +2366,7 @@ EOF
|
||||
case 'LinkedSet':
|
||||
$sInputType = self::ENUM_INPUT_TYPE_LINKEDSET;
|
||||
if ($oAttDef->IsIndirect()) {
|
||||
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix,
|
||||
$oAttDef->DuplicatesAllowed());
|
||||
$oWidget = new UILinksWidget($sClass, $sAttCode, $iId, $sNameSuffix, $oAttDef->DuplicatesAllowed());
|
||||
} else {
|
||||
$oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iId, $sNameSuffix);
|
||||
}
|
||||
@@ -4024,18 +4013,14 @@ HTML;
|
||||
$this->Set($sAttCode, $value);
|
||||
break;
|
||||
case 'LinkedSet':
|
||||
if ($this->IsValueModified($value))
|
||||
{
|
||||
if ($this->IsValueModified($value)) {
|
||||
$oLinkSet = $this->Get($sAttCode);
|
||||
$sLinkedClass = $oAttDef->GetLinkedClass();
|
||||
if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0))
|
||||
{
|
||||
if (array_key_exists('to_be_created', $value) && (count($value['to_be_created']) > 0)) {
|
||||
// Now handle the links to be created
|
||||
foreach ($value['to_be_created'] as $aData)
|
||||
{
|
||||
foreach ($value['to_be_created'] as $aData) {
|
||||
$sSubClass = $aData['class'];
|
||||
if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass)))
|
||||
{
|
||||
if (($sLinkedClass == $sSubClass) || (is_subclass_of($sSubClass, $sLinkedClass))) {
|
||||
$aObjData = $aData['data'];
|
||||
$oLink = MetaModel::NewObject($sSubClass);
|
||||
$oLink->UpdateObjectFromArray($aObjData);
|
||||
@@ -4247,28 +4232,20 @@ HTML;
|
||||
|
||||
case 'LinkedSet':
|
||||
/** @var AttributeLinkedSet $oAttDef */
|
||||
$aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}',
|
||||
'raw_data'), true);
|
||||
$aRawToBeCreated = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbc", '{}', 'raw_data'), true);
|
||||
$aToBeCreated = array();
|
||||
foreach($aRawToBeCreated as $aData)
|
||||
{
|
||||
foreach ($aRawToBeCreated as $aData) {
|
||||
$sSubFormPrefix = $aData['formPrefix'];
|
||||
$sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass();
|
||||
$aObjData = array();
|
||||
foreach($aData as $sKey => $value)
|
||||
{
|
||||
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
|
||||
{
|
||||
foreach ($aData as $sKey => $value) {
|
||||
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) {
|
||||
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]);
|
||||
// Recursing over n:n link datetime attributes
|
||||
// Note: We might need to do it with other attribute types, like Document or redundancy setting.
|
||||
if ($oLinkAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix,
|
||||
$aMatches[1], $sObjClass, $aData);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oLinkAttDef instanceof AttributeDateTime) {
|
||||
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aMatches[1], $sObjClass, $aData);
|
||||
} else {
|
||||
$aObjData[$aMatches[1]] = $value;
|
||||
}
|
||||
}
|
||||
@@ -4276,28 +4253,20 @@ HTML;
|
||||
$aToBeCreated[] = array('class' => $sObjClass, 'data' => $aObjData);
|
||||
}
|
||||
|
||||
$aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}',
|
||||
'raw_data'), true);
|
||||
$aRawToBeModified = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbm", '{}', 'raw_data'), true);
|
||||
$aToBeModified = array();
|
||||
foreach($aRawToBeModified as $iObjKey => $aData)
|
||||
{
|
||||
foreach ($aRawToBeModified as $iObjKey => $aData) {
|
||||
$sSubFormPrefix = $aData['formPrefix'];
|
||||
$sObjClass = isset($aData['class']) ? $aData['class'] : $oAttDef->GetLinkedClass();
|
||||
$aObjData = array();
|
||||
foreach($aData as $sKey => $value)
|
||||
{
|
||||
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches))
|
||||
{
|
||||
foreach ($aData as $sKey => $value) {
|
||||
if (preg_match("/^attr_$sSubFormPrefix(.*)$/", $sKey, $aMatches)) {
|
||||
$oLinkAttDef = MetaModel::GetAttributeDef($sObjClass, $aMatches[1]);
|
||||
// Recursing over n:n link datetime attributes
|
||||
// Note: We might need to do it with other attribute types, like Document or redundancy setting.
|
||||
if ($oLinkAttDef instanceof AttributeDateTime)
|
||||
{
|
||||
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix,
|
||||
$aMatches[1], $sObjClass, $aData);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($oLinkAttDef instanceof AttributeDateTime) {
|
||||
$aObjData[$aMatches[1]] = $this->PrepareValueFromPostedForm($sSubFormPrefix, $aMatches[1], $sObjClass, $aData);
|
||||
} else {
|
||||
$aObjData[$aMatches[1]] = $value;
|
||||
}
|
||||
}
|
||||
@@ -4306,14 +4275,11 @@ HTML;
|
||||
}
|
||||
|
||||
$value = array(
|
||||
'to_be_created' => $aToBeCreated,
|
||||
'to_be_created' => $aToBeCreated,
|
||||
'to_be_modified' => $aToBeModified,
|
||||
'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]',
|
||||
'raw_data'), true),
|
||||
'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]',
|
||||
'raw_data'), true),
|
||||
'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]',
|
||||
'raw_data'), true),
|
||||
'to_be_deleted' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbd", '[]', 'raw_data'), true),
|
||||
'to_be_added' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tba", '[]', 'raw_data'), true),
|
||||
'to_be_removed' => json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$sAttCode}_tbr", '[]', 'raw_data'), true),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -4479,13 +4445,13 @@ HTML;
|
||||
|
||||
// Protection against reentrance (e.g. cascading the update of ticket logs)
|
||||
// Note: This is based on the fix made on r 3190 in DBObject::DBUpdate()
|
||||
static $aUpdateReentrance = array();
|
||||
$sKey = get_class($this).'::'.$this->GetKey();
|
||||
if (array_key_exists($sKey, $aUpdateReentrance))
|
||||
{
|
||||
if (!MetaModel::StartReentranceProtection(Metamodel::REENTRANCE_TYPE_UPDATE, $this)) {
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
IssueLog::Debug("CRUD: DBUpdate $sClass::$sKey Rejected (reentrance)", LogChannels::DM_CRUD);
|
||||
|
||||
return $res;
|
||||
}
|
||||
$aUpdateReentrance[$sKey] = true;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -4496,13 +4462,13 @@ HTML;
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
}
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
unset($aUpdateReentrance[$sKey]);
|
||||
MetaModel::StopReentranceProtection(Metamodel::REENTRANCE_TYPE_UPDATE, $this);
|
||||
}
|
||||
|
||||
if ($this->IsModified()) {
|
||||
return $this->DBUpdate();
|
||||
}
|
||||
|
||||
return $res;
|
||||
@@ -5708,4 +5674,117 @@ JS
|
||||
'AttributeOneWayPassword',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventInsertRequested()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_INSERT_REQUESTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventInsertBefore()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_ABOUT_TO_INSERT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventInsertAfter()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_INSERT_DONE);
|
||||
}
|
||||
|
||||
final protected function EventComputeValues()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_COMPUTE_VALUES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aEventData
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventCheckToWrite(array $aEventData)
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_CHECK_TO_WRITE, $aEventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aEventData
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventCheckToDelete(array $aEventData)
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_CHECK_TO_DELETE, $aEventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventUpdateRequested()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_UPDATE_REQUESTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventUpdateBefore()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_ABOUT_TO_UPDATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aEventData
|
||||
*
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventUpdateAfter(array $aEventData)
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_UPDATE_DONE, $aEventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventDeleteBefore()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_ABOUT_TO_DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
*/
|
||||
final protected function EventDeleteAfter()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_DELETE_DONE);
|
||||
}
|
||||
|
||||
|
||||
final protected function EventArchive()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_ARCHIVE);
|
||||
}
|
||||
|
||||
final protected function EventUnarchive()
|
||||
{
|
||||
$this->FireEvent(EVENT_SERVICE_DB_UNARCHIVE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -185,6 +185,384 @@
|
||||
</style>
|
||||
</menu>
|
||||
</menus>
|
||||
<events>
|
||||
<event id="EVENT_SERVICE_DB_INSERT_REQUESTED" _delta="define">
|
||||
<description>An object insert in the database has been requested. All changes to the object will be persisted automatically.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::OnInsert</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_ABOUT_TO_INSERT" _delta="define">
|
||||
<description>An object is about to be inserted in the database (no change possible)</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::OnInsert</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_INSERT_DONE" _delta="define">
|
||||
<description>An object has been inserted into the database (but not reloaded). All changes to the object will be persisted automatically.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterInsert</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_UPDATE_REQUESTED" _delta="define">
|
||||
<description>An object update has been requested. All changes to the object will be persisted automatically.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::OnUpdate, DBObject::DoComputeValues</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_ABOUT_TO_UPDATE" _delta="define">
|
||||
<description>An object is about to be updated in the database (no change possible)</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::OnUpdate</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_UPDATE_DONE" _delta="define">
|
||||
<description>An object has been updated into the database and reloaded. All changes to the object will be persisted automatically.</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterUpdate</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_ABOUT_TO_DELETE" _delta="define">
|
||||
<description>An object is about to be deleted in the database</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::OnDelete</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object deleted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_DELETE_DONE" _delta="define">
|
||||
<description>An object has been deleted into the database</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::AfterDelete</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object deleted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_BEFORE_APPLY_STIMULUS" _delta="define">
|
||||
<description>A stimulus is about to be applied to an object</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object must be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_AFTER_APPLY_STIMULUS" _delta="define">
|
||||
<description>A stimulus has been applied to an object</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object is asked to be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_APPLY_STIMULUS_FAILED" _delta="define">
|
||||
<description>A stimulus has failed</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="action">
|
||||
<description>The action that failed to apply the stimulus</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="object">
|
||||
<description>The object where the stimulus is targeted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="stimulus">
|
||||
<description>Current stimulus applied</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="previous_state">
|
||||
<description>Object previous state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="new_state">
|
||||
<description>Object new state</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
<event_datum id="save_object">
|
||||
<description>The object must be saved in the database</description>
|
||||
<type>boolean</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_OBJECT_RELOAD" _delta="define">
|
||||
<description>An object has been re-loaded from the database</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object re-loaded</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_COMPUTE_VALUES" _delta="define">
|
||||
<description>An object needs to be recomputed after changes</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>DBObject::ComputeValues</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object inserted</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_CHECK_TO_WRITE" _delta="define">
|
||||
<description>Check an object before it is written into the database (no change possible)</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>cmdbAbstractObject::DoCheckToWrite</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object to check</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="error_messages">
|
||||
<description>Array of strings where all the errors found during the object checking are added</description>
|
||||
<type>array</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_CHECK_TO_DELETE" _delta="define">
|
||||
<description>Check an object before it is deleted from the database (no change possible)</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<replaces>cmdbAbstractObject::DoCheckToDelete</replaces>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object to check</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="error_messages">
|
||||
<description>Array of strings where all the errors found during the object checking are added</description>
|
||||
<type>array</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_ARCHIVE" _delta="define">
|
||||
<description>An object has been archived</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object archived</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DB_UNARCHIVE" _delta="define">
|
||||
<description>An object has been unarchived</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object unarchived</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_DOWNLOAD_DOCUMENT" _delta="define">
|
||||
<description>A document has been downloaded from the GUI</description>
|
||||
<sources>
|
||||
<source id="Document">Document</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The object containing the document</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="document">
|
||||
<description>The document downloaded</description>
|
||||
<type>ormDocument</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_LOGIN" _delta="define">
|
||||
<description>Inform the listeners about the connection states</description>
|
||||
<event_data>
|
||||
<event_datum id="code">
|
||||
<description>The login step result code (LoginWebPage::EXIT_CODE_...) </description>
|
||||
<type>integer</type>
|
||||
</event_datum>
|
||||
<event_datum id="state">
|
||||
<description>Current login state (LoginWebPage::LOGIN_STATE_CONNECTED...)</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
</events>
|
||||
<meta>
|
||||
<classes>
|
||||
<class id="cmdbAbstractObject" _delta="define">
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°5538
|
||||
*/
|
||||
class MySQLTransactionNotClosedException extends MySQLException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -59,6 +59,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
list($sAuthUser, $sAuthPwd) = $this->GetAuthUserAndPassword();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
if (!UserRights::CheckCredentials($sAuthUser, '', Session::Get('login_mode'), 'external'))
|
||||
{
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
@@ -88,4 +89,4 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
/** @var string $sAuthUser */
|
||||
return $sAuthUser; // Retrieve the value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
$sAuthPwd = utils::ReadPostedParam('auth_pwd', null, 'raw_data');
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
$sAuthPwd = utils::ReadParam('auth_pwd', null, false, 'raw_data');
|
||||
if (!UserRights::CheckCredentials($sAuthUser, $sAuthPwd, Session::Get('login_mode'), 'internal'))
|
||||
{
|
||||
$_SESSION['auth_user'] = $sAuthUser;
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
@@ -92,4 +93,4 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
|
||||
use Combodo\iTop\Application\Branding;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Service\EventData;
|
||||
use Combodo\iTop\Service\EventService;
|
||||
|
||||
/**
|
||||
* Web page used for displaying the login form
|
||||
@@ -479,11 +481,13 @@ class LoginWebPage extends NiceWebPage
|
||||
$iResponse = $oLoginFSMExtensionInstance->LoginAction($sLoginState, $iErrorCode);
|
||||
if ($iResponse == self::LOGIN_FSM_RETURN)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
|
||||
Session::WriteClose();
|
||||
return $iErrorCode; // Asked to exit FSM, generally login OK
|
||||
}
|
||||
if ($iResponse == self::LOGIN_FSM_ERROR)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['code' => $iErrorCode, 'state' => $sLoginState]));
|
||||
$sLoginState = self::LOGIN_STATE_SET_ERROR; // Next state will be error
|
||||
// An error was detected, skip the other plugins turn
|
||||
break;
|
||||
@@ -497,6 +501,7 @@ class LoginWebPage extends NiceWebPage
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
EventService::FireEvent(new EventData(EVENT_SERVICE_LOGIN, null, ['state' => $_SESSION['login_state']]));
|
||||
IssueLog::Error($e->getTraceAsString());
|
||||
static::ResetSession();
|
||||
die($e->getMessage());
|
||||
|
||||
@@ -108,15 +108,14 @@ class UILinksWidget
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false)
|
||||
protected function GetFormRow(WebPage $oP, DBObject $oLinkedObj, $linkObjOrId, $aArgs, $oCurrentObj, $iUniqueId, $bReadOnly = false, $bModified = false)
|
||||
{
|
||||
$sPrefix = "$this->m_sAttCode{$this->m_sNameSuffix}";
|
||||
$aRow = array();
|
||||
$aFieldsMap = array();
|
||||
$iKey = 0;
|
||||
|
||||
if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew()))
|
||||
{
|
||||
if (is_object($linkObjOrId) && (!$linkObjOrId->IsNew())) {
|
||||
$iKey = $linkObjOrId->GetKey();
|
||||
$iRemoteObjKey = $linkObjOrId->Get($this->m_sExtKeyToRemote);
|
||||
$sPrefix .= "[$iKey][";
|
||||
@@ -125,49 +124,44 @@ class UILinksWidget
|
||||
$aArgs['wizHelper'] = "oWizardHelper{$this->m_iInputId}{$iKey}";
|
||||
$aArgs['this'] = $linkObjOrId;
|
||||
|
||||
if ($bReadOnly)
|
||||
{
|
||||
if ($bReadOnly) {
|
||||
$aRow['form::checkbox'] = "";
|
||||
foreach ($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
foreach ($this->m_aEditableFields as $sFieldCode) {
|
||||
$sDisplayValue = $linkObjOrId->GetEditValue($sFieldCode);
|
||||
$aRow[$sFieldCode] = $sDisplayValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"$iKey\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$iKey\">";
|
||||
foreach ($this->m_aEditableFields as $sFieldCode)
|
||||
{
|
||||
foreach ($this->m_aEditableFields as $sFieldCode) {
|
||||
$sSafeFieldId = $this->GetFieldId($linkObjOrId->GetKey(), $sFieldCode);
|
||||
$this->AddRowForFieldCode($aRow, $sFieldCode, $aArgs, $linkObjOrId, $oP, $sNameSuffix, $sSafeFieldId);
|
||||
$aFieldsMap[$sFieldCode] = $sSafeFieldId;
|
||||
|
||||
if ($bModified) {
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
oWidget{$this->m_iInputId}.AddModified($iUniqueId, {$this->m_iInputId}, $sFieldCode, {$linkObjOrId->Get($sFieldCode)});
|
||||
EOF
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sState = $linkObjOrId->GetState();
|
||||
$sRemoteKeySafeFieldId = $this->GetFieldId($aArgs['this']->GetKey(), $this->m_sExtKeyToRemote);;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// form for creating a new record
|
||||
if (is_object($linkObjOrId))
|
||||
{
|
||||
if (is_object($linkObjOrId)) {
|
||||
// New link existing only in memory
|
||||
$oNewLinkObj = $linkObjOrId;
|
||||
$iRemoteObjKey = $oNewLinkObj->Get($this->m_sExtKeyToRemote);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe,
|
||||
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
else
|
||||
{
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
} else {
|
||||
$iRemoteObjKey = $linkObjOrId;
|
||||
$oNewLinkObj = MetaModel::NewObject($this->m_sLinkedClass);
|
||||
$oRemoteObj = MetaModel::GetObject($this->m_sRemoteClass, $iRemoteObjKey);
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToRemote,
|
||||
$oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe,
|
||||
$oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToRemote, $oRemoteObj); // Setting the extkey with the object alsoo fills the related external fields
|
||||
$oNewLinkObj->Set($this->m_sExtKeyToMe, $oCurrentObj); // Setting the extkey with the object also fills the related external fields
|
||||
}
|
||||
$sPrefix .= "[-$iUniqueId][";
|
||||
$sNameSuffix = "]"; // To make a tabular form
|
||||
@@ -177,8 +171,7 @@ class UILinksWidget
|
||||
$sInputValue = $iUniqueId > 0 ? "-$iUniqueId" : "$iUniqueId";
|
||||
$aRow['form::checkbox'] = "<input class=\"selection\" data-remote-id=\"$iRemoteObjKey\" data-link-id=\"0\" data-unique-id=\"$iUniqueId\" type=\"checkbox\" onClick=\"oWidget".$this->m_iInputId.".OnSelectChange();\" value=\"$sInputValue\">";
|
||||
|
||||
if ($iUniqueId > 0)
|
||||
{
|
||||
if ($iUniqueId > 0) {
|
||||
// Rows created with ajax call need OnLinkAdded call.
|
||||
//
|
||||
$oP->add_ready_script(
|
||||
@@ -187,9 +180,7 @@ PrepareWidgets();
|
||||
oWidget{$this->m_iInputId}.OnLinkAdded($iUniqueId, $iRemoteObjKey);
|
||||
EOF
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Rows added before loading the form don't have to call OnLinkAdded.
|
||||
// Listeners are already present and DOM is not recreated
|
||||
$iPositiveUniqueId = -$iUniqueId;
|
||||
@@ -378,10 +369,17 @@ JS
|
||||
$iMaxAddedId = 0;
|
||||
$iAddedId = -1; // Unique id for new links
|
||||
$oBlock->aRemoved = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->m_sAttCode}_tbd", '[]', 'raw_data'));
|
||||
$oModified = $oValue->GetModified($this->m_sExtKeyToRemote);
|
||||
while ($oCurrentLink = $oValue->Fetch()) {
|
||||
// We try to retrieve the remote object as usual
|
||||
if (!in_array($oCurrentLink->GetKey(), $oBlock->aRemoved)) {
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
|
||||
$bModified = false;
|
||||
if (array_key_exists($oCurrentLink->GetKey(), $oModified)) {
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oModified[$oCurrentLink->GetKey()], false /* Must not be found */);
|
||||
$bModified = true;
|
||||
} else {
|
||||
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $oCurrentLink->Get($this->m_sExtKeyToRemote), false /* Must not be found */);
|
||||
}
|
||||
// If successful, it means that we can edit its link
|
||||
if ($oLinkedObj !== null) {
|
||||
$bReadOnly = false;
|
||||
@@ -398,11 +396,12 @@ JS
|
||||
}
|
||||
|
||||
$iMaxAddedId = max($iMaxAddedId, $key);
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly);
|
||||
$aForm[$key] = $this->GetFormRow($oPage, $oLinkedObj, $oCurrentLink, $aArgs, $oCurrentObj, $key, $bReadOnly, $bModified);
|
||||
}
|
||||
}
|
||||
$oBlock->iMaxAddedId = (int)$iMaxAddedId;
|
||||
|
||||
|
||||
$oDataTable = DataTableUIBlockFactory::MakeForForm("{$this->m_sAttCode}{$this->m_sNameSuffix}", $this->m_aTableConfig, $aForm);
|
||||
$oDataTable->SetOptions(['select_mode' => 'custom', 'disable_hyperlinks' => true]);
|
||||
$oBlock->AddSubBlock($oDataTable);
|
||||
|
||||
@@ -3143,15 +3143,55 @@ HTML;
|
||||
*/
|
||||
public static function AddParameterToUrl(string $sUrl, string $sParamName, string $sParamValue): string
|
||||
{
|
||||
if (strpos($sUrl, '?') === false)
|
||||
{
|
||||
if (strpos($sUrl, '?') === false) {
|
||||
$sUrl = $sUrl.'?'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
$sUrl = $sUrl.'&'.urlencode($sParamName).'='.urlencode($sParamValue);
|
||||
}
|
||||
|
||||
return $sUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return traits array used by a class and by parent classes hierarchy.
|
||||
*
|
||||
* @see https://www.php.net/manual/en/function.class-uses.php#110752
|
||||
*
|
||||
* @param string $sClass Class to scan
|
||||
* @param bool $bAutoload Autoload flag
|
||||
*
|
||||
* @return array traits used
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function TraitsUsedByClass(string $sClass, bool $bAutoload = true): array
|
||||
{
|
||||
$aTraits = [];
|
||||
do {
|
||||
$aTraits = array_merge(class_uses($sClass, $bAutoload), $aTraits);
|
||||
} while ($sClass = get_parent_class($sClass));
|
||||
foreach ($aTraits as $sTrait => $same) {
|
||||
$aTraits = array_merge(class_uses($sTrait, $bAutoload), $aTraits);
|
||||
}
|
||||
|
||||
return array_unique($aTraits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test trait usage by a class or by parent classes hierarchy.
|
||||
*
|
||||
* @param string $sTrait Trait to search for
|
||||
* @param string $sClass Class to check
|
||||
*
|
||||
* @return bool
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function IsTraitUsedByClass(string $sTrait, string $sClass): bool
|
||||
{
|
||||
return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
|
||||
}
|
||||
|
||||
public static function GetUniqId()
|
||||
{
|
||||
return hash('sha256', uniqid(sprintf('%x', rand()), true).sprintf('%x', rand()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6022,7 +6022,9 @@ class AttributeDateTime extends AttributeDBField
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
// null value will be replaced by the current date, if not already set, in DoComputeValues
|
||||
if (!$this->IsNullAllowed()) {
|
||||
return date($this->GetInternalFormat());
|
||||
}
|
||||
return $this->GetNullValue();
|
||||
}
|
||||
|
||||
@@ -7812,7 +7814,7 @@ class AttributeBlob extends AttributeDefinition
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return "";
|
||||
return new ormDocument('', '', '');
|
||||
}
|
||||
|
||||
public function IsNullAllowed(DBObject $oHostObject = null)
|
||||
@@ -8161,6 +8163,11 @@ class AttributeImage extends AttributeBlob
|
||||
return $oDoc;
|
||||
}
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return new ormDocument('', '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the supplied ormDocument actually contains an image
|
||||
* {@inheritDoc}
|
||||
@@ -11352,6 +11359,13 @@ class AttributeTagSet extends AttributeSet
|
||||
return new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
|
||||
}
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetMaxItems());
|
||||
$oTagSet->SetValues([]);
|
||||
return $oTagSet;
|
||||
}
|
||||
|
||||
public function IsNull($proposedValue)
|
||||
{
|
||||
if (is_null($proposedValue))
|
||||
@@ -13083,7 +13097,7 @@ class AttributeObsolescenceFlag extends AttributeBoolean
|
||||
|
||||
public function GetDefaultValue(DBObject $oHostObject = null)
|
||||
{
|
||||
return $this->MakeRealValue("", $oHostObject);
|
||||
return $this->MakeRealValue(false, $oHostObject);
|
||||
}
|
||||
|
||||
public function IsNullAllowed()
|
||||
|
||||
@@ -11,7 +11,7 @@ define('UTF8_BOM', chr(239).chr(187).chr(191)); // 0xEF, 0xBB, 0xBF
|
||||
|
||||
/**
|
||||
* CellChangeSpec
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -42,6 +42,17 @@ abstract class CellChangeSpec
|
||||
return $this->m_sOql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
public function GetDisplayableValueAndDescription(): string
|
||||
{
|
||||
return sprintf("%s%s",
|
||||
$this->GetDisplayableValue(),
|
||||
$this->GetDescription()
|
||||
);
|
||||
}
|
||||
|
||||
abstract public function GetDescription();
|
||||
}
|
||||
|
||||
@@ -86,26 +97,90 @@ class CellStatus_Issue extends CellStatus_Modify
|
||||
parent::__construct($proposedValue, $previousValue);
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
public function GetDisplayableValue()
|
||||
{
|
||||
if (is_null($this->m_proposedValue))
|
||||
{
|
||||
return Dict::Format('UI:CSVReport-Value-SetIssue', $this->m_sReason);
|
||||
return Dict::Format('UI:CSVReport-Value-SetIssue');
|
||||
}
|
||||
return Dict::Format('UI:CSVReport-Value-ChangeIssue', $this->m_proposedValue, $this->m_sReason);
|
||||
return Dict::Format('UI:CSVReport-Value-ChangeIssue', \utils::EscapeHtml($this->m_proposedValue));
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return $this->m_sReason;
|
||||
}
|
||||
/*
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
public function GetDisplayableValueAndDescription(): string
|
||||
{
|
||||
return sprintf("%s. %s",
|
||||
$this->GetDisplayableValue(),
|
||||
$this->GetDescription()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
{
|
||||
public function __construct()
|
||||
/** @var string|null $m_sAllowedValues */
|
||||
private $m_sAllowedValues;
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @var string $sSerializedSearch
|
||||
*/
|
||||
private $sSerializedSearch;
|
||||
|
||||
/** @var string|null $m_sTargetClass */
|
||||
private $m_sTargetClass;
|
||||
|
||||
/**
|
||||
* CellStatus_SearchIssue constructor.
|
||||
* @since 3.1.0 N°5305
|
||||
*
|
||||
* @param string $sOql : main message
|
||||
* @param string $sReason : main message
|
||||
* @param null $sClass : used for additional message that provides allowed values for current class $sClass
|
||||
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
|
||||
*/
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null)
|
||||
{
|
||||
parent::__construct(null, null, null);
|
||||
parent::__construct(null, null, $sReason);
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
$this->m_sAllowedValues = $sAllowedValues;
|
||||
$this->m_sTargetClass = $sClass;
|
||||
}
|
||||
|
||||
public function GetDisplayableValue()
|
||||
{
|
||||
if (null === $this->m_sReason) {
|
||||
return Dict::Format('UI:CSVReport-Value-NoMatch', '');
|
||||
}
|
||||
|
||||
return $this->m_sReason;
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return Dict::S('UI:CSVReport-Value-NoMatch');
|
||||
if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) ||
|
||||
\utils::IsNullOrEmptyString($this->m_sTargetClass)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Dict::Format('UI:CSVReport-Value-NoMatch-PossibleValues', $this->m_sTargetClass, $this->m_sAllowedValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @return string
|
||||
*/
|
||||
public function GetSearchLinkUrl()
|
||||
{
|
||||
return sprintf("UI.php?operation=search&filter=%s",
|
||||
rawurlencode($this->sSerializedSearch)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,11 +201,24 @@ class CellStatus_NullIssue extends CellStatus_Issue
|
||||
class CellStatus_Ambiguous extends CellStatus_Issue
|
||||
{
|
||||
protected $m_iCount;
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @var string
|
||||
*/
|
||||
protected $sSerializedSearch;
|
||||
|
||||
public function __construct($previousValue, $iCount, $sOql)
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
*
|
||||
* @param $previousValue
|
||||
* @param int $iCount
|
||||
* @param string $sSerializedSearch
|
||||
*
|
||||
*/
|
||||
public function __construct($previousValue, $iCount, $sSerializedSearch)
|
||||
{
|
||||
$this->m_iCount = $iCount;
|
||||
$this->m_sQuery = $sOql;
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
parent::__construct(null, $previousValue, '');
|
||||
}
|
||||
|
||||
@@ -139,12 +227,23 @@ class CellStatus_Ambiguous extends CellStatus_Issue
|
||||
$sCount = $this->m_iCount;
|
||||
return Dict::Format('UI:CSVReport-Value-Ambiguous', $sCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @return string
|
||||
*/
|
||||
public function GetSearchLinkUrl()
|
||||
{
|
||||
return sprintf("UI.php?operation=search&filter=%s",
|
||||
rawurlencode($this->sSerializedSearch)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RowStatus
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -211,6 +310,26 @@ class RowStatus_Issue extends RowStatus
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* class dedicated to testability
|
||||
* not used/ignored in csv imports UI/CLI
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
class RowStatus_Error extends RowStatus
|
||||
{
|
||||
/** @var string */
|
||||
protected $m_sError;
|
||||
|
||||
public function __construct($sError)
|
||||
{
|
||||
$this->m_sError = $sError;
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
{
|
||||
return $this->m_sError;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BulkChange
|
||||
@@ -220,17 +339,35 @@ class RowStatus_Issue extends RowStatus
|
||||
*/
|
||||
class BulkChange
|
||||
{
|
||||
protected $m_sClass;
|
||||
/** @var string */
|
||||
protected $m_sClass;
|
||||
protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string)
|
||||
// #@# todo: rename the variables to sColIndex
|
||||
protected $m_aAttList; // attcode => iCol
|
||||
protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
|
||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
||||
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
|
||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
||||
/** @var array<string, string> attcode as key, iCol as value */
|
||||
protected $m_aAttList;
|
||||
/** @var array<string, array<string, string>> sExtKeyAttCode as key, array of sExtReconcKeyAttCode/iCol as value */
|
||||
protected $m_aExtKeys;
|
||||
/** @var string[] list of attcode (attcode = 'id' for the pkey) */
|
||||
protected $m_aReconcilKeys;
|
||||
/** @var string OQL - if specified, then the missing items will be reported */
|
||||
protected $m_sSynchroScope;
|
||||
/**
|
||||
* @var array<string, mixed> attcode as key, attvalue as value. Values to be set when an object gets out of scope
|
||||
* (ignored if no scope has been defined)
|
||||
*/
|
||||
protected $m_aOnDisappear;
|
||||
/**
|
||||
* @see DateTime::createFromFormat
|
||||
* @var string Date format specification
|
||||
*/
|
||||
protected $m_sDateFormat;
|
||||
/**
|
||||
* @see AttributeEnum
|
||||
* @var boolean true if Values in the data set are localized
|
||||
*/
|
||||
protected $m_bLocalizedValues;
|
||||
/** @var array Cache for resolving external keys based on the given search criterias */
|
||||
protected $m_aExtKeysMappingCache;
|
||||
|
||||
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null, $bLocalize = false)
|
||||
{
|
||||
@@ -261,30 +398,30 @@ class BulkChange
|
||||
$this->m_sReportCsvSep = $sSeparator;
|
||||
$this->m_sReportCsvDelimiter = $sDelimiter;
|
||||
}
|
||||
|
||||
|
||||
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
|
||||
foreach ($this->m_aExtKeys[$sAttCode] as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
if ($sForeignAttCode == 'id')
|
||||
if ($sReconKeyAttCode == 'id')
|
||||
{
|
||||
$value = (int) $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$aKeys = $oExtObjects->ToArray();
|
||||
return array($oReconFilter->ToOql(), $aKeys);
|
||||
return array($oReconFilter, $aKeys);
|
||||
}
|
||||
|
||||
// Returns true if the CSV data specifies that the external key must be left undefined
|
||||
@@ -318,10 +455,10 @@ class BulkChange
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// External keys reconciliation
|
||||
//
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aReconKeys)
|
||||
{
|
||||
// Skip external keys used for the reconciliation process
|
||||
// if (!array_key_exists($sAttCode, $this->m_aAttList)) continue;
|
||||
@@ -330,7 +467,7 @@ class BulkChange
|
||||
|
||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
||||
{
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// Default reporting
|
||||
// $aRowData[$iCol] is always null
|
||||
@@ -352,25 +489,24 @@ class BulkChange
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
if ($sForeignAttCode == 'id')
|
||||
if ($sReconKeyAttCode == 'id')
|
||||
{
|
||||
$value = $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$aCacheKeys[] = $value;
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$iForeignKey = null;
|
||||
$sOQL = '';
|
||||
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
|
||||
if (!array_key_exists($sAttCode, $this->m_aExtKeysMappingCache))
|
||||
{
|
||||
@@ -379,9 +515,8 @@ class BulkChange
|
||||
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
|
||||
{
|
||||
// Cache hit
|
||||
$iCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iObjectFoundCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
|
||||
$sOQL = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['oql'];
|
||||
// Record the hit
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
|
||||
}
|
||||
@@ -389,34 +524,35 @@ class BulkChange
|
||||
{
|
||||
// Cache miss, let's initialize it
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$iCount = $oExtObjects->Count();
|
||||
if ($iCount == 1)
|
||||
$iObjectFoundCount = $oExtObjects->Count();
|
||||
if ($iObjectFoundCount == 1)
|
||||
{
|
||||
$oForeignObj = $oExtObjects->Fetch();
|
||||
$iForeignKey = $oForeignObj->GetKey();
|
||||
}
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
||||
'c' => $iCount,
|
||||
'c' => $iObjectFoundCount,
|
||||
'k' => $iForeignKey,
|
||||
'oql' => $oReconFilter->ToOql(),
|
||||
'h' => 0, // number of hits on this cache entry
|
||||
);
|
||||
}
|
||||
switch($iCount)
|
||||
switch($iObjectFoundCount)
|
||||
{
|
||||
case 0:
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
$aResults[$sAttCode]= new CellStatus_SearchIssue();
|
||||
break;
|
||||
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResults[$sAttCode] = $oCellStatus_SearchIssue;
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
default:
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iObjectFoundCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iObjectFoundCount, $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +569,7 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
$aResults[$sAttCode]= new CellStatus_Modify($iForeignObj, $oTargetObj->GetOriginal($sAttCode));
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// Report the change on reconciliation values as well
|
||||
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
||||
@@ -446,7 +582,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the object attributes
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
@@ -487,7 +623,13 @@ class BulkChange
|
||||
$value = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
if (is_null($value) && (strlen($aRowData[$iCol]) > 0))
|
||||
{
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||
if ($oAttDef instanceof AttributeEnum || $oAttDef instanceof AttributeTagSet){
|
||||
/** @var AttributeDefinition $oAttributeDefinition */
|
||||
$oAttributeDefinition = $oAttDef;
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-AllowedValues', $sAttCode, implode(',', $oAttributeDefinition->GetAllowedValues()));
|
||||
} else {
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -504,7 +646,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -556,7 +698,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -567,12 +709,101 @@ class BulkChange
|
||||
}
|
||||
return $aResults;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* search with current permissions did not match
|
||||
* let's search why and give some more feedbacks to the user through proper labels
|
||||
*
|
||||
* @param DBObjectSearch $oDbSearchWithConditions search used to find external key
|
||||
*
|
||||
* @return \CellStatus_SearchIssue
|
||||
* @throws \CoreException
|
||||
* @throws \MissingQueryArgument
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
protected function GetCellSearchIssue($oDbSearchWithConditions) : CellStatus_SearchIssue {
|
||||
//current search with current permissions did not match
|
||||
//let's search why and give some more feedback to the user
|
||||
|
||||
$sSerializedSearch = $oDbSearchWithConditions->serialize();
|
||||
|
||||
// Count all objects with all permissions without any condition
|
||||
$oDbSearchWithoutAnyCondition = new DBObjectSearch($oDbSearchWithConditions->GetClass());
|
||||
$oDbSearchWithoutAnyCondition->AllowAllData(true);
|
||||
$oExtObjectSet = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||
$iAllowAllDataObjectCount = $oExtObjectSet->Count();
|
||||
|
||||
if ($iAllowAllDataObjectCount === 0) {
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
// Count all objects with current user permissions
|
||||
$oDbSearchWithoutAnyCondition->AllowAllData(false);
|
||||
$oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||
$iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count();
|
||||
|
||||
if ($iCurrentUserRightsObjectCount === 0){
|
||||
// No objects visible by current user
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
try{
|
||||
$aDisplayedAllowedValues = [];
|
||||
// Possibles values are displayed to UI user. we have to limit the amount of displayed values
|
||||
$oExtObjectSetWithCurrentUserPermissions->SetLimit(4);
|
||||
for($i = 0; $i < 3; $i++){
|
||||
/** @var \DBObject $oVisibleObject */
|
||||
$oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch();
|
||||
if (is_null($oVisibleObject)){
|
||||
break;
|
||||
}
|
||||
|
||||
$aCurrentAllowedValueFields = [];
|
||||
foreach ($oDbSearchWithConditions->GetInternalParams() as $sForeignAttCode => $sValue){
|
||||
$aCurrentAllowedValueFields[] = $oVisibleObject->Get($sForeignAttCode);
|
||||
}
|
||||
$aDisplayedAllowedValues[] = implode(" ", $aCurrentAllowedValueFields);
|
||||
|
||||
}
|
||||
$allowedValues = implode(", ", $aDisplayedAllowedValues);
|
||||
if ($oExtObjectSetWithCurrentUserPermissions->Count() > 3){
|
||||
$allowedValues .= "...";
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
IssueLog::Error("failure during CSV import when fetching few visible objects: ", null,
|
||||
[ 'target_class' => $oDbSearchWithConditions->GetClass(), 'criteria' => $oDbSearchWithConditions->GetCriteria(), 'message' => $e->getMessage()]
|
||||
);
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||
}
|
||||
|
||||
if ($iAllowAllDataObjectCount != $iCurrentUserRightsObjectCount) {
|
||||
// No match and some objects NOT visible by current user. including current search maybe...
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||
}
|
||||
|
||||
// No match. This is not linked to any right issue
|
||||
// Possible values: DD,DD
|
||||
$aCurrentValueFields = [];
|
||||
foreach ($oDbSearchWithConditions->GetInternalParams() as $sValue){
|
||||
$aCurrentValueFields[] = $sValue;
|
||||
}
|
||||
$value =implode(" ", $aCurrentValueFields);
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value);
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||
}
|
||||
|
||||
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// External keys
|
||||
//
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
@@ -585,7 +816,7 @@ class BulkChange
|
||||
$aResults[$iCol] = new CellStatus_Void('?');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update attributes
|
||||
//
|
||||
foreach($this->m_aOnDisappear as $sAttCode => $value)
|
||||
@@ -596,7 +827,7 @@ class BulkChange
|
||||
}
|
||||
$oTargetObj->Set($sAttCode, $value);
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -616,7 +847,7 @@ class BulkChange
|
||||
$aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -674,14 +905,16 @@ class BulkChange
|
||||
}
|
||||
|
||||
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
|
||||
|
||||
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return $oTargetObj;
|
||||
}
|
||||
|
||||
|
||||
// Check that any external key will have a value proposed
|
||||
$aMissingKeys = array();
|
||||
foreach (MetaModel::GetExternalKeys($this->m_sClass) as $sExtKeyAttCode => $oExtKey)
|
||||
@@ -689,7 +922,7 @@ class BulkChange
|
||||
if (!$oExtKey->IsNullAllowed())
|
||||
{
|
||||
if (!array_key_exists($sExtKeyAttCode, $this->m_aExtKeys) && !array_key_exists($sExtKeyAttCode, $this->m_aAttList))
|
||||
{
|
||||
{
|
||||
$aMissingKeys[] = $oExtKey->GetLabel();
|
||||
}
|
||||
}
|
||||
@@ -745,14 +978,16 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Modify(count($aChangedFields));
|
||||
|
||||
|
||||
// Optionaly record the results
|
||||
//
|
||||
if ($oChange)
|
||||
@@ -794,9 +1029,11 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||
//__ERRORS__ used by tests only
|
||||
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
@@ -821,7 +1058,7 @@ class BulkChange
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function Process(CMDBChange $oChange = null)
|
||||
{
|
||||
if ($oChange)
|
||||
@@ -866,7 +1103,7 @@ class BulkChange
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
@@ -881,14 +1118,18 @@ class BulkChange
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sDateExample = $oFormat->Format(new DateTime('2022-10-23 16:25:33'));
|
||||
$sRegExp = $oFormat->ToRegExpr('/');
|
||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
||||
$sErrorMsg = Dict::Format('UI:CSVReport-Row-Issue-ExpectedDateFormat', $sDateExample);
|
||||
if (!preg_match($sRegExp, $sValue))
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Issue(utils::HtmlEntities($sValue), null, $sErrorMsg);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
||||
$oDate = DateTime::createFromFormat($sFormat, $sValue);
|
||||
if ($oDate !== false)
|
||||
{
|
||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||
@@ -898,7 +1139,7 @@ class BulkChange
|
||||
{
|
||||
// Leave the cell unchanged
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, utils::HtmlEntities($this->m_aData[$iRow][$iCol]), Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -952,23 +1193,26 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
// The value has to be found or verified
|
||||
list($sQuery, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
||||
|
||||
|
||||
/** var DBObjectSearch $oReconFilter */
|
||||
list($oReconFilter, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
||||
|
||||
if (count($aMatches) == 1)
|
||||
{
|
||||
$oRemoteObj = reset($aMatches); // first item
|
||||
$valuecondition = $oRemoteObj->GetKey();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
|
||||
}
|
||||
}
|
||||
elseif (count($aMatches) == 0)
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_SearchIssue();
|
||||
}
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery);
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1019,7 +1263,7 @@ class BulkChange
|
||||
default:
|
||||
// Found several matches, ambiguous
|
||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
|
||||
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql());
|
||||
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->serialize());
|
||||
$aResult[$iRow]["finalclass"]= 'n/a';
|
||||
}
|
||||
}
|
||||
@@ -1110,7 +1354,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
$oBulkChanges->Seek(0);
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
while ($oChange = $oBulkChanges->Fetch())
|
||||
{
|
||||
@@ -1274,7 +1518,7 @@ EOF
|
||||
$oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue'));
|
||||
$sOldValue = $oOldTarget->GetHyperlink();
|
||||
}
|
||||
|
||||
|
||||
$sNewValue = Dict::S('UI:UndefinedObject');
|
||||
if ($oOperation->Get('newvalue') != 0)
|
||||
{
|
||||
@@ -1300,11 +1544,11 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
foreach($aObjects as $iUId => $aObjData)
|
||||
{
|
||||
@@ -1356,6 +1600,6 @@ EOF
|
||||
$aConfig[$sAttCode] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'description' => MetaModel::GetDescription($sClass, $sAttCode));
|
||||
}
|
||||
$oPage->table($aConfig, $aDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -499,7 +499,7 @@ abstract class CMDBObject extends DBObject
|
||||
$oMyChangeOp->Set("objkey", $this->GetKey());
|
||||
$oMyChangeOp->Set("attcode", $sAttCode);
|
||||
$oMyChangeOp->Set("oldvalue", $original);
|
||||
$oMyChangeOp->Set("newvalue", $value[$sAttCode]);
|
||||
$oMyChangeOp->Set("newvalue", $value);
|
||||
$iId = $oMyChangeOp->DBInsertNoReload();
|
||||
}
|
||||
elseif ($oAttDef instanceOf AttributeCustomFields)
|
||||
@@ -640,20 +640,6 @@ abstract class CMDBObject extends DBObject
|
||||
return $newKey;
|
||||
}
|
||||
|
||||
public function DBUpdate()
|
||||
{
|
||||
// Copy the changes list before the update (the list should be reset afterwards)
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$ret = parent::DBUpdate();
|
||||
return $ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param null $oDeletionPlan
|
||||
*
|
||||
|
||||
@@ -129,6 +129,22 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_events' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Filter Event Service debug by events',
|
||||
'default' => '',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_sources' => [
|
||||
'type' => 'array',
|
||||
'description' => 'Filter Event Service debug by event sources',
|
||||
'default' => '',
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'app_env_label' => [
|
||||
'type' => 'string',
|
||||
'description' => 'Label displayed to describe the current application environment, defaults to the environment name (e.g. "production")',
|
||||
|
||||
@@ -270,4 +270,4 @@
|
||||
</class>
|
||||
</classes>
|
||||
</meta>
|
||||
</itop_design>
|
||||
</itop_design>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -182,6 +182,26 @@ class DesignElement extends \DOMElement
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this);
|
||||
}
|
||||
|
||||
public static function ToArray(DesignElement $oNode)
|
||||
{
|
||||
$aRes = [];
|
||||
|
||||
if ($oNode->GetNodes('./*')->length == 0) {
|
||||
return $oNode->GetText('');
|
||||
}
|
||||
foreach ($oNode->GetNodes('./*') as $oSubNode) {
|
||||
/** @var \Combodo\iTop\DesignElement $oSubNode */
|
||||
$aSubArray = DesignElement::ToArray($oSubNode);
|
||||
if ($oSubNode->hasAttribute('id')) {
|
||||
$aRes[$oSubNode->getAttribute('id')] = $aSubArray;
|
||||
} else {
|
||||
$aRes[$oSubNode->tagName] = $aSubArray;
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTML representation of the DOM, for debugging purposes
|
||||
*
|
||||
|
||||
@@ -77,7 +77,7 @@ class EMail implements iEMail
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
* @since 2.7.>8 3.0.3 3.1.0 N°4947 Method creation, to factorize same code in children classes
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°4947 Method creation, to factorize same code in children classes
|
||||
*/
|
||||
protected function InitRecipientFrom()
|
||||
{
|
||||
|
||||
@@ -299,7 +299,7 @@ class ExecutionKPI
|
||||
*/
|
||||
private static function Push(ExecutionKPI $oExecutionKPI)
|
||||
{
|
||||
array_push(self::$m_aExecutionStack, $oExecutionKPI);
|
||||
self::$m_aExecutionStack[] = $oExecutionKPI;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,4 +449,3 @@ class ExecutionKPI
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -569,6 +569,14 @@ class LogChannels
|
||||
public const INLINE_IMAGE = 'InlineImage';
|
||||
|
||||
public const PORTAL = 'portal';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0 specific channel for event service
|
||||
*/
|
||||
public const EVENT_SERVICE = 'EventService';
|
||||
|
||||
public const DM_CRUD = 'DMCRUD';
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -128,6 +128,10 @@ abstract class MetaModel
|
||||
/** @var string */
|
||||
protected static $m_sEnvironment = 'production';
|
||||
|
||||
public const REENTRANCE_TYPE_UPDATE = 'update';
|
||||
|
||||
protected static $m_aReentranceProtection = [];
|
||||
|
||||
/**
|
||||
* MetaModel constructor.
|
||||
*/
|
||||
@@ -6785,6 +6789,19 @@ abstract class MetaModel
|
||||
}
|
||||
$sClass = $aRow[$sClassAlias."finalclass"];
|
||||
}
|
||||
|
||||
// if an object is already being updated, then this method will return this object instead of recreating a new one.
|
||||
// At this point the method DBUpdate of a new object with the same class and id won't do anything due to reentrance protection,
|
||||
// so to ensure that the potential modifications are correctly saved, the object currently being updated is returned.
|
||||
// DBUpdate() method then will take care that all the modifications will be saved.
|
||||
if (array_key_exists($sClassAlias.'id', $aRow)) {
|
||||
$iKey = $aRow[$sClassAlias."id"];
|
||||
$oObject = self::GetReentranceObject(Metamodel::REENTRANCE_TYPE_UPDATE, $sClass, $iKey);
|
||||
if ($oObject !== false) {
|
||||
return $oObject;
|
||||
}
|
||||
}
|
||||
|
||||
return new $sClass($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
|
||||
}
|
||||
|
||||
@@ -7543,6 +7560,36 @@ abstract class MetaModel
|
||||
/** @var AttributeEnum $oAttDef */
|
||||
return $oAttDef->GetStyle($sValue);
|
||||
}
|
||||
|
||||
protected static function GetReentranceObject($sType, $sClass, $sKey)
|
||||
{
|
||||
if (isset(self::$m_aReentranceProtection[$sType][$sClass][$sKey])) {
|
||||
return self::$m_aReentranceProtection[$sType][$sClass][$sKey];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sType
|
||||
* @param \DBObject $oObject
|
||||
*
|
||||
* @return bool true if reentry possible
|
||||
*/
|
||||
public static function StartReentranceProtection($sType, DBObject $oObject)
|
||||
{
|
||||
if (isset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()])) {
|
||||
return false;
|
||||
}
|
||||
self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()] = $oObject;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function StopReentranceProtection($sType, DBObject $oObject)
|
||||
{
|
||||
if (isset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()])) {
|
||||
unset(self::$m_aReentranceProtection[$sType][get_class($oObject)][$oObject->GetKey()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Service\EventData;
|
||||
use Combodo\iTop\Service\EventService;
|
||||
|
||||
|
||||
/**
|
||||
* ormDocument
|
||||
@@ -193,7 +196,6 @@ class ormDocument
|
||||
* @param string $sContentDisposition Either 'inline' or 'attachment'
|
||||
* @param string $sSecretField The attcode of the field containing a "secret" to be provided in order to retrieve the file
|
||||
* @param string $sSecretValue The value of the secret to be compared with the value of the attribute $sSecretField
|
||||
* @return none
|
||||
*/
|
||||
public static function DownloadDocument(WebPage $oPage, $sClass, $id, $sAttCode, $sContentDisposition = 'attachment', $sSecretField = null, $sSecretValue = null)
|
||||
{
|
||||
@@ -212,6 +214,12 @@ class ormDocument
|
||||
$oDocument = $oObj->Get($sAttCode);
|
||||
if (is_object($oDocument))
|
||||
{
|
||||
$aEventData = array(
|
||||
'debug_info' => $oDocument->GetFileName(),
|
||||
'object' => $oObj,
|
||||
'document' => $oDocument,
|
||||
);
|
||||
EventService::FireEvent(new EventData(EVENT_SERVICE_DOWNLOAD_DOCUMENT, $sClass, $aEventData));
|
||||
$oPage->TrashUnexpectedOutput();
|
||||
$oPage->SetContentType($oDocument->GetMimeType());
|
||||
$oPage->SetContentDisposition($sContentDisposition,$oDocument->GetFileName());
|
||||
|
||||
@@ -383,9 +383,8 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$iPreservedCount = count($this->aPreserved);
|
||||
if ($this->iCursor < $iPreservedCount)
|
||||
{
|
||||
$iRet = current($this->aPreserved);
|
||||
$this->oOriginalSet->Seek($iRet);
|
||||
$oRet = $this->oOriginalSet->Fetch();
|
||||
$sId = key($this->aPreserved);
|
||||
$oRet = MetaModel::GetObject($this->sClass, $sId);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -609,16 +608,32 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$aAdded = $this->aAdded;
|
||||
$aModified = $this->aModified;
|
||||
$aRemoved = array();
|
||||
if (count($this->aRemoved) > 0)
|
||||
{
|
||||
if (count($this->aRemoved) > 0) {
|
||||
$oSearch = new DBObjectSearch($this->sClass);
|
||||
$oSearch->AddCondition('id', $this->aRemoved, 'IN');
|
||||
$oSet = new DBObjectSet($oSearch);
|
||||
$aRemoved = $oSet->ToArray();
|
||||
}
|
||||
|
||||
return array_merge($aAdded, $aModified, $aRemoved);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all modified (added, modified and removed) links
|
||||
*
|
||||
* @return array of link objects
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetModified($sExtKeyToMe)
|
||||
{
|
||||
$aModified = [];
|
||||
foreach ($this->aModified as $oObj) {
|
||||
$aModified[$oObj->GetKey()] = $oObj->Get($sExtKeyToMe);
|
||||
}
|
||||
|
||||
return $aModified;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DBObject $oHostObject
|
||||
*
|
||||
@@ -662,8 +677,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
{
|
||||
$aCheckLinks[] = $iLinkId;
|
||||
}
|
||||
foreach ($this->aModified as $iLinkId => $oLink)
|
||||
{
|
||||
foreach ($this->aModified as $iLinkId => $oLink) {
|
||||
$aCheckLinks[] = $oLink->GetKey();
|
||||
}
|
||||
|
||||
@@ -699,8 +713,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
|
||||
// Write the links according to the existing links
|
||||
//
|
||||
foreach ($this->aAdded as $oLink)
|
||||
{
|
||||
foreach ($this->aAdded as $oLink) {
|
||||
// Make sure that the objects in the set point to "this"
|
||||
$oLink->Set($sExtKeyToMe, $oHostObject->GetKey());
|
||||
|
||||
@@ -736,6 +749,7 @@ class ormLinkSet implements iDBObjectSetIterator, Iterator, SeekableIterator
|
||||
$oLink->DBClone();
|
||||
}
|
||||
}
|
||||
$oLink->SetLinkHostObject($oHostObject);
|
||||
$oLink->DBWrite();
|
||||
|
||||
$this->aPreserved[$oLink->GetKey()] = $oLink;
|
||||
|
||||
@@ -119,6 +119,10 @@ $ibo-fieldsorter--selected--background-color: $ibo-color-blue-200 !default;
|
||||
&.selected {
|
||||
background-color: $ibo-datatable--row--background-color--is-selected;
|
||||
}
|
||||
|
||||
.ibo-datatable--row-actions-toolbar{
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
css/backoffice/pages/_csv-import.scss
vendored
7
css/backoffice/pages/_csv-import.scss
vendored
@@ -27,11 +27,6 @@ tr.ibo-csv-import--row-unchanged td {
|
||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||
}
|
||||
|
||||
.wizContainer table tr.ibo-csv-import--row-error td {
|
||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||
background-color: $ibo-color-red-200;
|
||||
}
|
||||
|
||||
tr.ibo-csv-import--row-modified td {
|
||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||
}
|
||||
@@ -44,4 +39,4 @@ tr.ibo-csv-import--row-added td {
|
||||
font-size: 4em;
|
||||
color: $ibo-color-primary-400;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,22 @@ use DBObjectSet;
|
||||
|
||||
class DBToolsUtils
|
||||
{
|
||||
private static bool $bAnalyzed = false;
|
||||
|
||||
private final static function AnalyzeTables()
|
||||
{
|
||||
if (self::$bAnalyzed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oResult = CMDBSource::Query('SHOW TABLES;');
|
||||
while ($aRow = $oResult->fetch_array()) {
|
||||
$sTable = $aRow['0'];
|
||||
CMDBSource::Query("ANALYZE TABLE `$sTable`; ");
|
||||
}
|
||||
self::$bAnalyzed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws \CoreException
|
||||
@@ -22,6 +38,7 @@ class DBToolsUtils
|
||||
*/
|
||||
public final static function GetDatabaseSize()
|
||||
{
|
||||
self::AnalyzeTables();
|
||||
$sSchema = CMDBSource::DBName();
|
||||
|
||||
$sReq = <<<EOF
|
||||
@@ -48,6 +65,7 @@ EOF;
|
||||
*/
|
||||
public final static function GetDBDataSize()
|
||||
{
|
||||
self::AnalyzeTables();
|
||||
$sSchema = CMDBSource::DBName();
|
||||
|
||||
$sReq = <<<EOF
|
||||
@@ -74,6 +92,7 @@ EOF;
|
||||
*/
|
||||
public final static function GetDBIndexSize()
|
||||
{
|
||||
self::AnalyzeTables();
|
||||
$sSchema = CMDBSource::DBName();
|
||||
|
||||
$sReq = <<<EOF
|
||||
@@ -127,6 +146,7 @@ EOF;
|
||||
|
||||
public static function GetDBTablesInfo()
|
||||
{
|
||||
self::AnalyzeTables();
|
||||
$sSchema = CMDBSource::DBName();
|
||||
|
||||
$sReq = <<<EOF
|
||||
|
||||
@@ -255,4 +255,50 @@
|
||||
</presentation>
|
||||
</class>
|
||||
</classes>
|
||||
<events>
|
||||
<event id="EVENT_SERVICE_ADD_ATTACHMENT_TO_OBJECT" _delta="define">
|
||||
<description>An attachment has been added to an object</description>
|
||||
<replaces>Attachment::AfterUpdate</replaces>
|
||||
<sources>
|
||||
<source id="Attachment">Attachment</source>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The attachment updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="target_object">
|
||||
<description>The object to which the attachment is linked</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
<event id="EVENT_SERVICE_REMOVE_ATTACHMENT_FROM_OBJECT" _delta="define">
|
||||
<description>An attachment has been removed from an object</description>
|
||||
<replaces>Attachment::AfterUpdate</replaces>
|
||||
<sources>
|
||||
<source id="Attachment">Attachment</source>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<event_data>
|
||||
<event_datum id="object">
|
||||
<description>The attachment updated</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="target_object">
|
||||
<description>The object to which the attachment is linked</description>
|
||||
<type>DBObject</type>
|
||||
</event_datum>
|
||||
<event_datum id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</event_datum>
|
||||
</event_data>
|
||||
</event>
|
||||
</events>
|
||||
</itop_design>
|
||||
|
||||
@@ -304,6 +304,8 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
|
||||
// Remove attachments that are no longer attached to the current object
|
||||
if (in_array($oAttachment->GetKey(), $aRemovedAttachmentIds))
|
||||
{
|
||||
$aData = ['target_object' => $oObject];
|
||||
$oAttachment->FireEvent(EVENT_SERVICE_REMOVE_ATTACHMENT_FROM_OBJECT, $aData);
|
||||
$oAttachment->DBDelete();
|
||||
$aActions[] = self::GetActionChangeOp($oAttachment, false /* false => deletion */);
|
||||
}
|
||||
@@ -332,6 +334,8 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
|
||||
$oAttachment->DBUpdate();
|
||||
// temporary attachment confirmed, list it in the history
|
||||
$aActions[] = self::GetActionChangeOp($oAttachment, true /* true => creation */);
|
||||
$aData = ['target_object' => $oObject];
|
||||
$oAttachment->FireEvent(EVENT_SERVICE_ADD_ATTACHMENT_TO_OBJECT, $aData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Config\Validator\iTopConfigAstValidator;
|
||||
use Combodo\iTop\Config\Validator\iTopConfigSyntaxValidator;
|
||||
|
||||
require_once(APPROOT.'application/application.inc.php');
|
||||
require_once(APPROOT.'application/startup.inc.php');
|
||||
require_once(APPROOT.'application/loginwebpage.class.inc.php');
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1472,7 +1472,7 @@
|
||||
<code><![CDATA[ protected function OnInsert()
|
||||
|
||||
{
|
||||
parent::OnInsert();
|
||||
parent::OnInsert();
|
||||
$this->ComputeImpactedItems();
|
||||
$this->SetIfNull('last_update', time());
|
||||
$this->SetIfNull('start_date', time());
|
||||
@@ -1484,7 +1484,7 @@
|
||||
<type>Overload-DBObject</type>
|
||||
<code><![CDATA[ protected function OnUpdate()
|
||||
{
|
||||
parent::OnUpdate();
|
||||
parent::OnUpdate();
|
||||
$aChanges = $this->ListChanges();
|
||||
if (array_key_exists('functionalcis_list', $aChanges))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<itop_design version="3.1">
|
||||
<events>
|
||||
<event id="EVENT_SERVICE_DISPLAY_OBJECT_DETAILS" _delta="define">
|
||||
<description>An object details is about to be displayed to a user</description>
|
||||
<sources>
|
||||
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
|
||||
</sources>
|
||||
<arguments>
|
||||
<argument id="object">
|
||||
<description>The object displayed</description>
|
||||
<type>DBObject</type>
|
||||
</argument>
|
||||
<argument id="debug_info">
|
||||
<description>Debug string</description>
|
||||
<type>string</type>
|
||||
</argument>
|
||||
</arguments>
|
||||
</event>
|
||||
</events>
|
||||
</itop_design>
|
||||
@@ -195,6 +195,8 @@ class ObjectController extends BrickController
|
||||
$sObjectClass = get_class($oObject);
|
||||
$sObjectId = $oObject->GetKey();
|
||||
|
||||
$oObject->FireEvent(EVENT_SERVICE_DISPLAY_OBJECT_DETAILS);
|
||||
|
||||
$aData = array('sMode' => 'view');
|
||||
$aData['form'] = $oObjectFormHandler->HandleForm($oRequest, $aData['sMode'], $sObjectClass, $sObjectId);
|
||||
$aData['form']['title'] = Dict::Format('Brick:Portal:Object:Form:View:Title', MetaModel::GetName($sObjectClass),
|
||||
|
||||
@@ -1198,7 +1198,7 @@
|
||||
</state>
|
||||
</states>
|
||||
</lifecycle>
|
||||
<methods>
|
||||
<methods>
|
||||
<method id="GetTicketRefFormat">
|
||||
<static>true</static>
|
||||
<access>public</access>
|
||||
@@ -1343,7 +1343,6 @@
|
||||
<type>Overload-DBObject</type>
|
||||
<code><![CDATA[ public function ComputeValues()
|
||||
{
|
||||
|
||||
// Compute the priority of the ticket
|
||||
$this->Set('priority', $this->ComputePriority());
|
||||
|
||||
|
||||
@@ -216,7 +216,7 @@
|
||||
<count_max>0</count_max>
|
||||
</field>
|
||||
</fields>
|
||||
<methods>
|
||||
<methods>
|
||||
<method id="DBInsertNoReload">
|
||||
<static>false</static>
|
||||
<access>public</access>
|
||||
|
||||
@@ -644,9 +644,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte třídu pro hledání: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Upraveno',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nemůže být změněno - důvod: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemůže být změněno na %1$s - důvod: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Žádná shoda',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Chybí povinná hodnota',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nalezeno %1$s objektů',
|
||||
'UI:CSVReport-Row-Unchanged' => 'nezměněn',
|
||||
|
||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vælg klasse at søge efter: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Ændret',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Kunne ikke ændres - årsag: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Kunne ikke ændres til %1$s - årsag: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Mangler obligatorisk værdi',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Tvetydig: fandt %1$s objekter',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Uændret',
|
||||
|
||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Wählen Sie für die Suche die Klasse aus: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modifiziert',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Konnte nicht geändert werden - Grund: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Konnte nicht zu %1$s geändert werden - Grund: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Kein Treffer',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Pflichtfeld fehlt',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Doppeldeutig: %1$s Objekte gefunden',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Unverändert',
|
||||
|
||||
@@ -656,9 +656,14 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modified',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Invalid value for attribute',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'',
|
||||
'UI:CSVReport-Value-NoMatch-PossibleValues' => 'Some possible \'%1$s\' value(s): %2$s',
|
||||
'UI:CSVReport-Value-NoMatch-NoObject' => 'There are no \'%1$s\' objects',
|
||||
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => 'There are no \'%1$s\' objects found with your current profile',
|
||||
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => 'There are some \'%1$s\' objects not visible with your current profile',
|
||||
|
||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects',
|
||||
'UI:CSVReport-Row-Unchanged' => 'unchanged',
|
||||
@@ -672,11 +677,13 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:CSVReport-Value-Issue-Readonly' => 'The attribute \'%1$s\' is read-only and cannot be modified (current value: %2$s, proposed value: %3$s)',
|
||||
'UI:CSVReport-Value-Issue-Format' => 'Failed to process input: %1$s',
|
||||
'UI:CSVReport-Value-Issue-NoMatch' => 'Unexpected value for attribute \'%1$s\': no match found, check spelling',
|
||||
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s',
|
||||
'UI:CSVReport-Value-Issue-Unknown' => 'Unexpected value for attribute \'%1$s\': %2$s',
|
||||
'UI:CSVReport-Row-Issue-Inconsistent' => 'Attributes not consistent with each others: %1$s',
|
||||
'UI:CSVReport-Row-Issue-Attribute' => 'Unexpected attribute value(s)',
|
||||
'UI:CSVReport-Row-Issue-MissingExtKey' => 'Could not be created, due to missing external key(s): %1$s',
|
||||
'UI:CSVReport-Row-Issue-DateFormat' => 'wrong date format',
|
||||
'UI:CSVReport-Row-Issue-ExpectedDateFormat' => 'Expected format: %1$s',
|
||||
'UI:CSVReport-Row-Issue-Reconciliation' => 'failed to reconcile',
|
||||
'UI:CSVReport-Row-Issue-Ambiguous' => 'ambiguous reconciliation',
|
||||
'UI:CSVReport-Row-Issue-Internal' => 'Internal error: %1$s, %2$s',
|
||||
@@ -806,6 +813,11 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:Schema:DisplaySelector/Code' => 'Code~~',
|
||||
'UI:Schema:Attribute/Filter' => 'Filter~~',
|
||||
'UI:Schema:DefaultNullValue' => 'Default null : "%1$s"~~',
|
||||
'UI:Schema:Events' => 'Events',
|
||||
'UI:Schema:Events:Defined' => 'Defined events',
|
||||
'UI:Schema:Events:NoEvent' => 'No event defined',
|
||||
'UI:Schema:Events:Listeners' => 'Event listeners',
|
||||
'UI:Schema:Events:NoListener' => 'No event listener',
|
||||
'UI:LinksWidget:Autocomplete+' => 'Type the first 3 characters...',
|
||||
'UI:Edit:SearchQuery' => 'Select a predefined query',
|
||||
'UI:Edit:TestQuery' => 'Test query',
|
||||
|
||||
@@ -644,9 +644,9 @@ Esperamos distrute de esta versión tanto como nosotros la imaginamos y creamos.
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleccione la clase a buscar: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modificado',
|
||||
'UI:CSVReport-Value-SetIssue' => 'No puede ser modificado - motivo: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'No puede ser cambiado a %1$s - motivo: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No hay Coincidencias',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Falta valor obligatorio',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambigüedad: encontrados %1$s objetos',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Sin Cambios',
|
||||
|
||||
@@ -639,9 +639,14 @@ Nous espérons que vous aimerez cette version autant que nous avons eu du plaisi
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Sélectionnez le type d\'objets à rechercher : ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modifié',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Modification impossible - cause : %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\' - cause : %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Pas de correspondance',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Valeur invalide',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\'',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Pas de correspondance avec \'%1$s\'',
|
||||
'UI:CSVReport-Value-NoMatch-PossibleValues' => 'Valeur(s) possible(s) pour l\'objet \'%1$s\' : %2$s',
|
||||
'UI:CSVReport-Value-NoMatch-NoObject' => 'Il n\'y a aucun objet \'%1$s\'',
|
||||
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => 'Il n\'y a aucun objet \'%1$s\' visible par votre utilisateur',
|
||||
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => 'Il existe des objet(s) \'%1$s\' non visible(s) par votre utilisateur',
|
||||
|
||||
'UI:CSVReport-Value-Missing' => 'Absence de valeur obligatoire',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambigüité: %1$d objets trouvés',
|
||||
'UI:CSVReport-Row-Unchanged' => 'inchangé',
|
||||
|
||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Keresendő osztály kiválasztása:',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
||||
|
||||
@@ -644,9 +644,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleziona la classe per la ricerca: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
||||
|
||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => '検索するクラスを選択してください。',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => '修正済み',
|
||||
'UI:CSVReport-Value-SetIssue' => '変更出来ません - 理由: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s へ変更出来ません - 理由: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'マッチしません',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => '必須の値がありません',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'あいまいな値: %1$s オブジェクト',
|
||||
'UI:CSVReport-Row-Unchanged' => '未変更',
|
||||
|
||||
@@ -644,9 +644,9 @@ We hopen dat je even hard van deze versie geniet als dat we zelf ervan hebben ge
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecteer de klasse om te zoeken: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Aangepast',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Kon niet worden aangepast - reden: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Kon niet worden aangepast naar %1$s - reden: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Geen match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Ontbrekende verplichte waarde',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Onduidelijk: gevonden %1$s objecten',
|
||||
'UI:CSVReport-Row-Unchanged' => 'onveranderd',
|
||||
|
||||
@@ -643,9 +643,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Wybierz klasę do przeszukania: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Zmodyfikowano',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nie można było zmienić - powód: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nie można zmienić na %1$s - powód: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Nie pasuje',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Brak wymaganej wartości',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Uwaga: znaleziono %1$s obiektów',
|
||||
'UI:CSVReport-Row-Unchanged' => 'niezmieniony',
|
||||
|
||||
@@ -644,9 +644,9 @@ Esperamos que você goste desta versão tanto quanto gostamos de imaginá-la e c
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecione a classe para pesquisar: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modificado',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Não pode ser modificado - motivo: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Não pode ser modificado para %1$s - motivo: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Não corresponde',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Faltando valor obrigatório',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambíguo: encontrado %1$s objeto(s)',
|
||||
'UI:CSVReport-Row-Unchanged' => 'inalterado',
|
||||
|
||||
@@ -645,9 +645,9 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Выбор класса для поиска: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Изменен',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Не может быть изменен - причина: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Не может быть изменен %1$s - причина: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Нет совпадений',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Отсутствует обязательное значение',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Неоднозначное сопоставление: найдено %1$s объектов',
|
||||
'UI:CSVReport-Row-Unchanged' => 'без изменений',
|
||||
|
||||
@@ -634,9 +634,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte triedu na vyhľadávanie: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Upravený',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nemožno zmeniť - dôvod: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemožno zmeniť na %1$s - dôvod: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Žiadna zhoda',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Chýbajúca povinná hodnota',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nájdených %1$s objektov',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Nezmený',
|
||||
|
||||
@@ -661,9 +661,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Aranacak sınıfı seçiniz: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Değiştiridi',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Değiştirilemedi - Sebep: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s olarak değiştirilemedi - Sebep: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Eşleşme yok',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Eksik Zorunlu Değer',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Belirsiz: %1$s nesnelerini buldum',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Değiştirilmedi',
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
|
||||
// Display DataTable
|
||||
Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Datatables:Language:Processing' => 'Please wait...',
|
||||
'UI:Datatables:Language:LengthMenu' => '_MENU_ per page',
|
||||
'UI:Datatables:Language:ZeroRecords' => 'No result',
|
||||
'UI:Datatables:Language:Info' => '_TOTAL_ item(s)',
|
||||
'UI:Datatables:Language:InfoEmpty' => 'No information',
|
||||
'UI:Datatables:Language:EmptyTable' => 'No data available in this table',
|
||||
'UI:Datatables:Language:Error' => 'An error occured while running the query',
|
||||
'UI:Datatables:Language:DisplayLength:All' => 'All',
|
||||
'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort',
|
||||
'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort',
|
||||
'UI:Datatables:Language:Processing' => 'Please wait...',
|
||||
'UI:Datatables:Language:LengthMenu' => '_MENU_ per page',
|
||||
'UI:Datatables:Language:ZeroRecords' => 'No result',
|
||||
'UI:Datatables:Language:Info' => '_TOTAL_ item(s)',
|
||||
'UI:Datatables:Language:InfoEmpty' => 'No information',
|
||||
'UI:Datatables:Language:EmptyTable' => 'No data available in this table',
|
||||
'UI:Datatables:Language:Error' => 'An error occured while running the query',
|
||||
'UI:Datatables:Language:DisplayLength:All' => 'All',
|
||||
'UI:Datatables:Language:Sort:Ascending' => 'enable for an ascending sort',
|
||||
'UI:Datatables:Language:Sort:Descending' => 'enable for a descending sort',
|
||||
'UI:Datatables:Column:RowActions:Label' => '',
|
||||
'UI:Datatables:Column:RowActions:Description' => '',
|
||||
));
|
||||
@@ -18,14 +18,16 @@
|
||||
*/
|
||||
// Display DataTable
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'UI:Datatables:Language:Processing' => 'Patientez ...',
|
||||
'UI:Datatables:Language:LengthMenu' => '_MENU_ par page',
|
||||
'UI:Datatables:Language:ZeroRecords' => 'Pas de résultat',
|
||||
'UI:Datatables:Language:Info' => '_TOTAL_ élément(s)',
|
||||
'UI:Datatables:Language:InfoEmpty' => 'Pas d\'information',
|
||||
'UI:Datatables:Language:EmptyTable' => 'Pas de résultat',
|
||||
'UI:Datatables:Language:Error' => 'Erreur lors du chargement des données',
|
||||
'UI:Datatables:Language:DisplayLength:All' => 'Tous',
|
||||
'UI:Datatables:Language:Sort:Ascending' => 'tri croissant',
|
||||
'UI:Datatables:Language:Sort:Descending' => 'tri décroissant',
|
||||
'UI:Datatables:Language:Processing' => 'Patientez ...',
|
||||
'UI:Datatables:Language:LengthMenu' => '_MENU_ par page',
|
||||
'UI:Datatables:Language:ZeroRecords' => 'Pas de résultat',
|
||||
'UI:Datatables:Language:Info' => '_TOTAL_ élément(s)',
|
||||
'UI:Datatables:Language:InfoEmpty' => 'Pas d\'information',
|
||||
'UI:Datatables:Language:EmptyTable' => 'Pas de résultat',
|
||||
'UI:Datatables:Language:Error' => 'Erreur lors du chargement des données',
|
||||
'UI:Datatables:Language:DisplayLength:All' => 'Tous',
|
||||
'UI:Datatables:Language:Sort:Ascending' => 'tri croissant',
|
||||
'UI:Datatables:Language:Sort:Descending' => 'tri décroissant',
|
||||
'UI:Datatables:Column:RowActions:Label' => '',
|
||||
'UI:Datatables:Column:RowActions:Description' => '',
|
||||
));
|
||||
@@ -649,9 +649,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => '选择要搜索的类别: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => '已修改',
|
||||
'UI:CSVReport-Value-SetIssue' => '无法修改 - 原因: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '无法修改成 %1$s - 原因: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => '不匹配',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => '缺少必填项',
|
||||
'UI:CSVReport-Value-Ambiguous' => '模糊匹配: 找到 %1$s 个对象',
|
||||
'UI:CSVReport-Row-Unchanged' => '保持不变',
|
||||
|
||||
@@ -78,4 +78,30 @@ function getMultipleSelectionParams(listId)
|
||||
});
|
||||
|
||||
return oRes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return column JSON declaration for row actions.
|
||||
* Could be part of column or columnDefs declaration of datatable.js.
|
||||
*
|
||||
* @param sTableId
|
||||
* @param iColumnTargetIndex
|
||||
* @returns {*}
|
||||
* @since 3.1.0
|
||||
*/
|
||||
function getRowActionsColumnDefinition(sTableId, iColumnTargetIndex = -1)
|
||||
{
|
||||
let aColumn = {
|
||||
type: "html",
|
||||
orderable: false,
|
||||
render: function ( data, type, row, meta ) {
|
||||
return $(`#${sTableId}_actions_buttons_template`).html();
|
||||
}
|
||||
};
|
||||
|
||||
if (iColumnTargetIndex !== -1) {
|
||||
aColumn['targets'] = iColumnTargetIndex;
|
||||
}
|
||||
|
||||
return aColumn;
|
||||
}
|
||||
|
||||
@@ -385,12 +385,10 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
||||
*/
|
||||
this.OnValueChange = function (iLink, iUniqueId, sAttCode, value, $oSourceObject) {
|
||||
let sFormPrefix = me.iInputId;
|
||||
if (iLink > 0)
|
||||
{
|
||||
if (iLink > 0) {
|
||||
// Modifying an existing link
|
||||
let oModified = me.aModified[iLink];
|
||||
if (oModified == undefined)
|
||||
{
|
||||
if (oModified == undefined) {
|
||||
// Still not marked as modified
|
||||
oModified = {};
|
||||
oModified['formPrefix'] = sFormPrefix;
|
||||
@@ -398,17 +396,26 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH
|
||||
// Weird formatting, aligned with the output of the direct links widget (new links to be created)
|
||||
oModified['attr_'+sFormPrefix+sAttCode] = value;
|
||||
me.aModified[iLink] = oModified;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
// Modifying a newly added link - the structure should already be up to date
|
||||
if (iUniqueId < 0)
|
||||
{
|
||||
if (iUniqueId < 0) {
|
||||
iUniqueId = -iUniqueId;
|
||||
}
|
||||
me.aAdded[iUniqueId]['attr_'+sFormPrefix+sAttCode] = value;
|
||||
}
|
||||
};
|
||||
this.AddModified = function (iLink, sFormPrefix, sAttCode, value) {
|
||||
// Modifying an existing link
|
||||
let oModified = me.aModified[iLink];
|
||||
if (oModified == undefined) {
|
||||
// Still not marked as modified
|
||||
oModified = {};
|
||||
oModified['formPrefix'] = sFormPrefix;
|
||||
}
|
||||
// Weird formatting, aligned with the output of the direct links widget (new links to be created)
|
||||
oModified['attr_'+sFormPrefix+sAttCode] = value;
|
||||
me.aModified[iLink] = oModified;
|
||||
};
|
||||
|
||||
this.OnFormSubmit = function () {
|
||||
let oDiv = $('#linkedset_'+me.id);
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
// autoload.php @generated by Composer
|
||||
|
||||
if (PHP_VERSION_ID < 50600) {
|
||||
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f::getLoader();
|
||||
|
||||
@@ -149,7 +149,7 @@ class ClassLoader
|
||||
|
||||
/**
|
||||
* @return string[] Array of classname => path
|
||||
* @psalm-var array<string, string>
|
||||
* @psalm-return array<string, string>
|
||||
*/
|
||||
public function getClassMap()
|
||||
{
|
||||
|
||||
@@ -29,10 +29,6 @@ class InstalledVersions
|
||||
* @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
private static $canGetVendors;
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// autoload_classmap.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
@@ -227,6 +227,7 @@ return array(
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTableRow\\FormTableRow' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTableRow/FormTableRow.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTable\\FormTable' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTable/FormTable.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\StaticTable' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\tTableRowActions' => $baseDir . '/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadge' => $baseDir . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadge.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadgeUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldSet\\FieldSet' => $baseDir . '/sources/Application/UI/Base/Component/FieldSet/FieldSet.php',
|
||||
@@ -272,6 +273,8 @@ return array(
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\QuickCreate\\QuickCreateHelper' => $baseDir . '/sources/Application/UI/Base/Component/QuickCreate/QuickCreateHelper.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Spinner\\Spinner' => $baseDir . '/sources/Application/UI/Base/Component/Spinner/Spinner.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Spinner\\SpinnerUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Spinner/SpinnerUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Template\\Template' => $baseDir . '/sources/Application/UI/Base/Component/Template/Template.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Template\\TemplateUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Template/TemplateUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Text\\Text' => $baseDir . '/sources/Application/UI/Base/Component/Text/Text.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Title\\Title' => $baseDir . '/sources/Application/UI/Base/Component/Title/Title.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Title\\TitleUIBlockFactory' => $baseDir . '/sources/Application/UI/Base/Component/Title/TitleUIBlockFactory.php',
|
||||
@@ -410,6 +413,9 @@ return array(
|
||||
'Combodo\\iTop\\Renderer\\FieldRenderer' => $baseDir . '/sources/Renderer/FieldRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php',
|
||||
'Combodo\\iTop\\Service\\EventData' => $baseDir . '/sources/Application/Service/EventData.php',
|
||||
'Combodo\\iTop\\Service\\EventHelper' => $baseDir . '/sources/Application/Service/EventHelper.php',
|
||||
'Combodo\\iTop\\Service\\EventService' => $baseDir . '/sources/Application/Service/EventService.php',
|
||||
'CompileCSSService' => $baseDir . '/application/compilecssservice.class.inc.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'Config' => $baseDir . '/core/config.class.inc.php',
|
||||
@@ -1015,6 +1021,7 @@ return array(
|
||||
'MySQLHasGoneAwayException' => $baseDir . '/application/exceptions/mysql/MySQLHasGoneAwayException.php',
|
||||
'MySQLNoTransactionException' => $baseDir . '/application/exceptions/mysql/MySQLNoTransactionException.php',
|
||||
'MySQLQueryHasNoResultException' => $baseDir . '/application/exceptions/mysql/MySQLQueryHasNoResultException.php',
|
||||
'MySQLTransactionNotClosedException' => $baseDir . '/application/exceptions/mysql/MySQLTransactionNotClosedException.php',
|
||||
'NestedQueryExpression' => $baseDir . '/core/oql/expression.class.inc.php',
|
||||
'NestedQueryOqlExpression' => $baseDir . '/core/oql/oqlquery.class.inc.php',
|
||||
'NewObjectMenuNode' => $baseDir . '/application/menunode.class.inc.php',
|
||||
@@ -1385,6 +1392,7 @@ return array(
|
||||
'RotatingLogFileNameBuilder' => $baseDir . '/core/log.class.inc.php',
|
||||
'RowStatus' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Disappeared' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Error' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Issue' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Modify' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_NewObj' => $baseDir . '/core/bulkchange.class.inc.php',
|
||||
|
||||
@@ -2,24 +2,24 @@
|
||||
|
||||
// autoload_files.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
|
||||
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
|
||||
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
|
||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
|
||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => $vendorDir . '/laminas/laminas-servicemanager/src/autoload.php',
|
||||
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
||||
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// autoload_namespaces.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// autoload_psr4.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
|
||||
@@ -25,33 +25,20 @@ class ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f', 'loadClassLoader'));
|
||||
|
||||
$includePaths = require __DIR__ . '/include_paths.php';
|
||||
$includePaths[] = get_include_path();
|
||||
set_include_path(implode(PATH_SEPARATOR, $includePaths));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::getInitializer($loader));
|
||||
} else {
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php';
|
||||
if ($classMap) {
|
||||
$loader->addClassMap($classMap);
|
||||
}
|
||||
}
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::getInitializer($loader));
|
||||
|
||||
$loader->setClassMapAuthoritative(true);
|
||||
$loader->register(true);
|
||||
|
||||
if ($useStaticLoader) {
|
||||
$includeFiles = Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$files;
|
||||
} else {
|
||||
$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
}
|
||||
$includeFiles = \Composer\Autoload\ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f::$files;
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file);
|
||||
}
|
||||
@@ -60,11 +47,16 @@ class ComposerAutoloaderInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fileIdentifier
|
||||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
function composerRequire7f81b4a2a468a061c306af5e447a9a9f($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
||||
require $file;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,22 +7,22 @@ namespace Composer\Autoload;
|
||||
class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
{
|
||||
public static $files = array (
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
|
||||
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
|
||||
'23c18046f52bef3eea034657bafda50f' => __DIR__ . '/..' . '/symfony/polyfill-php81/bootstrap.php',
|
||||
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
|
||||
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php',
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
|
||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
|
||||
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
|
||||
'c9d07b32a2e02bc0fc582d4f0c1b56cc' => __DIR__ . '/..' . '/laminas/laminas-servicemanager/src/autoload.php',
|
||||
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
|
||||
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
|
||||
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
|
||||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
@@ -592,6 +592,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTableRow\\FormTableRow' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTableRow/FormTableRow.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\FormTable\\FormTable' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/StaticTable/FormTable/FormTable.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\StaticTable\\StaticTable' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/StaticTable/StaticTable.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\DataTable\\tTableRowActions' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/DataTable/tTableRowActions.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadge' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadge.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldBadge\\FieldBadgeUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldBadge/FieldBadgeUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\FieldSet\\FieldSet' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/FieldSet/FieldSet.php',
|
||||
@@ -637,6 +638,8 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\QuickCreate\\QuickCreateHelper' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/QuickCreate/QuickCreateHelper.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Spinner\\Spinner' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Spinner/Spinner.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Spinner\\SpinnerUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Spinner/SpinnerUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Template\\Template' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Template/Template.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Template\\TemplateUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Template/TemplateUIBlockFactory.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Text\\Text' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Text/Text.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Title\\Title' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Title/Title.php',
|
||||
'Combodo\\iTop\\Application\\UI\\Base\\Component\\Title\\TitleUIBlockFactory' => __DIR__ . '/../..' . '/sources/Application/UI/Base/Component/Title/TitleUIBlockFactory.php',
|
||||
@@ -775,6 +778,9 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'Combodo\\iTop\\Renderer\\FieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FieldRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php',
|
||||
'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php',
|
||||
'Combodo\\iTop\\Service\\EventData' => __DIR__ . '/../..' . '/sources/Application/Service/EventData.php',
|
||||
'Combodo\\iTop\\Service\\EventHelper' => __DIR__ . '/../..' . '/sources/Application/Service/EventHelper.php',
|
||||
'Combodo\\iTop\\Service\\EventService' => __DIR__ . '/../..' . '/sources/Application/Service/EventService.php',
|
||||
'CompileCSSService' => __DIR__ . '/../..' . '/application/compilecssservice.class.inc.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'Config' => __DIR__ . '/../..' . '/core/config.class.inc.php',
|
||||
@@ -1380,6 +1386,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'MySQLHasGoneAwayException' => __DIR__ . '/../..' . '/application/exceptions/mysql/MySQLHasGoneAwayException.php',
|
||||
'MySQLNoTransactionException' => __DIR__ . '/../..' . '/application/exceptions/mysql/MySQLNoTransactionException.php',
|
||||
'MySQLQueryHasNoResultException' => __DIR__ . '/../..' . '/application/exceptions/mysql/MySQLQueryHasNoResultException.php',
|
||||
'MySQLTransactionNotClosedException' => __DIR__ . '/../..' . '/application/exceptions/mysql/MySQLTransactionNotClosedException.php',
|
||||
'NestedQueryExpression' => __DIR__ . '/../..' . '/core/oql/expression.class.inc.php',
|
||||
'NestedQueryOqlExpression' => __DIR__ . '/../..' . '/core/oql/oqlquery.class.inc.php',
|
||||
'NewObjectMenuNode' => __DIR__ . '/../..' . '/application/menunode.class.inc.php',
|
||||
@@ -1750,6 +1757,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
|
||||
'RotatingLogFileNameBuilder' => __DIR__ . '/../..' . '/core/log.class.inc.php',
|
||||
'RowStatus' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Disappeared' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Error' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Issue' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_Modify' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
'RowStatus_NewObj' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// include_paths.php @generated by Composer
|
||||
|
||||
$vendorDir = dirname(dirname(__FILE__));
|
||||
$vendorDir = dirname(__DIR__);
|
||||
$baseDir = dirname($vendorDir);
|
||||
|
||||
return array(
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '7b60c9c71af3167cc075063ce67b836b96a9e2f0',
|
||||
'reference' => '8a3e07dd80c8316d68ad44a892c51f4ed5de572c',
|
||||
'name' => 'combodo/itop',
|
||||
'dev' => true,
|
||||
),
|
||||
@@ -25,7 +25,7 @@
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'reference' => '7b60c9c71af3167cc075063ce67b836b96a9e2f0',
|
||||
'reference' => '8a3e07dd80c8316d68ad44a892c51f4ed5de572c',
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'combodo/tcpdf' => array(
|
||||
|
||||
@@ -138,7 +138,7 @@ try {
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the most frequent (and regularly occuring) character among the given set, in the specified lines
|
||||
* @param array $aCSVData The input data, one entry per line
|
||||
@@ -174,7 +174,7 @@ try {
|
||||
}
|
||||
$iLine++;
|
||||
}
|
||||
|
||||
|
||||
$aScores = array();
|
||||
foreach($aGuesses as $sSep => $aData)
|
||||
{
|
||||
@@ -185,7 +185,7 @@ try {
|
||||
$sSeparator = $aKeys[0]; // Take the first key, the one with the best score
|
||||
return $sSeparator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to predict the CSV parameters based on the input data
|
||||
* @param string $sCSVData The input data
|
||||
@@ -196,10 +196,10 @@ try {
|
||||
$aData = explode("\n", $sCSVData);
|
||||
$sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line
|
||||
$sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line
|
||||
|
||||
|
||||
return array('separator' => $sSeparator, 'qualifier' => $sQualifier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a banner for the special "synchro" mode
|
||||
* @param WebPage $oP The Page for the output
|
||||
@@ -215,6 +215,7 @@ try {
|
||||
* Add a paragraph to the body of the page
|
||||
*
|
||||
* @param string $s_html
|
||||
* @param ?string $sLinkUrl
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -259,9 +260,9 @@ try {
|
||||
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
|
||||
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
|
||||
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
|
||||
|
||||
|
||||
$sChosenDateFormat = ($sDateTimeFormat == 'default') ? (string)AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
|
||||
|
||||
|
||||
if (!empty($sSynchroScope))
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sSynchroScope);
|
||||
@@ -276,7 +277,7 @@ try {
|
||||
$sSynchroScope = '';
|
||||
$aSynchroUpdate = null;
|
||||
}
|
||||
|
||||
|
||||
// Parse the data set
|
||||
$oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier, MetaModel::GetConfig()->Get('max_execution_time_per_loop'));
|
||||
$aData = $oCSVParser->ToArray($iSkippedLines);
|
||||
@@ -286,10 +287,10 @@ try {
|
||||
$aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error
|
||||
$iRealSkippedLines++;
|
||||
}
|
||||
|
||||
|
||||
// Format for the line numbers
|
||||
$sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3
|
||||
|
||||
|
||||
// Compute the list of search/reconciliation criteria
|
||||
$aSearchKeys = array();
|
||||
foreach($aSearchFields as $index => $sDummy)
|
||||
@@ -303,16 +304,16 @@ try {
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSearchKeys[$sSearchField] = '';
|
||||
$aSearchKeys[$sSearchField] = '';
|
||||
}
|
||||
if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField))
|
||||
{
|
||||
// Remove invalid or unmapped search fields
|
||||
$aSearchFields[$index] = null;
|
||||
unset($aSearchKeys[$sSearchField]);
|
||||
unset($aSearchKeys[$sSearchField]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Compute the list of fields and external keys to process
|
||||
$aExtKeys = array();
|
||||
$aAttributes = array();
|
||||
@@ -345,13 +346,13 @@ try {
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttributes[$sAttCode] = $iIndex;
|
||||
$aAttributes[$sAttCode] = $iIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$oMyChange = null;
|
||||
if (!$bSimulate)
|
||||
{
|
||||
@@ -360,7 +361,7 @@ try {
|
||||
CMDBObject::SetCurrentChangeFromParams($sUserString, CMDBChangeOrigin::CSV_INTERACTIVE);
|
||||
$oMyChange = CMDBObject::GetCurrentChange();
|
||||
}
|
||||
|
||||
|
||||
$oBulk = new BulkChange(
|
||||
$sClassName,
|
||||
$aData,
|
||||
@@ -370,7 +371,7 @@ try {
|
||||
empty($sSynchroScope) ? null : $sSynchroScope,
|
||||
$aSynchroUpdate,
|
||||
$sChosenDateFormat, // date format
|
||||
true // localize
|
||||
true // localize
|
||||
);
|
||||
$oBulk->SetReportHtml();
|
||||
|
||||
@@ -437,7 +438,6 @@ try {
|
||||
|
||||
case 'RowStatus_NewObj':
|
||||
$iCreated++;
|
||||
$sFinalClass = $aResRow['finalclass'];
|
||||
$sStatus = '<img src="../images/added.png" title="'.Dict::S('UI:CSVReport-Icon-Created').'">';
|
||||
$sCSSRowClass = 'ibo-csv-import--row-added';
|
||||
if ($bSimulate) {
|
||||
@@ -453,7 +453,7 @@ try {
|
||||
case 'RowStatus_Issue':
|
||||
$iErrors++;
|
||||
$sMessage .= GetDivAlert($oStatus->GetDescription());
|
||||
$sStatus = '<img src="../images/error.png" title="'.Dict::S('UI:CSVReport-Icon-Error').'">';//translate
|
||||
$sStatus = '<div class="ibo-csv-import--cell-error"><i class="fas fa-exclamation-triangle" title="'.Dict::S('UI:CSVReport-Icon-Error').'" /></div>';//translate
|
||||
$sCSSMessageClass = 'ibo-csv-import--cell-error';
|
||||
$sCSSRowClass = 'ibo-csv-import--row-error';
|
||||
if (array_key_exists($iLine, $aData)) {
|
||||
@@ -474,33 +474,36 @@ try {
|
||||
if (isset($aExternalKeysByColumn[$iNumber - 1])) {
|
||||
$sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
|
||||
$oExtKeyCellStatus = $aResRow[$sExtKeyName];
|
||||
switch (get_class($oExtKeyCellStatus)) {
|
||||
case 'CellStatus_Issue':
|
||||
case 'CellStatus_SearchIssue':
|
||||
case 'CellStatus_NullIssue':
|
||||
case 'CellStatus_Ambiguous':
|
||||
$sCellMessage .= GetDivAlert($oExtKeyCellStatus->GetDescription());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
$oCellStatus = $oExtKeyCellStatus;
|
||||
}
|
||||
$sHtmlValue = $oCellStatus->GetDisplayableValue();
|
||||
switch (get_class($oCellStatus)) {
|
||||
case 'CellStatus_Issue':
|
||||
case 'CellStatus_NullIssue':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'</div>';
|
||||
break;
|
||||
|
||||
case 'CellStatus_SearchIssue':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">ERROR: '.$sHtmlValue.$sCellMessage.'</div>';
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||
'<a href="',
|
||||
$oCellStatus->GetSearchLinkUrl(),
|
||||
'"><div class="ibo-csv-import--cell-error">',
|
||||
Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue),
|
||||
GetDivAlert($oCellStatus->GetDescription()),
|
||||
'<i class="fas fa-search"></i></div><a/>'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'CellStatus_Ambiguous':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error" >'.Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue).$sCellMessage.'</div>';
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||
'<a href="',
|
||||
$oCellStatus->GetSearchLinkUrl(),
|
||||
'"><i class="fas fa-search"/><div class="ibo-csv-import--cell-error">',
|
||||
Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue),
|
||||
GetDivAlert($oCellStatus->GetDescription()),
|
||||
'<i class="fas fa-search"></i></div><a/>'
|
||||
);
|
||||
break;
|
||||
|
||||
case 'CellStatus_Modify':
|
||||
@@ -589,7 +592,7 @@ try {
|
||||
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
|
||||
$oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
|
||||
|
||||
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<img src="../images/error.png"> '.sprintf($aDisplayFilters['errors'], $iErrors), '', "1", "show_errors", "checkbox");
|
||||
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<i class="fas fa-exclamation-triangle" style="color:#A33; background-color: #FFF0F0;"> '.sprintf($aDisplayFilters['errors'], $iErrors).'</i></i>', '', "1", "show_errors", "checkbox");
|
||||
$oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
|
||||
$oCheckBoxUnchanged->SetBeforeInput(false);
|
||||
$oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
@@ -676,7 +679,7 @@ try {
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$sErrors = json_encode(Dict::Format('UI:CSVImportError_items', $iErrors));
|
||||
$sCreated = json_encode(Dict::Format('UI:CSVImportCreated_items', $iCreated));
|
||||
$sModified = json_encode(Dict::Format('UI:CSVImportModified_items', $iModified));
|
||||
@@ -771,7 +774,7 @@ EOF
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/**
|
||||
* Perform the actual load of the CSV data and display the results
|
||||
@@ -795,7 +798,7 @@ EOF
|
||||
$oField->AddSubBlock($oText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simulate the load of the CSV data and display the results
|
||||
* @param WebPage $oPage The web page to display the wizard
|
||||
@@ -807,7 +810,7 @@ EOF
|
||||
$oPage->AddSubBlock($oPanel);
|
||||
ProcessCSVData($oPage, true /* simulate */);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the mapping between the CSV column and the fields of the objects
|
||||
* @param WebPage $oPage The web page to display the wizard
|
||||
@@ -920,10 +923,10 @@ EOF
|
||||
$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
|
||||
$sFieldsMapping = addslashes(json_encode($aFieldsMapping));
|
||||
$sSearchFields = addslashes(json_encode($aSearchFields));
|
||||
|
||||
|
||||
$oPage->add_ready_script("DoMapping('$sFieldsMapping', '$sSearchFields');"); // There is already a class selected, run the mapping
|
||||
}
|
||||
|
||||
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
var aDefaultKeys = new Array();
|
||||
@@ -1139,7 +1142,7 @@ EOF
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the options of the CSV load and check for CSV parsing errors
|
||||
* @param WebPage $oPage The current web page
|
||||
@@ -1163,7 +1166,7 @@ EOF
|
||||
$sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data');
|
||||
}
|
||||
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
|
||||
|
||||
|
||||
// Compute a subset of the data set, now that we know the charset
|
||||
if ($sEncoding == 'UTF-8')
|
||||
{
|
||||
@@ -1180,7 +1183,7 @@ EOF
|
||||
{
|
||||
$sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData);
|
||||
}
|
||||
|
||||
|
||||
$aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data
|
||||
|
||||
$iSkippedLines = utils::ReadParam('nb_skipped_lines', '');
|
||||
@@ -1188,7 +1191,7 @@ EOF
|
||||
$sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data');
|
||||
if ($sTextQualifier == '') // May be set to an empty value by the previous page
|
||||
{
|
||||
$sTextQualifier = $aGuesses['qualifier'];
|
||||
$sTextQualifier = $aGuesses['qualifier'];
|
||||
}
|
||||
$sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier;
|
||||
$bHeaderLine = utils::ReadParam('header_line', 0);
|
||||
@@ -1606,7 +1609,7 @@ EOF
|
||||
null, AjaxTab::ENUM_TAB_PLACEHOLDER_MISC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch($iStep)
|
||||
{
|
||||
case 11:
|
||||
@@ -1614,45 +1617,45 @@ EOF
|
||||
$oPage = new AjaxPage('');
|
||||
BulkChange::DisplayImportHistory($oPage);
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tableHover();');
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
|
||||
break;
|
||||
|
||||
|
||||
case 10:
|
||||
// Case generated by BulkChange::DisplayImportHistory
|
||||
$iChange = (int)utils::ReadParam('changeid', 0);
|
||||
BulkChange::DisplayImportHistoryDetails($oPage, $iChange);
|
||||
break;
|
||||
|
||||
|
||||
case 5:
|
||||
LoadData($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 4:
|
||||
Preview($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 3:
|
||||
SelectMapping($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 2:
|
||||
SelectOptions($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 1:
|
||||
case 6: // Loop back here when we are done
|
||||
default:
|
||||
Welcome($oPage);
|
||||
}
|
||||
|
||||
|
||||
$oPage->output();
|
||||
}
|
||||
catch(CoreException $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
|
||||
$oP->output();
|
||||
|
||||
if (MetaModel::IsLogEnabledIssue())
|
||||
@@ -1680,8 +1683,8 @@ catch(Exception $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
|
||||
$oP->output();
|
||||
|
||||
if (MetaModel::IsLogEnabledIssue())
|
||||
@@ -1701,4 +1704,4 @@ catch(Exception $e)
|
||||
|
||||
IssueLog::Error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockF
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentWithSideContent;
|
||||
use Combodo\iTop\Service\EventService;
|
||||
|
||||
require_once('../approot.inc.php');
|
||||
require_once(APPROOT.'/application/application.inc.php');
|
||||
@@ -264,7 +265,92 @@ function DisplayTriggers($oPage, $sClass)
|
||||
cmdbAbstractObject::DisplaySet($oPage, $oSet, array('block_id' => 'triggers'));
|
||||
}
|
||||
|
||||
function DisplayEvents(WebPage $oPage, $sClass)
|
||||
{
|
||||
$aEvents = EventService::GetEventsByClass($sClass);
|
||||
$aColumns = [
|
||||
'event' => ['label' => 'Event'],
|
||||
'description' => ['label' => 'Description'],
|
||||
];
|
||||
$aRows = [];
|
||||
foreach ($aEvents as $sEvent => $aEventInfo) {
|
||||
$aDesc = $aEventInfo['description'];
|
||||
$aRows[] = [
|
||||
'event' => $sEvent,
|
||||
'description' => $aDesc['description'] ?? '',
|
||||
];
|
||||
}
|
||||
$oTable = DataTableUIBlockFactory::MakeForStaticData(Dict::S('UI:Schema:Events:Defined'), $aColumns, $aRows);
|
||||
$oPage->AddSubBlock($oTable);
|
||||
|
||||
$aSources = [];
|
||||
if (MetaModel::IsAbstract($sClass)) {
|
||||
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sChildClass) {
|
||||
if (!MetaModel::IsAbstract($sChildClass)) {
|
||||
$oObject = MetaModel::NewObject($sChildClass);
|
||||
$aSources[] = $oObject->GetObjectUniqId();
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
|
||||
$aSources[] = $sParentClass;
|
||||
}
|
||||
} else {
|
||||
$oObject = MetaModel::NewObject($sClass);
|
||||
$aSources[] = $oObject->GetObjectUniqId();
|
||||
foreach (MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL, false) as $sParentClass) {
|
||||
$aSources[] = $sParentClass;
|
||||
}
|
||||
}
|
||||
$aListeners = [];
|
||||
foreach (array_keys($aEvents) as $sEvent) {
|
||||
$aListeners = array_merge($aListeners, EventService::GetListeners($sEvent, $aSources));
|
||||
}
|
||||
usort($aListeners, function ($a, $b) {
|
||||
if ($a['event'] == $b['event']) {
|
||||
if ($a['priority'] == $b['priority']) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($a['priority'] > $b['priority']) ? 1 : -1;
|
||||
}
|
||||
return ($a['event'] > $b['event']) ? 1 : -1;
|
||||
});
|
||||
$aColumns = [
|
||||
'event' => ['label' => 'Event'],
|
||||
'listener' => ['label' => 'Listener'],
|
||||
'priority' => ['label' => 'Priority'],
|
||||
'module' => ['label' => 'Module'],
|
||||
];
|
||||
$aRows = [];
|
||||
$oReflectionClass = new ReflectionClass($sClass);
|
||||
foreach ($aListeners as $aListener) {
|
||||
if (is_object($aListener['callback'][0])) {
|
||||
$sListenerClass = $sClass;
|
||||
if ($aListener['callback'][0] != $sClass) {
|
||||
$oListenerReflectionClass = new ReflectionClass(get_class($aListener['callback'][0]));
|
||||
if (!$oListenerReflectionClass->isSubclassOf($sClass)) {
|
||||
$sListenerClass = get_class($aListener['callback'][0]);
|
||||
} elseif (!$oReflectionClass->hasMethod($aListener['callback'][1])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$sListener = $sListenerClass.'->'.$aListener['callback'][1].'(\Combodo\iTop\Service\EventData $oEventData)';
|
||||
} else {
|
||||
$sListener = $aListener['callback'][0].'::'.$aListener['callback'][1].'(\Combodo\iTop\Service\EventData $oEventData)';
|
||||
}
|
||||
$aRows[] = [
|
||||
'event' => $aListener['event'],
|
||||
'listener' => $sListener,
|
||||
'priority' => $aListener['priority'],
|
||||
'module' => $aListener['module'],
|
||||
];
|
||||
}
|
||||
|
||||
$oTable = DataTableUIBlockFactory::MakeForStaticData(Dict::S('UI:Schema:Events:Listeners'), $aColumns, $aRows);
|
||||
$oPage->AddSubBlock($oTable);
|
||||
|
||||
}
|
||||
/**
|
||||
* Display the list of classes from the business model
|
||||
*/
|
||||
@@ -1061,6 +1147,9 @@ EOF
|
||||
$oPage->SetCurrentTab('UI:Schema:Triggers');
|
||||
DisplayTriggers($oPage, $sClass);
|
||||
|
||||
$oPage->SetCurrentTab('UI:Schema:Events');
|
||||
DisplayEvents($oPage, $sClass);
|
||||
|
||||
$oPage->SetCurrentTab();
|
||||
$oPage->SetCurrentTabContainer();
|
||||
}
|
||||
|
||||
@@ -372,6 +372,7 @@ class MFCompiler
|
||||
}
|
||||
|
||||
$this->LoadSnippets();
|
||||
$this->LoadGlobalEventListeners();
|
||||
|
||||
// Compile, module by module
|
||||
//
|
||||
@@ -418,7 +419,16 @@ class MFCompiler
|
||||
$sCompiledCode .= $this->CompileConstant($oConstant)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$oEvents = $this->oFactory->ListEvents($sModuleName);
|
||||
if ($oEvents->length > 0)
|
||||
{
|
||||
foreach($oEvents as $oEvent)
|
||||
{
|
||||
$sCompiledCode .= $this->CompileEvent($oEvent, $sModuleName)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists($sModuleName, $this->aSnippets))
|
||||
{
|
||||
foreach( $this->aSnippets[$sModuleName]['before'] as $aSnippet)
|
||||
@@ -1082,6 +1092,20 @@ EOF
|
||||
return $sRet;
|
||||
}
|
||||
|
||||
protected function CompileEvent(DesignElement $oEvent, string $sModuleName)
|
||||
{
|
||||
$sName = $oEvent->getAttribute('id');
|
||||
$aEventDescription = DesignElement::ToArray($oEvent);
|
||||
|
||||
$sDescription = var_export($aEventDescription, true);
|
||||
$sConstant = $sName;
|
||||
|
||||
$sOutput = "define('$sConstant', '$sName');\n";
|
||||
$sOutput .= "Combodo\iTop\Service\EventService::RegisterEvent('$sName', $sDescription, '$sModuleName');\n";
|
||||
|
||||
return $sOutput;
|
||||
}
|
||||
|
||||
protected function CompileConstant($oConstant)
|
||||
{
|
||||
$sName = $oConstant->getAttribute('id');
|
||||
@@ -1273,6 +1297,7 @@ EOF
|
||||
foreach($oIndexes->getElementsByTagName('index') as $oIndex)
|
||||
{
|
||||
$sIndexId = $oIndex->getAttribute('id');
|
||||
/** @var DesignElement $oAttributes */
|
||||
$oAttributes = $oIndex->GetUniqueElement('attributes');
|
||||
foreach ($oAttributes->getElementsByTagName('attribute') as $oAttribute) {
|
||||
$aIndexes[$sIndexId][] = $oAttribute->getAttribute('id');
|
||||
@@ -1281,6 +1306,52 @@ EOF
|
||||
$aClassParams['indexes'] = var_export($aIndexes, true);
|
||||
}
|
||||
|
||||
$sEvents = '';
|
||||
$sMethods = '';
|
||||
$oHooks = $oClass->GetOptionalElement('event_listeners');
|
||||
if ($oHooks) {
|
||||
foreach ($oHooks->getElementsByTagName('listener') as $oListener) {
|
||||
/** @var DesignElement $oListener */
|
||||
$oEventNode = $oListener->GetUniqueElement('event');
|
||||
/** @var DesignElement $oEventNode $oEventNode */
|
||||
$sEventName = $oEventNode->GetText();
|
||||
$sListenerId = $oListener->getAttribute('id');
|
||||
$oCallback = $oListener->GetUniqueElement('callback', false);
|
||||
if (is_object($oCallback)) {
|
||||
$sCallback = $oCallback->GetText();
|
||||
} else {
|
||||
$oExecute = $oListener->GetUniqueElement('execute', true);
|
||||
$sExecute = trim($oExecute->GetText());
|
||||
$sCallback = "EventHook_{$sEventName}_$sListenerId";
|
||||
$sCallbackFct = preg_replace('@^function\s*\(@', "public function $sCallback(", $sExecute);
|
||||
if ($sExecute == $sCallbackFct) {
|
||||
throw new DOMFormatException("Malformed tag <execute> in class: $sClass hook: $sEventName listener: $sListenerId");
|
||||
}
|
||||
$sMethods .= "\n $sCallbackFct\n\n";
|
||||
}
|
||||
if (strpos($sCallback, '::') === false) {
|
||||
$sEventListener = '[$this, \''.$sCallback.'\']';
|
||||
} else {
|
||||
$sEventListener = "'$sCallback'";
|
||||
}
|
||||
|
||||
$sListenerPriority = (float)($oListener->GetChildText('priority', '0'));
|
||||
$sEvents .= "\n Combodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", $sEventListener, \$this->m_sObjectUniqId, \"$sListenerId\", null, $sListenerPriority, '$sModuleRelativeDir');";
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($sEvents))
|
||||
{
|
||||
$sMethods .= <<<EOF
|
||||
protected function RegisterEvents()
|
||||
{
|
||||
parent::RegisterEvents();
|
||||
$sEvents
|
||||
}
|
||||
|
||||
EOF;
|
||||
}
|
||||
|
||||
if ($oArchive = $oProperties->GetOptionalElement('archive')) {
|
||||
$bEnabled = $this->GetPropBoolean($oArchive, 'enabled', false);
|
||||
$aClassParams['archive'] = $bEnabled;
|
||||
@@ -2077,7 +2148,6 @@ EOF
|
||||
}
|
||||
|
||||
// Methods
|
||||
$sMethods = "";
|
||||
$oMethods = $oClass->GetUniqueElement('methods');
|
||||
foreach($oMethods->getElementsByTagName('method') as $oMethod)
|
||||
{
|
||||
@@ -2208,6 +2278,7 @@ $sLifecycle
|
||||
$sHighlightScale
|
||||
$sZlists;
|
||||
EOF;
|
||||
|
||||
// some other stuff (magical attributes like friendlyName) are done in MetaModel::InitClasses and though not present in the
|
||||
// generated PHP
|
||||
$sPHP .= $this->GeneratePhpCodeForClass($sClassName, $sParentClass, $sClassParams, $sInitMethodCalls, $bIsAbstractClass, $sMethods, $aRequiredFiles, $sCodeComment);
|
||||
@@ -3538,6 +3609,103 @@ EOF;
|
||||
foreach($this->aSnippets as $sModuleId => $void)
|
||||
{
|
||||
uasort($this->aSnippets[$sModuleId]['before'], array(get_class($this), 'SortOnRank'));
|
||||
uasort($this->aSnippets[$sModuleId]['after'], array(get_class($this), 'SortOnRank'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \DOMFormatException
|
||||
*/
|
||||
protected function LoadGlobalEventListeners()
|
||||
{
|
||||
$sClassName = 'GlobalEventListeners';
|
||||
$sModuleId = '_core_';
|
||||
if (!array_key_exists($sModuleId, $this->aSnippets)) {
|
||||
$this->aSnippets[$sModuleId] = ['before' => [], 'after' => []];
|
||||
}
|
||||
$oEventListeners = $this->oFactory->GetNodes('/itop_design/event_listeners/listener');
|
||||
$aEventListeners = [];
|
||||
foreach ($oEventListeners as $oListener) {
|
||||
/** @var \DOMElement $oListener */
|
||||
$sListenerId = $oListener->getAttribute('id');
|
||||
$sEventName = $oListener->GetChildText('event');
|
||||
|
||||
$oExecute = $oListener->GetUniqueElement('execute');
|
||||
$sExecute = trim($oExecute->GetText());
|
||||
$sCallback = "{$sEventName}_{$sListenerId}";
|
||||
$sCallbackFct = preg_replace('@^function\s*\(@', "public static function $sCallback(", $sExecute);
|
||||
if ($sExecute == $sCallbackFct) {
|
||||
throw new DOMFormatException("Malformed tag <execute> in event: $sEventName listener: $sListenerId");
|
||||
}
|
||||
$fPriority = (float)($oListener->GetChildText('priority', '0'));
|
||||
|
||||
$aFilters = [];
|
||||
$oFilters = $oListener->GetNodes('filters/filter');
|
||||
foreach ($oFilters as $oFilter) {
|
||||
$aFilters[] = $oFilter->GetText();
|
||||
}
|
||||
if (empty($aFilters)) {
|
||||
$sEventSource = 'null';
|
||||
} else {
|
||||
$sEventSource = '["'.implode('", "', $aFilters).'"]';
|
||||
}
|
||||
|
||||
$aContexts = [];
|
||||
$oContexts = $oListener->GetNodes('contexts/context');
|
||||
foreach ($oContexts as $oContext) {
|
||||
$aContexts[] = $oContext->GetText();
|
||||
}
|
||||
if (empty($aContexts)) {
|
||||
$sContext = 'null';
|
||||
} else {
|
||||
$sContext = '["'.implode('", "', $aContexts).'"]';
|
||||
}
|
||||
|
||||
$aEventListeners[] = array(
|
||||
'event_name' => $sEventName,
|
||||
'callback' => $sCallback,
|
||||
'content' => $sCallbackFct,
|
||||
'priority' => $fPriority,
|
||||
'source' => $sEventSource,
|
||||
'context' => $sContext,
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($aEventListeners)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sRegister = '';
|
||||
$sMethods = '';
|
||||
foreach ($aEventListeners as $aListener) {
|
||||
$sCallback = $aListener['callback'];
|
||||
$sEventName = $aListener['event_name'];
|
||||
$sEventSource = $aListener['source'];
|
||||
$sContext = $aListener['context'];
|
||||
$sPriority = $aListener['priority'];
|
||||
$sRegister .= "\nCombodo\iTop\Service\EventService::RegisterListener(\"$sEventName\", '$sClassName::$sCallback', $sEventSource, null, $sContext, $sPriority, '$sModuleId');";
|
||||
$sCallbackFct = $aListener['content'];
|
||||
$sMethods .= "\n $sCallbackFct\n\n";
|
||||
}
|
||||
|
||||
$sContent = <<<PHP
|
||||
class $sClassName
|
||||
{
|
||||
$sMethods
|
||||
}
|
||||
|
||||
$sRegister
|
||||
|
||||
PHP;
|
||||
|
||||
$fOrder = 0;
|
||||
$this->aSnippets[$sModuleId]['after'][] = array(
|
||||
'rank' => $fOrder,
|
||||
'content' => $sContent,
|
||||
'snippet_id' => $sClassName,
|
||||
);
|
||||
foreach ($this->aSnippets as $sModuleId => $void) {
|
||||
uasort($this->aSnippets[$sModuleId]['after'], array(get_class($this), 'SortOnRank'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -580,6 +580,8 @@ class ModelFactory
|
||||
|
||||
$this->oMeta = $this->oDOMDocument->CreateElement('meta');
|
||||
$this->oRoot->AppendChild($this->oMeta);
|
||||
$this->oMeta = $this->oDOMDocument->CreateElement('events');
|
||||
$this->oRoot->AppendChild($this->oMeta);
|
||||
|
||||
foreach ($aRootNodeExtensions as $sElementName)
|
||||
{
|
||||
@@ -860,6 +862,14 @@ class ModelFactory
|
||||
$oNode->SetAttribute('_created_in', $sModuleName);
|
||||
}
|
||||
}
|
||||
$oNodeList = $oXPath->query('/itop_design/events/event');
|
||||
foreach ($oNodeList as $oNode)
|
||||
{
|
||||
if ($oNode->getAttribute('_created_in') == '')
|
||||
{
|
||||
$oNode->SetAttribute('_created_in', $sModuleName);
|
||||
}
|
||||
}
|
||||
$oNodeList = $oXPath->query('/itop_design/menus/menu');
|
||||
foreach ($oNodeList as $oNode)
|
||||
{
|
||||
@@ -1252,6 +1262,19 @@ EOF
|
||||
return $this->GetNodes("/itop_design/constants/constant[@_created_in='$sModuleName']");
|
||||
}
|
||||
|
||||
/**
|
||||
* List all events from the DOM, for a given module
|
||||
*
|
||||
* @param string $sModuleName
|
||||
*
|
||||
* @return \DOMNodeList
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ListEvents($sModuleName)
|
||||
{
|
||||
return $this->GetNodes("/itop_design/events/event[@_created_in='$sModuleName']");
|
||||
}
|
||||
|
||||
/**
|
||||
* List all classes from the DOM, for a given module
|
||||
*
|
||||
|
||||
90
sources/Application/Service/EventData.php
Normal file
90
sources/Application/Service/EventData.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2020 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Service;
|
||||
|
||||
|
||||
/**
|
||||
* Data given to the Event Service callbacks
|
||||
* Class EventServiceData
|
||||
*
|
||||
* @package Combodo\iTop\Service
|
||||
*/
|
||||
class EventData
|
||||
{
|
||||
private $sEvent;
|
||||
private $mEventSource;
|
||||
private $aEventData;
|
||||
private $aCallbackData;
|
||||
|
||||
/**
|
||||
* EventServiceData constructor.
|
||||
*
|
||||
* @param string $sEvent
|
||||
* @param string|array|null $mEventSource
|
||||
* @param array $aEventData
|
||||
*/
|
||||
public function __construct(string $sEvent, $mEventSource = null, array $aEventData = [])
|
||||
{
|
||||
$this->sEvent = $sEvent;
|
||||
$this->aEventData = $aEventData;
|
||||
$this->mEventSource = $mEventSource;
|
||||
$this->aCallbackData = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function GetEvent()
|
||||
{
|
||||
return $this->sEvent;
|
||||
}
|
||||
|
||||
public function Get($sParam)
|
||||
{
|
||||
if (is_array($this->aEventData) && isset($this->aEventData[$sParam])) {
|
||||
return $this->aEventData[$sParam];
|
||||
}
|
||||
|
||||
if (is_array($this->aCallbackData) && isset($this->aCallbackData[$sParam])) {
|
||||
return $this->aCallbackData[$sParam];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetEventSource()
|
||||
{
|
||||
return $this->mEventSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function GetEventData(): array
|
||||
{
|
||||
return $this->aEventData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $aCallbackData
|
||||
*/
|
||||
public function SetCallbackData($aCallbackData)
|
||||
{
|
||||
$this->aCallbackData = $aCallbackData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetCallbackData()
|
||||
{
|
||||
return $this->aCallbackData;
|
||||
}
|
||||
}
|
||||
109
sources/Application/Service/EventHelper.php
Normal file
109
sources/Application/Service/EventHelper.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Service;
|
||||
|
||||
use IssueLog;
|
||||
use LogChannels;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
|
||||
class EventHelper
|
||||
{
|
||||
|
||||
public static function GetCachedClasses($sModuleName, callable $ListBuilder)
|
||||
{
|
||||
$aClasses = [];
|
||||
$sCacheFileName = '';
|
||||
|
||||
if (!utils::IsDevelopmentEnvironment()) {
|
||||
// Try to read from cache
|
||||
$sCacheFileName = utils::GetCachePath()."EventsClassList/$sModuleName.php";
|
||||
if (is_file($sCacheFileName)) {
|
||||
$aClasses = include $sCacheFileName;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($aClasses)) {
|
||||
$aClasses = call_user_func($ListBuilder);
|
||||
|
||||
if (!utils::IsDevelopmentEnvironment() && !empty($aClasses)) {
|
||||
// Save to cache
|
||||
$sCacheContent = "<?php\n\nreturn ".var_export($aClasses, true).";";
|
||||
SetupUtils::builddir(dirname($sCacheFileName));
|
||||
file_put_contents($sCacheFileName, $sCacheContent);
|
||||
}
|
||||
}
|
||||
|
||||
return $aClasses;
|
||||
}
|
||||
|
||||
public static function Trace($sMessage)
|
||||
{
|
||||
IssueLog::Trace($sMessage, LogChannels::EVENT_SERVICE);
|
||||
}
|
||||
|
||||
public static function Debug($sMessage, $sEvent, $sources)
|
||||
{
|
||||
$oConfig = utils::GetConfig();
|
||||
$aLogEvents = $oConfig->Get('event_service.debug.filter_events');
|
||||
$aLogSources = $oConfig->Get('event_service.debug.filter_sources');
|
||||
|
||||
if (is_array($aLogEvents)) {
|
||||
if (!in_array($sEvent, $aLogEvents)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (is_array($aLogSources)) {
|
||||
if (!self::MatchEventSource($aLogSources, $sources)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
IssueLog::Debug($sMessage, LogChannels::EVENT_SERVICE);
|
||||
}
|
||||
|
||||
public static function Error($sMessage)
|
||||
{
|
||||
IssueLog::Error($sMessage, LogChannels::EVENT_SERVICE);
|
||||
}
|
||||
|
||||
public static function MatchEventSource($srcRegistered, $srcEvent): bool
|
||||
{
|
||||
if (empty($srcRegistered)) {
|
||||
// no filtering
|
||||
return true;
|
||||
}
|
||||
if (empty($srcEvent)) {
|
||||
// no match (the registered source is not empty)
|
||||
return false;
|
||||
}
|
||||
if (is_string($srcRegistered)) {
|
||||
$aSrcRegistered = [$srcRegistered];
|
||||
} elseif (is_array($srcRegistered)) {
|
||||
$aSrcRegistered = $srcRegistered;
|
||||
} else {
|
||||
$aSrcRegistered = [];
|
||||
}
|
||||
|
||||
if (is_string($srcEvent)) {
|
||||
$aSrcEvent = [$srcEvent];
|
||||
} elseif (is_array($srcEvent)) {
|
||||
$aSrcEvent = $srcEvent;
|
||||
} else {
|
||||
$aSrcEvent = [];
|
||||
}
|
||||
|
||||
foreach ($aSrcEvent as $sSrcEvent) {
|
||||
if (in_array($sSrcEvent, $aSrcRegistered)) {
|
||||
// sources matches
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// no match
|
||||
return false;
|
||||
}
|
||||
}
|
||||
294
sources/Application/Service/EventService.php
Normal file
294
sources/Application/Service/EventService.php
Normal file
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2020 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Service;
|
||||
|
||||
use Closure;
|
||||
use ContextTag;
|
||||
use CoreException;
|
||||
use Exception;
|
||||
use ExecutionKPI;
|
||||
use ReflectionClass;
|
||||
|
||||
class EventService
|
||||
{
|
||||
public static $aEventListeners = [];
|
||||
private static $iEventIdCounter = 0;
|
||||
private static $aEventDescription = [];
|
||||
|
||||
/**
|
||||
* Register a callback for a specific event
|
||||
*
|
||||
* @param string $sEvent corresponding event
|
||||
* @param callable $callback The callback to call
|
||||
* @param array|string|null $sEventSource event filtering depending on the source of the event
|
||||
* @param mixed $aCallbackData optional data given by the registrar to the callback
|
||||
* @param array|string|null $context context filter
|
||||
* @param float $fPriority optional priority for callback order
|
||||
*
|
||||
* @return string Id of the registration (used for unregistering)
|
||||
*
|
||||
*/
|
||||
public static function RegisterListener(string $sEvent, callable $callback, $sEventSource = null, $aCallbackData = [], $context = null, float $fPriority = 0.0, $sModuleId = ''): string
|
||||
{
|
||||
if (!is_callable($callback, false, $sName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$aEventCallbacks = self::$aEventListeners[$sEvent] ?? [];
|
||||
$sId = 'event_'.self::$iEventIdCounter++;
|
||||
$aEventCallbacks[] = array(
|
||||
'id' => $sId,
|
||||
'event' => $sEvent,
|
||||
'callback' => $callback,
|
||||
'source' => $sEventSource,
|
||||
'name' => $sName,
|
||||
'data' => $aCallbackData,
|
||||
'context' => $context,
|
||||
'priority' => $fPriority,
|
||||
'module' => $sModuleId,
|
||||
);
|
||||
usort($aEventCallbacks, function ($a, $b) {
|
||||
$fPriorityA = $a['priority'];
|
||||
$fPriorityB = $b['priority'];
|
||||
if ($fPriorityA == $fPriorityB) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ($fPriorityA < $fPriorityB) ? -1 : 1;
|
||||
});
|
||||
self::$aEventListeners[$sEvent] = $aEventCallbacks;
|
||||
|
||||
$iTotalRegistrations = 0;
|
||||
foreach (self::$aEventListeners as $aEvent) {
|
||||
$iTotalRegistrations += count($aEvent);
|
||||
}
|
||||
$sLogEventName = "$sEvent:".self::GetSourcesAsString($sEventSource);
|
||||
EventHelper::Trace("Registering event '$sLogEventName' for '$sName' with id '$sId' (total $iTotalRegistrations)");
|
||||
|
||||
return $sId;
|
||||
}
|
||||
|
||||
public static function GetListenersAsJSON()
|
||||
{
|
||||
return json_encode(self::$aEventListeners, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire an event. Call all the callbacks registered for this event.
|
||||
*
|
||||
* @param \Combodo\iTop\Service\EventData $oEventData
|
||||
*
|
||||
* @throws \Exception from the callback
|
||||
*/
|
||||
public static function FireEvent(EventData $oEventData)
|
||||
{
|
||||
$sEvent = $oEventData->GetEvent();
|
||||
if (!array_key_exists($sEvent, self::$aEventDescription)) {
|
||||
$sError = "Fire event error: Event $sEvent is not registered";
|
||||
EventHelper::Error($sError);
|
||||
throw new CoreException($sError);
|
||||
}
|
||||
$eventSource = $oEventData->GetEventSource();
|
||||
$oKPI = new ExecutionKPI();
|
||||
$sLogEventName = "$sEvent - ".self::GetSourcesAsString($eventSource).' '.json_encode($oEventData->GetEventData());
|
||||
EventHelper::Trace("Fire event '$sLogEventName'");
|
||||
if (!isset(self::$aEventListeners[$sEvent])) {
|
||||
EventHelper::Debug("No listener for '$sLogEventName'", $sEvent, $eventSource);
|
||||
$oKPI->ComputeStats('FireEvent', $sEvent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (self::GetListeners($sEvent, $eventSource) as $aEventCallback) {
|
||||
if (!self::MatchContext($aEventCallback['context'])) {
|
||||
continue;
|
||||
}
|
||||
$sName = $aEventCallback['name'];
|
||||
EventHelper::Debug("Fire event '$sLogEventName' calling '$sName'", $sEvent, $eventSource);
|
||||
try {
|
||||
$oEventData->SetCallbackData($aEventCallback['data']);
|
||||
call_user_func($aEventCallback['callback'], $oEventData);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
EventHelper::Error("Event '$sLogEventName' for '$sName' id {$aEventCallback['id']} failed with error: ".$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
EventHelper::Debug("End of event '$sLogEventName'", $sEvent, $eventSource);
|
||||
$oKPI->ComputeStats('FireEvent', $sEvent);
|
||||
}
|
||||
|
||||
public static function GetListeners($sEvent, $eventSource)
|
||||
{
|
||||
$aListeners = [];
|
||||
if (isset(self::$aEventListeners[$sEvent])) {
|
||||
foreach (self::$aEventListeners[$sEvent] as $aEventCallback) {
|
||||
if (EventHelper::MatchEventSource($aEventCallback['source'], $eventSource)) {
|
||||
$aListeners[] = $aEventCallback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aListeners;
|
||||
}
|
||||
|
||||
private static function MatchContext($registeredContext): bool
|
||||
{
|
||||
if (empty($registeredContext)) {
|
||||
return true;
|
||||
}
|
||||
if (is_string($registeredContext)) {
|
||||
$aContexts = array($registeredContext);
|
||||
} elseif (is_array($registeredContext)) {
|
||||
$aContexts = $registeredContext;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
foreach ($aContexts as $sContext) {
|
||||
if (ContextTag::Check($sContext)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function GetSourcesAsString($srcRegistered): string
|
||||
{
|
||||
if (empty($srcRegistered)) {
|
||||
return '';
|
||||
}
|
||||
if (is_string($srcRegistered)) {
|
||||
return substr($srcRegistered, 0, 30);
|
||||
}
|
||||
if (is_array($srcRegistered)) {
|
||||
return substr(implode(',', $srcRegistered), 0, 30);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a previously registered callback
|
||||
*
|
||||
* @param string $sId the callback registration id
|
||||
*/
|
||||
public static function UnRegisterListener(string $sId)
|
||||
{
|
||||
$bRemoved = self::Browse(function ($sEvent, $idx, $aEventCallback) use ($sId) {
|
||||
if ($aEventCallback['id'] == $sId) {
|
||||
$sName = self::$aEventListeners[$sEvent][$idx]['name'];
|
||||
unset (self::$aEventListeners[$sEvent][$idx]);
|
||||
EventHelper::Trace("Unregistered callback '$sName' id $sId' on event '$sEvent'");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!$bRemoved) {
|
||||
EventHelper::Trace("No registration found for callback '$sId'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an event
|
||||
*
|
||||
* @param string $sEvent event to unregister
|
||||
*/
|
||||
public static function UnRegisterEventListeners(string $sEvent)
|
||||
{
|
||||
if (!isset(self::$aEventListeners[$sEvent])) {
|
||||
EventHelper::Trace("No registration found for event '$sEvent'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
unset(self::$aEventListeners[$sEvent]);
|
||||
EventHelper::Trace("Unregistered all the callbacks on event '$sEvent'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all the events
|
||||
*/
|
||||
public static function UnRegisterAll()
|
||||
{
|
||||
self::$aEventListeners = array();
|
||||
EventHelper::Trace("Unregistered all events");
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse all the registrations
|
||||
*
|
||||
* @param \Closure $callback function($sEvent, $idx, $aEventCallback) to call (return false to interrupt the browsing)
|
||||
*
|
||||
* @return bool true if interrupted else false
|
||||
*/
|
||||
private static function Browse(closure $callback): bool
|
||||
{
|
||||
foreach (self::$aEventListeners as $sEvent => $aCallbackList) {
|
||||
foreach ($aCallbackList as $idx => $aEventCallback) {
|
||||
if (call_user_func($callback, $sEvent, $idx, $aEventCallback) === false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// For information only
|
||||
public static function RegisterEvent(string $sEvent, array $aDescription, string $sModule)
|
||||
{
|
||||
if (isset(self::$aEventDescription[$sEvent])) {
|
||||
$sPrevious = self::$aEventDescription[$sEvent]['module'];
|
||||
EventHelper::Error("The Event $sEvent defined by $sModule has already been defined in $sPrevious, check your delta");
|
||||
}
|
||||
|
||||
self::$aEventDescription[$sEvent] = [
|
||||
'name'=> $sEvent,
|
||||
'description' => $aDescription,
|
||||
'module' => $sModule,
|
||||
];
|
||||
}
|
||||
|
||||
public static function GetEventsByClass($sClass)
|
||||
{
|
||||
$aRes = [];
|
||||
$oClass = new ReflectionClass($sClass);
|
||||
foreach (self::$aEventDescription as $sEvent => $aEventInfo) {
|
||||
if (isset($aEventInfo['description']['sources'])) {
|
||||
foreach ($aEventInfo['description']['sources'] as $sSource) {
|
||||
if ($sClass == $sSource || $oClass->isSubclassOf($sSource)) {
|
||||
$aRes[$sEvent] = $aEventInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
// Intentionally duplicated from SetupUtils, not yet loaded when RegisterEvent is called
|
||||
private static function FromCamelCase($sInput) {
|
||||
$sPattern = '!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!';
|
||||
preg_match_all($sPattern, $sInput, $aMatches);
|
||||
$aRet = $aMatches[0];
|
||||
foreach ($aRet as &$sMatch) {
|
||||
$sMatch = $sMatch == strtoupper($sMatch) ?
|
||||
strtolower($sMatch) :
|
||||
lcfirst($sMatch);
|
||||
}
|
||||
return implode('_', $aRet);
|
||||
}
|
||||
|
||||
public static function GetDefinedEventsAsJSON()
|
||||
{
|
||||
return json_encode(self::$aEventDescription, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use DataTableConfig;
|
||||
class DataTable extends UIContentBlock
|
||||
{
|
||||
use tJSRefreshCallback;
|
||||
use tTableRowActions;
|
||||
|
||||
// Overloaded constants
|
||||
public const BLOCK_CODE = 'ibo-datatable';
|
||||
@@ -51,6 +52,7 @@ class DataTable extends UIContentBlock
|
||||
*/
|
||||
protected $aInitDisplayData;
|
||||
|
||||
|
||||
/**
|
||||
* Panel constructor.
|
||||
*
|
||||
@@ -250,4 +252,5 @@ class DataTable extends UIContentBlock
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use appUserPreferences;
|
||||
use AttributeLinkedSet;
|
||||
use cmdbAbstractObject;
|
||||
use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSection;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTable\FormTable;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTableRow\FormTableRow;
|
||||
@@ -19,8 +20,10 @@ use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\StaticTable
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Html\HtmlFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Template\TemplateUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Title\TitleUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Controller\AjaxRenderController;
|
||||
use DBObjectSet;
|
||||
@@ -180,6 +183,46 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
return $oContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a row actions toolbar template.
|
||||
*
|
||||
* @param iUIBlock $oTable datatable object that needs to use tTableRowActions trait
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Component\Template\Template
|
||||
* @throws \Exception
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function MakeActionRowToolbarTemplate(iUIBlock $oTable)
|
||||
{
|
||||
// test trait
|
||||
$sTableClass = get_class($oTable);
|
||||
if (!utils::IsTraitUsedByClass(tTableRowActions::class, $sTableClass)) {
|
||||
throw new \Exception("DataTableUIBlockFactory::MakeActionRowToolbarTemplate: {$sTableClass} iUIBlock needs tTableRowActions trait");
|
||||
}
|
||||
|
||||
// row actions template
|
||||
$oTemplate = TemplateUIBlockFactory::MakeStandard($oTable->GetId().'_actions_buttons_template');
|
||||
|
||||
// row actions toolbar container
|
||||
$oToolbar = ToolbarUIBlockFactory::MakeStandard();
|
||||
$oToolbar->AddCSSClass('ibo-datatable--row-actions-toolbar');
|
||||
|
||||
// for each action...create an icon button
|
||||
foreach ($oTable->GetRowActions() as $iKey => $aAction) {
|
||||
$oButton = ButtonUIBlockFactory::MakeIconAction(
|
||||
array_key_exists('icon_classes', $aAction) ? $aAction['icon_classes'] : 'fas fa-question',
|
||||
array_key_exists('tooltip', $aAction) ? $aAction['tooltip'] : '',
|
||||
array_key_exists('name', $aAction) ? $aAction['name'] : 'undefined'
|
||||
);
|
||||
$oButton->SetDataAttributes(['action-id' => $iKey]);
|
||||
$oToolbar->AddSubBlock($oButton);
|
||||
}
|
||||
|
||||
$oTemplate->AddSubBlock($oToolbar);
|
||||
|
||||
return $oTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a basis Panel component
|
||||
*
|
||||
@@ -457,9 +500,8 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
} else {
|
||||
$aOptions['sSelectedRows'] = '[]';
|
||||
}
|
||||
$aExtraParams['table_id']=$sTableId;
|
||||
$aExtraParams['list_id']=$sListId;
|
||||
|
||||
$aExtraParams['table_id'] = $sTableId;
|
||||
$aExtraParams['list_id'] = $sListId;
|
||||
|
||||
$oDataTable->SetOptions($aOptions);
|
||||
$oDataTable->SetAjaxUrl(utils::GetAbsoluteUrlAppRoot()."pages/ajax.render.php");
|
||||
@@ -475,6 +517,12 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$oDataTable->SetResultColumns($oCustomSettings->aColumns);
|
||||
$oDataTable->SetInitDisplayData(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams));
|
||||
|
||||
// row actions
|
||||
if (isset($aExtraParams['row_actions'])) {
|
||||
$oDataTable->SetRowActions($aExtraParams['row_actions']);
|
||||
}
|
||||
|
||||
|
||||
return $oDataTable;
|
||||
}
|
||||
|
||||
@@ -713,6 +761,11 @@ class DataTableUIBlockFactory extends AbstractUIBlockFactory
|
||||
$oDataTable->SetResultColumns($oCustomSettings->aColumns);
|
||||
$oDataTable->SetInitDisplayData(AjaxRenderController::GetDataForTable($oSet, $aClassAliases, $aColumnsToLoad, $sIdName, $aExtraParams));
|
||||
|
||||
// row actions
|
||||
if (isset($aExtraParams['row_actions'])) {
|
||||
$oDataTable->SetRowActions($aExtraParams['row_actions']);
|
||||
}
|
||||
|
||||
return $oDataTable;
|
||||
}
|
||||
|
||||
@@ -908,6 +961,7 @@ JS;
|
||||
* @param array $aExtraParams
|
||||
* @param string $sFilter
|
||||
* @param array $aOptions
|
||||
* @param array $aRowActions @since 3.1.0
|
||||
* *
|
||||
* $aColumns =[
|
||||
* 'nameField1' => ['label' => labelFIeld1, 'description' => descriptionField1],
|
||||
@@ -917,7 +971,7 @@ JS;
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Layout\UIContentBlock
|
||||
*/
|
||||
public static function MakeForStaticData(string $sTitle, array $aColumns, array $aData, ?string $sId = null, array $aExtraParams = [], string $sFilter = "", array $aOptions = [])
|
||||
public static function MakeForStaticData(string $sTitle, array $aColumns, array $aData, ?string $sId = null, array $aExtraParams = [], string $sFilter = "", array $aOptions = [], array $aRowActions = null)
|
||||
{
|
||||
$oBlock = new UIContentBlock();
|
||||
if ($sTitle != "") {
|
||||
@@ -925,6 +979,13 @@ JS;
|
||||
$oBlock->AddSubBlock($oTitle);
|
||||
}
|
||||
$oTable = new StaticTable($sId, [], $aExtraParams);
|
||||
if ($aRowActions != null) {
|
||||
$oTable->SetRowActions($aRowActions);
|
||||
$aColumns['actions'] = [
|
||||
'label' => Dict::S('UI:Datatables:Column:RowActions:Label'),
|
||||
'description' => Dict::S('UI:Datatables:Column:RowActions:Description'),
|
||||
];
|
||||
}
|
||||
$oTable->SetColumns($aColumns);
|
||||
$oTable->SetData($aData);
|
||||
$oTable->SetFilter($sFilter);
|
||||
@@ -940,6 +1001,7 @@ JS;
|
||||
* @param array $aColumns
|
||||
* @param array $aData
|
||||
* @param string $sFilter
|
||||
* @param array $aRowActions @since 3.1.0
|
||||
*
|
||||
* $aColumns =[
|
||||
* 'nameField1' => ['label' => labelFIeld1, 'description' => descriptionField1],
|
||||
@@ -949,10 +1011,17 @@ JS;
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTable\FormTable
|
||||
*/
|
||||
public static function MakeForForm(string $sRef, array $aColumns, array $aData = [], string $sFilter = '')
|
||||
public static function MakeForForm(string $sRef, array $aColumns, array $aData = [], string $sFilter = '', array $aRowActions = null)
|
||||
{
|
||||
$oTable = new FormTable("datatable_".$sRef);
|
||||
$oTable->SetRef($sRef);
|
||||
if ($aRowActions != null) {
|
||||
$oTable->SetRowActions($aRowActions);
|
||||
$aColumns['actions'] = [
|
||||
'label' => Dict::S('UI:Datatables:Column:RowActions:Label'),
|
||||
'description' => Dict::S('UI:Datatables:Column:RowActions:Description'),
|
||||
];
|
||||
}
|
||||
$oTable->SetColumns($aColumns);
|
||||
$oTable->SetFilter($sFilter);
|
||||
|
||||
@@ -970,24 +1039,44 @@ JS;
|
||||
public static function GetAllowedParams(): array
|
||||
{
|
||||
return [
|
||||
'surround_with_panel', /** bool embed table into a Panel */
|
||||
'menu', /** bool display table menu */
|
||||
'view_link', /** bool display the friendlyname column with links to the objects details */
|
||||
'link_attr', /** string link att code */
|
||||
'object_id', /** int Id of the object linked */
|
||||
'target_attr', /** string target att code of the link */
|
||||
'selection_mode', /** bool activate selection */
|
||||
'selection_type', /** string 'multiple' or 'single' */
|
||||
'extra_fields', /** string comma separated list of link att code to display ('alias.attcode')*/
|
||||
'zlist', /** string name of the zlist to display when 'extra_fields' is not set */
|
||||
'display_limit', /** bool if true pagination is used (default = true) */
|
||||
'table_id', /** string datatable id */
|
||||
'cssCount', /** string external counter (input hidden) js selector */
|
||||
'selected_rows', /** array list of Ids already selected when displaying the datatable */
|
||||
'display_aliases', /** string comma separated list of class aliases to display */
|
||||
'list_id', /** string list outer id */
|
||||
'selection_enabled', /** list of id in witch select is allowed, if not exists all lines are selectable */
|
||||
'id_for_select', /**give definition of id for select checkbox*/
|
||||
'surround_with_panel',
|
||||
/** bool embed table into a Panel */
|
||||
'menu',
|
||||
/** bool display table menu */
|
||||
'view_link',
|
||||
/** bool display the friendlyname column with links to the objects details */
|
||||
'link_attr',
|
||||
/** string link att code */
|
||||
'object_id',
|
||||
/** int Id of the object linked */
|
||||
'target_attr',
|
||||
/** string target att code of the link */
|
||||
'selection_mode',
|
||||
/** bool activate selection */
|
||||
'selection_type',
|
||||
/** string 'multiple' or 'single' */
|
||||
'extra_fields',
|
||||
/** string comma separated list of link att code to display ('alias.attcode')*/
|
||||
'zlist',
|
||||
/** string name of the zlist to display when 'extra_fields' is not set */
|
||||
'display_limit',
|
||||
/** bool if true pagination is used (default = true) */
|
||||
'table_id',
|
||||
/** string datatable id */
|
||||
'cssCount',
|
||||
/** string external counter (input hidden) js selector */
|
||||
'selected_rows',
|
||||
/** array list of Ids already selected when displaying the datatable */
|
||||
'display_aliases',
|
||||
/** string comma separated list of class aliases to display */
|
||||
'list_id',
|
||||
/** string list outer id */
|
||||
'selection_enabled',
|
||||
/** list of id in witch select is allowed, if not exists all lines are selectable */
|
||||
'id_for_select',
|
||||
/**give definition of id for select checkbox*/
|
||||
'row_actions',
|
||||
/** array of blocks displayed on every row */
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormT
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\FormTableRow\FormTableRow;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable\StaticTable;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\tTableRowActions;
|
||||
use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
|
||||
/**
|
||||
@@ -19,10 +20,10 @@ use Combodo\iTop\Application\UI\Base\iUIBlock;
|
||||
class FormTable extends StaticTable
|
||||
{
|
||||
// Overloaded constants
|
||||
public const BLOCK_CODE = 'ibo-formtable';
|
||||
public const REQUIRES_ANCESTORS_DEFAULT_JS_FILES = true;
|
||||
public const REQUIRES_ANCESTORS_DEFAULT_CSS_FILES = true;
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/datatable/static/formtable/layout';
|
||||
public const BLOCK_CODE = 'ibo-formtable';
|
||||
public const REQUIRES_ANCESTORS_DEFAULT_JS_FILES = true;
|
||||
public const REQUIRES_ANCESTORS_DEFAULT_CSS_FILES = true;
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/datatable/static/formtable/layout';
|
||||
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/datatable/static/formtable/layout';
|
||||
|
||||
/** @var string */
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Combodo\iTop\Application\UI\Base\Component\DataTable\StaticTable;
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\tTableRowActions;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Application\UI\Base\tJSRefreshCallback;
|
||||
use utils;
|
||||
@@ -18,12 +19,13 @@ use utils;
|
||||
class StaticTable extends UIContentBlock
|
||||
{
|
||||
use tJSRefreshCallback;
|
||||
use tTableRowActions;
|
||||
|
||||
// Overloaded constants
|
||||
public const BLOCK_CODE = 'ibo-datatable';
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/datatable/static/layout';
|
||||
public const BLOCK_CODE = 'ibo-datatable';
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/datatable/static/layout';
|
||||
public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/datatable/static/layout';
|
||||
public const DEFAULT_JS_FILES_REL_PATH = [
|
||||
public const DEFAULT_JS_FILES_REL_PATH = [
|
||||
'node_modules/datatables.net/js/jquery.dataTables.min.js',
|
||||
'node_modules/datatables.net-fixedheader/js/dataTables.fixedHeader.min.js',
|
||||
'node_modules/datatables.net-responsive/js/dataTables.responsive.min.js',
|
||||
@@ -59,7 +61,7 @@ class StaticTable extends UIContentBlock
|
||||
private $aExtraParams;
|
||||
/*@var string $sUrlForRefresh*/
|
||||
private $sFilter;
|
||||
/** @var array $aOptions
|
||||
/** @var array $aOptions
|
||||
* List of specific options for display datatable
|
||||
*/
|
||||
private $aOptions;
|
||||
@@ -81,6 +83,17 @@ class StaticTable extends UIContentBlock
|
||||
return $this->aColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return columns count.
|
||||
*
|
||||
* @return int
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function GetColumnsCount(): int
|
||||
{
|
||||
return count($this->aColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aColumns
|
||||
*
|
||||
@@ -129,8 +142,8 @@ class StaticTable extends UIContentBlock
|
||||
{
|
||||
//$('#".$this->sId."').DataTable().clear().rows.add(data).draw()
|
||||
$aParams = [
|
||||
'style' => 'list',
|
||||
'filter' => $this->sFilter,
|
||||
'style' => 'list',
|
||||
'filter' => $this->sFilter,
|
||||
'extra_params' => $this->aExtraParams,
|
||||
];
|
||||
|
||||
@@ -140,7 +153,7 @@ class StaticTable extends UIContentBlock
|
||||
$('#".$this->sId."').dataTable().fnAddData(data);
|
||||
});";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
@@ -149,6 +162,7 @@ class StaticTable extends UIContentBlock
|
||||
if (isset($this->aOptions[$sOption])) {
|
||||
return $this->aOptions[$sOption];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\UI\Base\Component\DataTable;
|
||||
|
||||
/**
|
||||
* Trait tTableRowActions
|
||||
*
|
||||
* This brings the ability to add action rows to tables.
|
||||
*
|
||||
* @internal
|
||||
* @package Combodo\iTop\Application\UI\Base\Component\DataTable
|
||||
* @since 3.1.0
|
||||
*/
|
||||
trait tTableRowActions
|
||||
{
|
||||
/**
|
||||
* @var $aRowActions array array of row actions
|
||||
* action => {
|
||||
* tooltip: string,
|
||||
* icon_classes: string,
|
||||
* js_row_action: string
|
||||
* }
|
||||
*/
|
||||
protected $aRowActions;
|
||||
|
||||
/**
|
||||
* Set row actions.
|
||||
*
|
||||
* @param array $aRowActions
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function SetRowActions(array $aRowActions)
|
||||
{
|
||||
$this->aRowActions = $aRowActions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get row actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function GetRowActions(): array
|
||||
{
|
||||
return $this->aRowActions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if row actions is set and not empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function HasRowActions(): bool
|
||||
{
|
||||
return isset($this->aRowActions) && count($this->aRowActions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return row actions template.
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Component\Template\Template
|
||||
*/
|
||||
public function GetRowActionsTemplate()
|
||||
{
|
||||
return DataTableUIBlockFactory::MakeActionRowToolbarTemplate($this);
|
||||
}
|
||||
}
|
||||
37
sources/Application/UI/Base/Component/Template/Template.php
Normal file
37
sources/Application/UI/Base/Component/Template/Template.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2022 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\UI\Base\Component\Template;
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
|
||||
/**
|
||||
* Class Template
|
||||
*
|
||||
* @author Benjamin Dalsass<benjamin.dalsass@combodo.com>
|
||||
* @package Combodo\iTop\Application\UI\Base\Component\Template
|
||||
* @since 3.1.0
|
||||
*/
|
||||
class Template extends UIContentBlock
|
||||
{
|
||||
// Overloaded constants
|
||||
public const BLOCK_CODE = 'ibo-template';
|
||||
public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/components/template/layout';
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2013-2022 Combodo SARL
|
||||
*
|
||||
* This file is part of iTop.
|
||||
*
|
||||
* iTop is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* iTop is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Application\UI\Base\Component\Template;
|
||||
|
||||
use Combodo\iTop\Application\UI\Base\AbstractUIBlockFactory;
|
||||
|
||||
/**
|
||||
* Class TemplateUIBlockFactory
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @author Benjamin Dalsass <benjamin.dalsass@combodo.com>
|
||||
* @package Combodo\iTop\Application\UI\Base\Component\Template
|
||||
* @since 3.1.0
|
||||
* @link
|
||||
*/
|
||||
class TemplateUIBlockFactory extends AbstractUIBlockFactory
|
||||
{
|
||||
/** @inheritDoc */
|
||||
public const TWIG_TAG_NAME = 'UITemplate';
|
||||
/** @inheritDoc */
|
||||
public const UI_BLOCK_CLASS_NAME = Template::class;
|
||||
|
||||
/**
|
||||
* Make a Template component
|
||||
*
|
||||
* @return \Combodo\iTop\Application\UI\Base\Component\Template\Template
|
||||
*/
|
||||
public static function MakeStandard(string $sId)
|
||||
{
|
||||
return new Template($sId);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
|
||||
/**
|
||||
* Simple web page with no includes or fancy formatting, useful to generateXML documents
|
||||
* The page adds the content-type text/XML and the encoding into the headers
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Class DownloadPage
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Class JsonPage
|
||||
*
|
||||
|
||||
@@ -142,7 +142,6 @@ EOF
|
||||
/**
|
||||
* Generates the PDF document and returns the PDF content as a string
|
||||
*
|
||||
* @return string
|
||||
* @see WebPage::output()
|
||||
*/
|
||||
public function output()
|
||||
|
||||
@@ -174,6 +174,7 @@ class UnauthenticatedWebPage extends NiceWebPage
|
||||
$oKpi->ComputeAndReport(get_class($this).' output');
|
||||
echo $sHtml;
|
||||
$oKpi->ComputeAndReport('Echoing ('.round(strlen($sHtml) / 1024).' Kb)');
|
||||
ExecutionKPI::ReportStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Simple web page with no includes or fancy formatting, useful to generateXML documents
|
||||
* The page adds the content-type text/XML and the encoding into the headers
|
||||
|
||||
@@ -20,5 +20,12 @@
|
||||
{% for aColumn in oUIBlock.GetDisplayColumns() %}
|
||||
<th class="ibo-datatable-header" {% if aColumn["description"] is not empty %}title="{{ aColumn["description"] }}"{% endif %}>{{ aColumn["attribute_label"] }} </th>
|
||||
{% endfor %}
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
<th class="ibo-datatable-header">{{ 'UI:Datatables:Column:RowActions:Label'|dict_s }} </th>
|
||||
{% endif %}
|
||||
</thead>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
{{ render_block(oUIBlock.GetRowActionsTemplate()) }}
|
||||
{% endif %}
|
||||
@@ -208,6 +208,9 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({
|
||||
}
|
||||
},
|
||||
{% endfor %}
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
getRowActionsColumnDefinition('{{ oUIBlock.GetId() }}'),
|
||||
{% endif %}
|
||||
],
|
||||
ajax: $.fn.dataTable.pipeline({
|
||||
url: "{{ oUIBlock.GetAjaxUrl() }}",
|
||||
@@ -415,4 +418,6 @@ if(window.ResizeObserver){
|
||||
}, 120);
|
||||
});
|
||||
oTable{{ sListIDForVarSuffix }}Resize.observe($('#{{ oUIBlock.GetId() }}')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
{% include 'base/components/datatable/row-actions/handler.js.twig' %}
|
||||
@@ -0,0 +1,15 @@
|
||||
{# @copyright Copyright (C) 2010-2022 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
|
||||
// for each row action
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
{% for aAction in oUIBlock.GetRowActions() %}
|
||||
$('#{{ oUIBlock.GetId() }} tbody').on('click', 'button[data-action-id="{{ loop.index0 }}"]', function() {
|
||||
let iActionId = $(this).data('action-id');
|
||||
let oDatatable = $('#{{ oUIBlock.GetId() }}').DataTable();
|
||||
let oTrElement = $(this).closest('tr');
|
||||
let aData = oDatatable.row(oTrElement).data();
|
||||
{{ aAction.js_row_action|raw }};
|
||||
});
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@@ -13,8 +13,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for oSubBlock in oUIBlock.GetRows() %}
|
||||
{{ render_block(oSubBlock, {aPage: aPage}) }}
|
||||
{% for oRowBlock in oUIBlock.GetRows() %}
|
||||
{{ render_block(oRowBlock, {aPage: aPage}) }}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
{{ render_block(oUIBlock.GetRowActionsTemplate()) }}
|
||||
{% endif %}
|
||||
@@ -20,7 +20,10 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({
|
||||
},
|
||||
{% endif %}
|
||||
columnDefs: [
|
||||
{orderable: false, targets: 0}
|
||||
{orderable: false, targets: 0},
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
getRowActionsColumnDefinition('{{ oUIBlock.GetId() }}', {{ oUIBlock.GetColumnsCount() - 1 }}),
|
||||
{% endif %}
|
||||
],
|
||||
{% endif %}
|
||||
drawCallback: function (settings) {
|
||||
@@ -123,4 +126,6 @@ if (window.ResizeObserver)
|
||||
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
{% include 'base/components/datatable/row-actions/handler.js.twig' %}
|
||||
@@ -44,4 +44,8 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</table>
|
||||
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
{{ render_block(oUIBlock.GetRowActionsTemplate()) }}
|
||||
{% endif %}
|
||||
@@ -54,6 +54,9 @@ var oTable{{ sListIDForVarSuffix }} = $('#{{ oUIBlock.GetId() }}').DataTable({
|
||||
sortable: true
|
||||
},
|
||||
{% endfor %}
|
||||
{% if oUIBlock.HasRowActions() %}
|
||||
getRowActionsColumnDefinition('{{ oUIBlock.GetId() }}'),
|
||||
{% endif %}
|
||||
],
|
||||
drawCallback: function (settings) {
|
||||
if(settings.json)
|
||||
@@ -103,4 +106,6 @@ if (window.ResizeObserver)
|
||||
}, 120);
|
||||
});
|
||||
oStaticTable{{ sListIDForVarSuffix }}Resize.observe($('#{{ oUIBlock.GetId() }}')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
{% include 'base/components/datatable/row-actions/handler.js.twig' %}
|
||||
9
templates/base/components/template/layout.html.twig
Normal file
9
templates/base/components/template/layout.html.twig
Normal file
@@ -0,0 +1,9 @@
|
||||
{# @copyright Copyright (C) 2010-2022 Combodo SARL #}
|
||||
{# @license http://opensource.org/licenses/AGPL-3.0 #}
|
||||
{% apply spaceless %}
|
||||
<template id="{{ oUIBlock.GetId() }}" data-role="ibo-template">
|
||||
{% for oSubBlock in oUIBlock.GetSubBlocks() %}
|
||||
{{ render_block(oSubBlock, {aPage: aPage}) }}
|
||||
{% endfor %}
|
||||
</template>
|
||||
{% endapply %}
|
||||
@@ -27,8 +27,10 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
*/
|
||||
|
||||
use ArchivedObjectException;
|
||||
use CMDBSource;
|
||||
use CMDBObject;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Service\EventData;
|
||||
use Combodo\iTop\Service\EventService;
|
||||
use Contact;
|
||||
use DBObject;
|
||||
use DBObjectSet;
|
||||
@@ -70,9 +72,13 @@ define('TAG_ATTCODE', 'domains');
|
||||
class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
private $iTestOrgId;
|
||||
|
||||
// For cleanup
|
||||
private $aCreatedObjects = array();
|
||||
|
||||
// Counts
|
||||
public $aReloadCount = [];
|
||||
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = false;
|
||||
|
||||
@@ -96,6 +102,8 @@ class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
$this->CreateTestOrganization();
|
||||
}
|
||||
|
||||
EventService::RegisterListener(EVENT_SERVICE_DB_OBJECT_RELOAD, [$this, 'CountObjectReload']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,12 +127,13 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$this->debug("Removing $sClass::$iKey");
|
||||
$oObject->DBDelete();
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$this->debug($e->getMessage());
|
||||
catch (Exception $e) {
|
||||
$this->debug("Error when removing created objects: $sClass::$iKey. Exception message: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,9 +455,9 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oUserProfile = new URP_UserProfile();
|
||||
$oUserProfile->Set('profileid', $iProfileId);
|
||||
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||
/** @var DBObjectSet $oSet */
|
||||
/** @var \ormLinkSet $oSet */
|
||||
$oSet = $oUser->Get('profile_list');
|
||||
$oSet->AddObject($oUserProfile);
|
||||
$oSet->AddItem($oUserProfile);
|
||||
$oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array(
|
||||
'profile_list' => $oSet,
|
||||
));
|
||||
@@ -788,6 +797,49 @@ class ItopDataTestCase extends ItopTestCase
|
||||
return $oOrg;
|
||||
}
|
||||
|
||||
public function ResetReloadCount()
|
||||
{
|
||||
$this->aReloadCount = [];
|
||||
}
|
||||
|
||||
public function DebugReloadCount($sMsg, $bResetCount = true)
|
||||
{
|
||||
$iTotalCount = 0;
|
||||
$aTotalPerClass = [];
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$iClassCount = 0;
|
||||
foreach ($aCountByKeys as $iCount) {
|
||||
$iClassCount += $iCount;
|
||||
}
|
||||
$iTotalCount += $iClassCount;
|
||||
$aTotalPerClass[$sClass] = $iClassCount;
|
||||
}
|
||||
$this->debug("$sMsg - $iTotalCount reload(s)");
|
||||
foreach ($this->aReloadCount as $sClass => $aCountByKeys) {
|
||||
$this->debug(" $sClass => $aTotalPerClass[$sClass] reload(s)");
|
||||
foreach ($aCountByKeys as $sKey => $iCount) {
|
||||
$this->debug(" $sClass::$sKey => $iCount");
|
||||
}
|
||||
}
|
||||
if ($bResetCount) {
|
||||
$this->ResetReloadCount();
|
||||
}
|
||||
}
|
||||
|
||||
public function CountObjectReload(EventData $oData)
|
||||
{
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$sKey = $oObject->GetKey();
|
||||
$iCount = $this->GetObjectReloadCount($sClass, $sKey);
|
||||
$this->aReloadCount[$sClass][$sKey] = 1 + $iCount;
|
||||
}
|
||||
|
||||
public function GetObjectReloadCount($sClass, $sKey)
|
||||
{
|
||||
return $this->aReloadCount[$sClass][$sKey] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a series of operations will trigger a given number of MySL queries
|
||||
*
|
||||
@@ -797,7 +849,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLQueryHasNoResultException
|
||||
*/
|
||||
protected static function assertDBQueryCount($iExpectedCount, callable $oFunction)
|
||||
protected function assertDBQueryCount($iExpectedCount, callable $oFunction)
|
||||
{
|
||||
$iInitialCount = (int) CMDBSource::QueryToScalar("SHOW SESSION STATUS LIKE 'Queries'", 1);
|
||||
$oFunction();
|
||||
@@ -805,12 +857,12 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$iCount = $iFinalCount - 1 - $iInitialCount;
|
||||
if ($iCount != $iExpectedCount)
|
||||
{
|
||||
static::fail("Expected $iExpectedCount queries. $iCount have been executed.");
|
||||
$this->fail("Expected $iExpectedCount queries. $iCount have been executed.");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise PHP Unit will consider that no assertion has been made
|
||||
static::assertTrue(true);
|
||||
// Otherwise, PHP Unit will consider that no assertion has been made
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -835,7 +887,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a consistent set of iTop objects from the specified XML text string
|
||||
* Import a consistent set of iTop objects from the specified XML text string
|
||||
* @param string $sXmlDataset
|
||||
* @param boolean $bSearch If true, a search will be performed on each object (based on its reconciliation keys)
|
||||
* before trying to import it (existing objects will be updated)
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Combodo\iTop\Test\UnitTest;
|
||||
* Time: 11:21
|
||||
*/
|
||||
|
||||
use CMDBSource;
|
||||
use MySQLTransactionNotClosedException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SetupUtils;
|
||||
|
||||
@@ -48,15 +50,26 @@ class ItopTestCase extends TestCase
|
||||
@include_once getcwd().'/approot.inc.php'; // this is when launching phpunit from within the IDE
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \MySQLTransactionNotClosedException see N°5538
|
||||
* @since 2.7.8 3.0.3 3.1.0 N°5538
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
|
||||
if (CMDBSource::IsInsideTransaction()) {
|
||||
// Nested transactions were opened but not finished !
|
||||
throw new MySQLTransactionNotClosedException('Some DB transactions were opened but not closed ! Fix the code by adding ROLLBACK or COMMIT statements !', []);
|
||||
}
|
||||
}
|
||||
|
||||
protected function debug($sMsg)
|
||||
{
|
||||
if (DEBUG_UNIT_TEST)
|
||||
{
|
||||
if (is_string($sMsg))
|
||||
{
|
||||
echo "$sMsg\n";
|
||||
}
|
||||
else {
|
||||
{
|
||||
if (DEBUG_UNIT_TEST) {
|
||||
if (is_string($sMsg)) {
|
||||
echo "$sMsg\n";
|
||||
} else {
|
||||
/** @noinspection ForgottenDebugOutputInspection */
|
||||
print_r($sMsg);
|
||||
}
|
||||
|
||||
344
test/core/BulkChangeExtKeyTest.inc.php
Normal file
344
test/core/BulkChangeExtKeyTest.inc.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use MetaModel;
|
||||
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*
|
||||
* created a dedicated test for external keys imports.
|
||||
*
|
||||
* Class BulkChangeExtKeyTest
|
||||
*
|
||||
* @package Combodo\iTop\Test\UnitTest\Core
|
||||
*/
|
||||
class BulkChangeExtKeyTest extends ItopDataTestCase {
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
/**
|
||||
* this test may delete Person objects to cover all usecases
|
||||
* DO NOT CHANGE USE_TRANSACTION value to avoid any DB loss!
|
||||
*/
|
||||
const USE_TRANSACTION = true;
|
||||
|
||||
private $sUid;
|
||||
|
||||
protected function setUp() : void {
|
||||
parent::setUp();
|
||||
require_once(APPROOT.'core/bulkchange.class.inc.php');
|
||||
}
|
||||
|
||||
private function deleteAllRacks(){
|
||||
$oSearch = \DBSearch::FromOQL("SELECT Rack");
|
||||
$oSet = new \DBObjectSet($oSearch);
|
||||
$iCount = $oSet->Count();
|
||||
if ($iCount != 0){
|
||||
while ($oRack = $oSet->Fetch()){
|
||||
$oRack->DBDelete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_NoObjectAtAll($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
|
||||
$this->performBulkChangeTest(
|
||||
'There are no \'Rack\' objects',
|
||||
"",
|
||||
null,
|
||||
$bIsRackReconKey
|
||||
);
|
||||
}
|
||||
|
||||
public function createRackObjects($aRackDict) {
|
||||
foreach ($aRackDict as $iOrgId => $aRackNames) {
|
||||
foreach ($aRackNames as $sRackName) {
|
||||
$this->createObject('Rack', ['name' => $sRackName, 'description' => "${sRackName}Desc", 'org_id' => $iOrgId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function createAnotherUserInAnotherOrg() {
|
||||
$oOrg2 = $this->CreateOrganization('UnitTestOrganization2');
|
||||
$oProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Configuration Manager'), true);
|
||||
|
||||
$sUid = $this->GetUid();
|
||||
|
||||
$oUserProfile = new \URP_UserProfile();
|
||||
$oUserProfile->Set('profileid', $oProfile->GetKey());
|
||||
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||
$oSet = \DBObjectSet::FromObject($oUserProfile);
|
||||
|
||||
$oPerson = $this->CreatePerson('666', $oOrg2->GetKey());
|
||||
$oUser = $this->createObject('UserLocal', array(
|
||||
'contactid' => $oPerson->GetKey(),
|
||||
'login' => $sUid,
|
||||
'password' => "ABCdef$sUid@12345",
|
||||
'language' => 'EN US',
|
||||
'profile_list' => $oSet,
|
||||
));
|
||||
|
||||
$oAllowedOrgList = $oUser->Get('allowed_org_list');
|
||||
/** @var \URP_UserOrg $oUserOrg */
|
||||
$oUserOrg = \MetaModel::NewObject('URP_UserOrg', ['allowed_org_id' => $oOrg2->GetKey(),]);
|
||||
$oAllowedOrgList->AddItem($oUserOrg);
|
||||
$oUser->Set('allowed_org_list', $oAllowedOrgList);
|
||||
$oUser->DBWrite();
|
||||
return [$oOrg2, $oUser];
|
||||
}
|
||||
|
||||
public function ReconciliationKeyProvider(){
|
||||
return [
|
||||
'rack_id NOT a reconcilication key' => [ false ],
|
||||
'rack_id reconcilication key' => [ true ],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_NoObjectVisibleByCurrentUser($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
$this->createRackObjects(
|
||||
[
|
||||
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||
]
|
||||
);
|
||||
|
||||
list($oOrg2, $oUser) = $this->createAnotherUserInAnotherOrg();
|
||||
\UserRights::Login($oUser->Get('login'));
|
||||
|
||||
$this->performBulkChangeTest(
|
||||
"There are no 'Rack' objects found with your current profile",
|
||||
"",
|
||||
$oOrg2,
|
||||
$bIsRackReconKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_SomeObjectVisibleByCurrentUser($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
list($oOrg2, $oUser) = $this->createAnotherUserInAnotherOrg();
|
||||
$this->createRackObjects(
|
||||
[
|
||||
$this->getTestOrgId() => ['RackTest1', 'RackTest2'],
|
||||
$oOrg2->GetKey() => ['RackTest3', 'RackTest4'],
|
||||
]
|
||||
);
|
||||
|
||||
\UserRights::Login($oUser->Get('login'));
|
||||
|
||||
$this->performBulkChangeTest(
|
||||
"There are some 'Rack' objects not visible with your current profile",
|
||||
"Some possible 'Rack' value(s): RackTest3, RackTest4",
|
||||
$oOrg2,
|
||||
$bIsRackReconKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
$this->createRackObjects(
|
||||
[
|
||||
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||
]
|
||||
);
|
||||
|
||||
$this->performBulkChangeTest(
|
||||
"No match for value 'UnexistingRack'",
|
||||
"Some possible 'Rack' value(s): RackTest1, RackTest2, RackTest3...",
|
||||
null,
|
||||
$bIsRackReconKey
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser_AmbigousMatch($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
$this->createRackObjects(
|
||||
[
|
||||
$this->getTestOrgId() => ['UnexistingRack', 'UnexistingRack']
|
||||
]
|
||||
);
|
||||
|
||||
$this->performBulkChangeTest(
|
||||
"invalid value for attribute",
|
||||
"Ambiguous: found 2 objects",
|
||||
null,
|
||||
$bIsRackReconKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'Found 2 matches'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dataProvider ReconciliationKeyProvider
|
||||
*/
|
||||
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser_FurtherExtKeyForRack($bIsRackReconKey){
|
||||
$this->deleteAllRacks();
|
||||
$this->createRackObjects(
|
||||
[
|
||||
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||
]
|
||||
);
|
||||
|
||||
$aCsvData = [["UnexistingRackDescription"]];
|
||||
$aExtKeys = ["org_id" => ["name" => 0], "rack_id" => ["name" => 1, "description" => 3]];
|
||||
|
||||
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%28%60Rack%60.%60name%60+%3D+%3Aname%29+AND+%28%60Rack%60.%60description%60+%3D+%3Adescription%29%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%2C%22description%22%3A%22UnexistingRackDescription%22%7D%2C%5B%5D%5D'
|
||||
;
|
||||
$this->performBulkChangeTest(
|
||||
"No match for value 'UnexistingRack UnexistingRackDescription'",
|
||||
"Some possible 'Rack' value(s): RackTest1 RackTest1Desc, RackTest2 RackTest2Desc, RackTest3 RackTest3Desc...",
|
||||
null,
|
||||
$bIsRackReconKey,
|
||||
$aCsvData,
|
||||
$aExtKeys,
|
||||
$sSearchLinkUrl
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private function GetUid(){
|
||||
if (is_null($this->sUid)){
|
||||
$this->sUid = date('dmYHis');
|
||||
}
|
||||
|
||||
return $this->sUid;
|
||||
}
|
||||
|
||||
/** *
|
||||
* @param $aInitData
|
||||
* @param $aCsvData
|
||||
* @param $aAttributes
|
||||
* @param $aExtKeys
|
||||
* @param $aReconcilKeys
|
||||
*/
|
||||
public function performBulkChangeTest($sExpectedDisplayableValue, $sExpectedDescription, $oOrg, $bIsRackReconKey,
|
||||
$aAdditionalCsvData=null, $aExtKeys=null, $sSearchLinkUrl=null, $sError="Object not found") {
|
||||
if ($sSearchLinkUrl===null){
|
||||
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%60Rack%60.%60name%60+%3D+%3Aname%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%7D%2C%5B%5D%5D';
|
||||
}
|
||||
if (is_null($oOrg)){
|
||||
$iOrgId = $this->getTestOrgId();
|
||||
$sOrgName = "UnitTestOrganization";
|
||||
}else{
|
||||
$iOrgId = $oOrg->GetKey();
|
||||
$sOrgName = $oOrg->Get('name');
|
||||
}
|
||||
|
||||
$sUid = $this->GetUid();
|
||||
|
||||
$aCsvData = [[$sOrgName, "UnexistingRack", "$sUid"]];
|
||||
if ($aAdditionalCsvData !== null){
|
||||
foreach ($aAdditionalCsvData as $i => $aData){
|
||||
foreach ($aData as $sData){
|
||||
$aCsvData[$i][] = $sData;
|
||||
}
|
||||
}
|
||||
}
|
||||
$aAttributes = ["name" => 2];
|
||||
if ($aExtKeys == null){
|
||||
$aExtKeys = ["org_id" => ["name" => 0], "rack_id" => ["name" => 1]];
|
||||
}
|
||||
$aReconcilKeys = [ "name" ];
|
||||
|
||||
$aResult = [
|
||||
0 => $sOrgName,
|
||||
"org_id" => $iOrgId,
|
||||
1 => "UnexistingRack",
|
||||
2 => "\"$sUid\"",
|
||||
"rack_id" => [
|
||||
$sExpectedDisplayableValue,
|
||||
$sExpectedDescription
|
||||
],
|
||||
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => $sError,
|
||||
];
|
||||
|
||||
if ($bIsRackReconKey){
|
||||
$aReconcilKeys[] = "rack_id";
|
||||
$aResult[2] = $sUid;
|
||||
$aResult["__STATUS__"] = "Issue: failed to reconcile";
|
||||
}
|
||||
|
||||
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
|
||||
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||
$this->debug("aReconcilKeys:". var_export($aReconcilKeys));
|
||||
$oBulk = new \BulkChange(
|
||||
"Server",
|
||||
$aCsvData,
|
||||
$aAttributes,
|
||||
$aExtKeys,
|
||||
$aReconcilKeys,
|
||||
null,
|
||||
null,
|
||||
"Y-m-d H:i:s", // date format
|
||||
true // localize
|
||||
);
|
||||
$this->debug("BulkChange:");
|
||||
$oChange = \CMDBObject::GetCurrentChange();
|
||||
$this->debug("GetCurrentChange:");
|
||||
$aRes = $oBulk->Process($oChange);
|
||||
$this->debug("Process:");
|
||||
static::assertNotNull($aRes);
|
||||
$this->debug("assertNotNull:");
|
||||
var_dump($aRes);
|
||||
foreach ($aRes as $aRow) {
|
||||
if (array_key_exists('__STATUS__', $aRow)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
if (array_key_exists($i,$aResult)) {
|
||||
$this->debug("aResult:".var_export($aResult[$i]));
|
||||
if ($oCell instanceof \CellStatus_SearchIssue ||
|
||||
$oCell instanceof \CellStatus_Ambiguous) {
|
||||
$this->assertEquals($aResult[$i][0], $oCell->GetDisplayableValue(),
|
||||
"failure on ".get_class($oCell).' cell type');
|
||||
$this->assertEquals($sSearchLinkUrl, $oCell->GetSearchLinkUrl(),
|
||||
"failure on ".get_class($oCell).' cell type');
|
||||
$this->assertEquals($aResult[$i][1], $oCell->GetDescription(),
|
||||
"failure on ".get_class($oCell).' cell type');
|
||||
}
|
||||
}
|
||||
} else if ($i === "__ERRORS__") {
|
||||
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||
}
|
||||
}
|
||||
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
||||
}
|
||||
}
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',$db_core_transactions_enabled);
|
||||
}
|
||||
}
|
||||
@@ -104,13 +104,13 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
if (array_key_exists('__STATUS__', $aRow)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
//$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$this->debug("aResult:".$aResult[$i]);
|
||||
$this->assertEquals($oCell->GetDisplayableValue(), $aResult[$i]);
|
||||
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -131,28 +131,37 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "Server1", 2 => "1", 3 => "production", 4 => "date", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "Server1", 2 => "1", 3 => "production", 4 => "'date' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
],
|
||||
"Case 1 : no match" => [
|
||||
[["Bad", "Server1", "1", "production", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
["org_id" => "",1 => "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
["org_id" => "No match for value 'Bad'",1 => "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
],
|
||||
"Case 10 : Missing mandatory value" => [
|
||||
[["", "Server1", "1", "production", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ "org_id" => "", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[ "org_id" => "invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
],
|
||||
"Case 6 : Unexpected value" => [
|
||||
[["Demo", "Server1", "1", "<svg onclick\"alert(1)\">", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[0 => "Demo", "org_id" => "3", 1 => "Server1", 2 => "1", 3 => "<svg onclick"alert(1)">", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[
|
||||
0 => "Demo",
|
||||
"org_id" => "3",
|
||||
1 => "Server1",
|
||||
2 => "1",
|
||||
3 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||
4 => "",
|
||||
"id" => 1,
|
||||
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Unexpected value for attribute 'status': no match found, check spelling"],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -172,17 +181,20 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
/** @var Server $oServer */
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $aInitData[0],
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||
|
||||
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||
/** @var Server $oServer */
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $aInitData[0],
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||
}
|
||||
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
|
||||
$oBulk = new \BulkChange(
|
||||
@@ -207,13 +219,16 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
if (array_key_exists('__STATUS__', $aRow)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$this->debug("aResult:".$aResult[$i]);
|
||||
$this->assertEquals( $aResult[$i], $oCell->GetDisplayableValue());
|
||||
$this->assertEquals( $aResult[$i], $oCell->GetDisplayableValue(), "failure on " . get_class($oCell) . ' cell type');
|
||||
} else if ($i === "__ERRORS__") {
|
||||
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||
}
|
||||
}
|
||||
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
||||
@@ -225,21 +240,58 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
|
||||
public function CSVImportProvider() {
|
||||
return [
|
||||
"Case 6 - 1 : Unexpected value" => [
|
||||
"Case 6 - 1 : Unexpected value (update)" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
[["Demo", "ServerTest", "key", "BadValue", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[0 => "Demo", "org_id" => "3", 1 => "ServerTest", 2 => "1", 3 => "BadValue", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[
|
||||
0 => "Demo",
|
||||
"org_id" => "3",
|
||||
1 => "ServerTest",
|
||||
2 => "1",
|
||||
3 => "'BadValue' is an invalid value",
|
||||
4 => "",
|
||||
"id" => 1,
|
||||
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||
],
|
||||
],
|
||||
"Case 6 - 2 : Unexpected value" => [
|
||||
"Case 6 - 2 : Unexpected value (update)" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
[["Demo", "ServerTest", "key", "<svg onclick\"alert(1)\">", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[0 => "Demo", "org_id" => "3", 1 => "ServerTest", 2 => "1", 3 => "<svg onclick"alert(1)">", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[
|
||||
0 => "Demo",
|
||||
"org_id" => "3",
|
||||
1 => "ServerTest",
|
||||
2 => "1",
|
||||
3 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||
4 => "",
|
||||
"id" => 1,
|
||||
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||
],
|
||||
],
|
||||
"Case 6 - 3 : Unexpected value (creation)" => [
|
||||
[],
|
||||
[["Demo", "ServerTest", "<svg onclick\"alert(1)\">", ""]],
|
||||
["name" => 1, "status" => 2, "purchase_date" => 3],
|
||||
["org_id" => ["name" => 0]],
|
||||
["name"],
|
||||
[
|
||||
0 => "Demo",
|
||||
"org_id" => "3",
|
||||
1 => "\"ServerTest\"",
|
||||
2 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||
3 => "",
|
||||
"id" => 1,
|
||||
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||
],
|
||||
],
|
||||
"Case 8 : unchanged name" => [
|
||||
["1", "<svg onclick\"alert(1)\">", "production", ""],
|
||||
@@ -263,7 +315,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "date", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'date' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
],
|
||||
"Case 9 - 2: wrong date format" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -271,7 +323,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "<svg onclick"alert(1)">", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'<svg onclick"alert(1)">' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
],
|
||||
"Case 1 - 1 : no match" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -279,7 +331,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "Bad", "org_id" => "",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[ 0 => "Bad", "org_id" => "No match for value 'Bad'",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Object not found",
|
||||
],
|
||||
],
|
||||
"Case 1 - 2 : no match" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -287,7 +341,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "<svg fonclick"alert(1)">", "org_id" => "",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[ 0 => "<svg fonclick"alert(1)">", "org_id" => "No match for value '<svg fonclick\"alert(1)\">'",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Object not found",
|
||||
],
|
||||
],
|
||||
"Case 10 : Missing mandatory value" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -295,7 +351,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "", "org_id" => "", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||
[ 0 => "", "org_id" => "invalid value for attribute", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||
"__ERRORS__" => "Null not allowed",
|
||||
],
|
||||
],
|
||||
|
||||
"Case 0 : Date format" => [
|
||||
@@ -304,7 +362,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["id"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "2020-20-03", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'2020-20-03' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -326,20 +384,22 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
/** @var Server $oServer */
|
||||
$oOrganisation = $this->createObject('Organization', array(
|
||||
'name' =>$aInitData[0]
|
||||
));
|
||||
$aResult["org_id"]=$oOrganisation->GetKey();
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $oOrganisation->GetKey(),
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||
/** @var Server $oServer */
|
||||
$oOrganisation = $this->createObject('Organization', array(
|
||||
'name' => $aInitData[0]
|
||||
));
|
||||
$aResult["org_id"] = $oOrganisation->GetKey();
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $oOrganisation->GetKey(),
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
}
|
||||
$oBulk = new \BulkChange(
|
||||
"Server",
|
||||
$aCsvData,
|
||||
@@ -356,15 +416,17 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
static::assertNotNull($aRes);
|
||||
foreach ($aRes as $aRow) {
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$this->debug("aResult:".$aResult[$i]);
|
||||
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
||||
}
|
||||
elseif ($i == "__STATUS__") {
|
||||
} elseif ($i == "__STATUS__") {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
} else if ($i === "__ERRORS__") {
|
||||
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||
}
|
||||
}
|
||||
$this->assertEquals($aResult[0], $aRow[0]->GetDisplayableValue());
|
||||
@@ -402,4 +464,4 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ use Combodo\iTop\Core\DbConnectionWrapper;
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
use Exception;
|
||||
use MetaModel;
|
||||
use MySQLTransactionNotClosedException;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
@@ -231,22 +232,56 @@ class TransactionsTest extends ItopTestCase
|
||||
public function DBUpdateProvider()
|
||||
{
|
||||
return [
|
||||
"Normal case" => ['iFailAt' => -1, 'bIsModified' => false],
|
||||
"ticket_request" => ['iFailAt' => 1, 'bIsModified' => true],
|
||||
"Normal case" => ['iFailAt' => -1, 'bIsModified' => false],
|
||||
"ticket_request" => ['iFailAt' => 1, 'bIsModified' => true],
|
||||
"lnkcontacttoticket" => ['iFailAt' => 2, 'bIsModified' => true],
|
||||
"History 1" => ['iFailAt' => 3, 'bIsModified' => true],
|
||||
"History 2" => ['iFailAt' => 4, 'bIsModified' => true],
|
||||
"History 3" => ['iFailAt' => 5, 'bIsModified' => true],
|
||||
"History 4" => ['iFailAt' => 6, 'bIsModified' => true],
|
||||
"History 5" => ['iFailAt' => 7, 'bIsModified' => true],
|
||||
"History 6" => ['iFailAt' => 8, 'bIsModified' => true],
|
||||
"History 7" => ['iFailAt' => 9, 'bIsModified' => true],
|
||||
"History 8" => ['iFailAt' => 10, 'bIsModified' => true],
|
||||
"History 9" => ['iFailAt' => 11, 'bIsModified' => true],
|
||||
"History 10" => ['iFailAt' => 12, 'bIsModified' => true],
|
||||
"History 11" => ['iFailAt' => 13, 'bIsModified' => true],
|
||||
"History 12" => ['iFailAt' => 14, 'bIsModified' => true],
|
||||
"History 13" => ['iFailAt' => 15, 'bIsModified' => true],
|
||||
"History 1" => ['iFailAt' => 3, 'bIsModified' => true],
|
||||
"History 2" => ['iFailAt' => 4, 'bIsModified' => true],
|
||||
"History 3" => ['iFailAt' => 5, 'bIsModified' => true],
|
||||
"History 4" => ['iFailAt' => 6, 'bIsModified' => true],
|
||||
"History 5" => ['iFailAt' => 7, 'bIsModified' => true],
|
||||
"History 6" => ['iFailAt' => 8, 'bIsModified' => true],
|
||||
"History 7" => ['iFailAt' => 9, 'bIsModified' => true],
|
||||
"History 8" => ['iFailAt' => 10, 'bIsModified' => true],
|
||||
"History 9" => ['iFailAt' => 11, 'bIsModified' => true],
|
||||
"History 10" => ['iFailAt' => 12, 'bIsModified' => true],
|
||||
"History 11" => ['iFailAt' => 13, 'bIsModified' => true],
|
||||
"History 12" => ['iFailAt' => 14, 'bIsModified' => true],
|
||||
"History 13" => ['iFailAt' => 15, 'bIsModified' => true],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testTransactionOpenedThenClosed()
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION;');
|
||||
CMDBSource::Query('COMMIT;');
|
||||
}
|
||||
|
||||
/**
|
||||
* This will throw an exception in the tearDown method.
|
||||
* This cannot be detected nor by `@expectedException` nor `expectException` method, so we have a specific tearDown impl
|
||||
*
|
||||
* @return void
|
||||
* @doesNotPerformAssertions
|
||||
*/
|
||||
public function testTransactionOpenedNotClosed()
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION;');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
try {
|
||||
parent::tearDown();
|
||||
}
|
||||
catch (MySQLTransactionNotClosedException $e) {
|
||||
if ($this->getName() === 'testTransactionOpenedNotClosed') {
|
||||
$this->debug('Executing the testTransactionOpenNoClose method throws a '.MySQLTransactionNotClosedException::class.' exception in tearDown');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
219
test/core/CRUD/DBObjectTest.php
Normal file
219
test/core/CRUD/DBObjectTest.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2022 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\CRUD;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use lnkContactToFunctionalCI;
|
||||
use MetaModel;
|
||||
|
||||
/**
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
*/
|
||||
class DBObjectTest extends ItopDataTestCase
|
||||
{
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = true;
|
||||
|
||||
|
||||
public function testReloadNotNecessaryForInsert()
|
||||
{
|
||||
$oPerson = $this->CreatePersonInstance();
|
||||
|
||||
// Insert without Reload
|
||||
$oPerson->DBInsert();
|
||||
|
||||
$aValues1 = [];
|
||||
foreach (MetaModel::GetAttributesList('Person') as $sAttCode) {
|
||||
if (MetaModel::GetAttributeDef('Person', $sAttCode) instanceof \AttributeLinkedSet) {
|
||||
continue;
|
||||
}
|
||||
$aValues1[$sAttCode] = $oPerson->Get($sAttCode);
|
||||
}
|
||||
$sOrgName1 = $oPerson->Get('org_name');
|
||||
/** @var \ormLinkSet $oCIList1 */
|
||||
$oCIList1 = $oPerson->Get('cis_list');
|
||||
$oTeamList1 = $oPerson->Get('team_list');
|
||||
|
||||
$sPerson1 = print_r($oPerson, true);
|
||||
|
||||
// 1st Reload
|
||||
$oPerson->Reload(true);
|
||||
$sPerson2 = print_r($oPerson, true);
|
||||
$this->assertNotEquals($sPerson1, $sPerson2);
|
||||
|
||||
$aValues2 = [];
|
||||
foreach (MetaModel::GetAttributesList('Person') as $sAttCode) {
|
||||
if (MetaModel::GetAttributeDef('Person', $sAttCode) instanceof \AttributeLinkedSet) {
|
||||
continue;
|
||||
}
|
||||
$aValues2[$sAttCode] = $oPerson->Get($sAttCode);
|
||||
}
|
||||
|
||||
$sOrgName2 = $oPerson->Get('org_name');
|
||||
/** @var \ormLinkSet $oCIList2 */
|
||||
$oCIList2 = $oPerson->Get('cis_list');
|
||||
$oTeamList2 = $oPerson->Get('team_list');
|
||||
|
||||
$this->assertEquals($sOrgName1, $sOrgName2);
|
||||
$this->assertTrue($oCIList1->Equals($oCIList2));
|
||||
$this->assertTrue($oTeamList1->Equals($oTeamList2));
|
||||
$this->assertEquals($aValues1, $aValues2);
|
||||
|
||||
// 2nd Reload
|
||||
$oPerson->Reload(true);
|
||||
$sPerson3 = print_r($oPerson, true);
|
||||
$this->assertEquals($sPerson2, $sPerson3);
|
||||
|
||||
}
|
||||
|
||||
public function testFriendlynameResetOnExtKeyReset()
|
||||
{
|
||||
$oPerson = $this->CreatePersonInstance();
|
||||
$oManager = $this->CreatePersonInstance();
|
||||
$oManager->DBInsert();
|
||||
|
||||
$oPerson->Set('manager_id', $oManager->GetKey());
|
||||
|
||||
$this->assertNotEmpty($oPerson->Get('manager_id_friendlyname'));
|
||||
|
||||
$oPerson->Set('manager_id', 0);
|
||||
|
||||
$this->assertEmpty($oPerson->Get('manager_id_friendlyname'));
|
||||
}
|
||||
|
||||
public function testReloadNotNecessaryForUpdate()
|
||||
{
|
||||
$oPerson = $this->CreatePersonInstance();
|
||||
$oPerson->DBInsert();
|
||||
$oManager = $this->CreatePersonInstance();
|
||||
$oManager->DBInsert();
|
||||
|
||||
$oPerson->Set('manager_id', $oManager->GetKey());
|
||||
$oPerson->DBUpdate();
|
||||
|
||||
$sManagerFriendlyname1 = $oPerson->Get('manager_id_friendlyname');
|
||||
$oCIList1 = $oPerson->Get('cis_list');
|
||||
$oTeamList1 = $oPerson->Get('team_list');
|
||||
$aValues1 = [];
|
||||
foreach (MetaModel::GetAttributesList('Person') as $sAttCode) {
|
||||
if (MetaModel::GetAttributeDef('Person', $sAttCode) instanceof \AttributeLinkedSet) {
|
||||
continue;
|
||||
}
|
||||
$aValues1[$sAttCode] = $oPerson->Get($sAttCode);
|
||||
}
|
||||
|
||||
$sPerson1 = print_r($oPerson, true);
|
||||
|
||||
// 1st Reload
|
||||
$oPerson->Reload(true);
|
||||
|
||||
$sPerson2 = print_r($oPerson, true);
|
||||
$this->assertNotEquals($sPerson1, $sPerson2);
|
||||
|
||||
$sManagerFriendlyname2 = $oPerson->Get('manager_id_friendlyname');
|
||||
$oCIList2 = $oPerson->Get('cis_list');
|
||||
$oTeamList2 = $oPerson->Get('team_list');
|
||||
$aValues2 = [];
|
||||
foreach (MetaModel::GetAttributesList('Person') as $sAttCode) {
|
||||
if (MetaModel::GetAttributeDef('Person', $sAttCode) instanceof \AttributeLinkedSet) {
|
||||
continue;
|
||||
}
|
||||
$aValues2[$sAttCode] = $oPerson->Get($sAttCode);
|
||||
}
|
||||
|
||||
$this->assertEquals($sManagerFriendlyname1, $sManagerFriendlyname2);
|
||||
$this->assertTrue($oCIList1->Equals($oCIList2));
|
||||
$this->assertTrue($oTeamList1->Equals($oTeamList2));
|
||||
$this->assertEquals($aValues1, $aValues2);
|
||||
|
||||
// 2nd Reload
|
||||
$oPerson->Reload(true);
|
||||
$sPerson3 = print_r($oPerson, true);
|
||||
$this->assertEquals($sPerson2, $sPerson3);
|
||||
}
|
||||
|
||||
public function testGetObjectUpdateUnderReentryProtection()
|
||||
{
|
||||
$oPerson = $this->CreatePersonInstance();
|
||||
$oPerson->DBInsert();
|
||||
|
||||
$oPerson->Set('email', 'test@combodo.com');
|
||||
$oPerson->DBUpdate();
|
||||
|
||||
$this->assertFalse($oPerson->IsModified());
|
||||
|
||||
$oNewPerson = MetaModel::GetObject('Person', $oPerson->GetKey());
|
||||
$this->assertNotEquals($oPerson->GetObjectUniqId(), $oNewPerson->GetObjectUniqId());
|
||||
|
||||
MetaModel::StartReentranceProtection(Metamodel::REENTRANCE_TYPE_UPDATE, $oPerson);
|
||||
|
||||
$oPerson->Set('email', 'test1@combodo.com');
|
||||
$oPerson->DBUpdate();
|
||||
|
||||
$this->assertTrue($oPerson->IsModified());
|
||||
|
||||
$oNewPerson = MetaModel::GetObject('Person', $oPerson->GetKey());
|
||||
$this->assertEquals($oPerson->GetObjectUniqId(), $oNewPerson->GetObjectUniqId());
|
||||
|
||||
MetaModel::StopReentranceProtection(Metamodel::REENTRANCE_TYPE_UPDATE, $oPerson);
|
||||
}
|
||||
|
||||
public function testObjectIsReadOnly()
|
||||
{
|
||||
$oPerson = $this->CreatePersonInstance();
|
||||
|
||||
$sMessage = 'Not allowed to write to this object !';
|
||||
$oPerson->SetReadOnly($sMessage);
|
||||
try {
|
||||
$oPerson->Set('email', 'test1@combodo.com');
|
||||
$this->assertTrue(false, 'Set() should have raised a CoreException');
|
||||
}
|
||||
catch (\CoreException $e) {
|
||||
$this->assertEquals($sMessage, $e->getMessage());
|
||||
}
|
||||
|
||||
$oPerson->SetReadWrite();
|
||||
|
||||
$oPerson->Set('email', 'test1@combodo.com');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
*/
|
||||
private function CreatePersonInstance()
|
||||
{
|
||||
$oServer1 = $this->CreateServer(1);
|
||||
$oServer2 = $this->CreateServer(2);
|
||||
|
||||
$sClass = 'Person';
|
||||
$aParams = [
|
||||
'name' => 'Person_'.rand(10000, 99999),
|
||||
'first_name' => 'Test',
|
||||
'org_id' => $this->getTestOrgId(),
|
||||
];
|
||||
|
||||
$oPerson = MetaModel::NewObject($sClass);
|
||||
foreach ($aParams as $sAttCode => $oValue) {
|
||||
$oPerson->Set($sAttCode, $oValue);
|
||||
}
|
||||
|
||||
$oNewLink1 = new lnkContactToFunctionalCI();
|
||||
$oNewLink1->Set('functionalci_id', $oServer1->GetKey());
|
||||
$oNewLink2 = new lnkContactToFunctionalCI();
|
||||
$oNewLink2->Set('functionalci_id', $oServer2->GetKey());
|
||||
$oCIs = $oPerson->Get('cis_list');
|
||||
$oCIs->AddItem($oNewLink1);
|
||||
$oCIs->AddItem($oNewLink2);
|
||||
$oPerson->Set('cis_list', $oCIs);
|
||||
|
||||
return $oPerson;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user