mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-17 22:39:03 +02:00
Merge branch 'support/3.1.0' into feature/5324-powerportaluser-repairprofiles
This commit is contained in:
@@ -19,17 +19,24 @@
|
||||
* The target license file path is in `$xmlFilePath`
|
||||
*/
|
||||
|
||||
$iTopFolder = __DIR__ . "/../../" ;
|
||||
$xmlFilePath = $iTopFolder . "setup/licenses/community-licenses.xml";
|
||||
$iTopFolder = __DIR__."/../../";
|
||||
$xmlFilePath = $iTopFolder."setup/licenses/community-licenses.xml";
|
||||
|
||||
function get_scope($product_node)
|
||||
{
|
||||
$jqExec = shell_exec("jq -V"); // a param is mandatory otherwise the script will freeze
|
||||
if ((null === $jqExec) || (false === $jqExec)) {
|
||||
echo "/!\ JQ is required but cannot be launched :( \n";
|
||||
echo "Check this script PHPDoc block for instructions\n";
|
||||
die(-1);
|
||||
}
|
||||
|
||||
|
||||
function get_scope($product_node) {
|
||||
$scope = $product_node->getAttribute("scope");
|
||||
|
||||
if ($scope === "")
|
||||
{ //put iTop first
|
||||
if ($scope === "") { //put iTop first
|
||||
return "aaaaaaaaa";
|
||||
}
|
||||
|
||||
return $scope;
|
||||
}
|
||||
|
||||
|
||||
@@ -228,6 +228,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"is_link" => true, /** @since 3.1.0 N°6482 */
|
||||
'uniqueness_rules' => array(
|
||||
'no_duplicate' => array(
|
||||
'attributes' => array(
|
||||
|
||||
@@ -334,6 +334,7 @@ class URP_UserProfile extends UserRightsBaseClassGUI
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"is_link" => true, /** @since 3.1.0 N°6482 */
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
//MetaModel::Init_InheritAttributes();
|
||||
|
||||
@@ -277,6 +277,7 @@ class URP_UserProfile extends UserRightsBaseClass
|
||||
"db_table" => "priv_urp_userprofile",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"is_link" => true, /** @since 3.1.0 N°6482 */
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
//MetaModel::Init_InheritAttributes();
|
||||
|
||||
@@ -238,7 +238,7 @@ class ApplicationContext
|
||||
{
|
||||
$aContextInputBlocks = [];
|
||||
foreach ($this->aValues as $sName => $sValue) {
|
||||
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", utils::EscapeHtml($sValue));
|
||||
$aContextInputBlocks[] = InputUIBlockFactory::MakeForHidden("c[$sName]", $sValue);
|
||||
}
|
||||
return $aContextInputBlocks;
|
||||
}
|
||||
|
||||
@@ -86,15 +86,26 @@ class lnkAuditCategoryToAuditDomain extends cmdbAbstractObject
|
||||
{
|
||||
$aParams = array
|
||||
(
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('category_id', 'domain_id'),
|
||||
"db_table" => "priv_link_audit_category_domain",
|
||||
"db_key_field" => "id",
|
||||
"category" => "application, grant_by_profile",
|
||||
"key_type" => "autoincrement",
|
||||
"name_attcode" => "",
|
||||
"state_attcode" => "",
|
||||
"reconc_keys" => array('category_id', 'domain_id'),
|
||||
"db_table" => "priv_link_audit_category_domain",
|
||||
"db_key_field" => "id",
|
||||
"db_finalclass_field" => "",
|
||||
"is_link" => true,
|
||||
"is_link" => true,
|
||||
'uniqueness_rules' => array(
|
||||
'no_duplicate' => array(
|
||||
'attributes' => array(
|
||||
0 => 'category_id',
|
||||
1 => 'domain_id',
|
||||
),
|
||||
'filter' => '',
|
||||
'disabled' => false,
|
||||
'is_blocking' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("targetclass" => "AuditCategory", "jointype" => '', "allowed_values" => null, "sql" => "category_id", "is_null_allowed" => false, "on_target_delete" => DEL_AUTO, "depends_on" => array())));
|
||||
|
||||
@@ -47,6 +47,7 @@ use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleFormRenderer;
|
||||
use Combodo\iTop\Service\Links\LinkSetDataTransformer;
|
||||
use Combodo\iTop\Service\Links\LinkSetModel;
|
||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectHelper;
|
||||
|
||||
|
||||
define('OBJECT_PROPERTIES_TAB', 'ObjectProperties');
|
||||
@@ -187,9 +188,6 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
/** @var array initial attributes flags cache [attcode]['flags'] */
|
||||
protected $aInitialAttributesFlags;
|
||||
|
||||
protected $iUpdateLoopCount;
|
||||
|
||||
const MAX_UPDATE_LOOP_COUNT = 10;
|
||||
|
||||
/**
|
||||
* @var array First level classname, second level id, value number of calls done
|
||||
@@ -227,7 +225,6 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
|
||||
$this->sDisplayMode = static::DEFAULT_DISPLAY_MODE;
|
||||
$this->bAllowWrite = false;
|
||||
$this->bAllowDelete = false;
|
||||
$this->iUpdateLoopCount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2822,33 +2819,33 @@ JS
|
||||
}
|
||||
}
|
||||
// Custom operation for the form ?
|
||||
if (isset($aExtraParams['custom_operation'])) {
|
||||
$sOperation = $aExtraParams['custom_operation'];
|
||||
} else {
|
||||
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
|
||||
$sOperation = 'apply_modify';
|
||||
} else {
|
||||
$sOperation = 'apply_new';
|
||||
}
|
||||
}
|
||||
if (isset($aExtraParams['custom_operation'])) {
|
||||
$sOperation = $aExtraParams['custom_operation'];
|
||||
} else {
|
||||
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
|
||||
$sOperation = 'apply_modify';
|
||||
} else {
|
||||
$sOperation = 'apply_new';
|
||||
}
|
||||
}
|
||||
|
||||
$oContentBlock = new UIContentBlock();
|
||||
$oPage->AddUiBlock($oContentBlock);
|
||||
$oContentBlock = new UIContentBlock();
|
||||
$oPage->AddUiBlock($oContentBlock);
|
||||
|
||||
$oForm = new Form("form_{$this->m_iFormId}");
|
||||
$oForm->SetAction($sFormAction);
|
||||
$sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');";
|
||||
if (isset($aExtraParams['js_handlers']['form_on_submit'])) {
|
||||
$oForm->SetOnSubmitJsCode($sOnSubmitForm.$aExtraParams['js_handlers']['form_on_submit']);
|
||||
} else {
|
||||
$oForm->SetOnSubmitJsCode($sOnSubmitForm."return bOnSubmitForm;");
|
||||
}
|
||||
$oContentBlock->AddSubBlock($oForm);
|
||||
$oForm = new Form("form_{$this->m_iFormId}");
|
||||
$oForm->SetAction($sFormAction);
|
||||
$sOnSubmitForm = "let bOnSubmitForm = OnSubmit('form_{$this->m_iFormId}');";
|
||||
if (isset($aExtraParams['js_handlers']['form_on_submit'])) {
|
||||
$oForm->SetOnSubmitJsCode($sOnSubmitForm . $aExtraParams['js_handlers']['form_on_submit']);
|
||||
} else {
|
||||
$oForm->SetOnSubmitJsCode($sOnSubmitForm . "return bOnSubmitForm;");
|
||||
}
|
||||
$oContentBlock->AddSubBlock($oForm);
|
||||
|
||||
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
|
||||
// The object already exists in the database, it's a modification
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
|
||||
}
|
||||
if ($this->GetDisplayMode() === static::ENUM_DISPLAY_MODE_EDIT) {
|
||||
// The object already exists in the database, it's a modification
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('id', $iKey, "{$sPrefix}_id"));
|
||||
}
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', $sOperation));
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
|
||||
|
||||
@@ -2857,6 +2854,11 @@ JS
|
||||
$oPage->SetTransactionId($iTransactionId);
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
|
||||
|
||||
// Add temporary object watchdog (only on root form)
|
||||
if (!utils::IsXmlHttpRequest()) {
|
||||
$oPage->add_ready_script(TemporaryObjectHelper::GetWatchDogJS($iTransactionId));
|
||||
}
|
||||
|
||||
// TODO 3.0.0: Is this (the if condition, not the code inside) still necessary?
|
||||
if (isset($aExtraParams['wizard_container']) && $aExtraParams['wizard_container']) {
|
||||
$sClassLabel = MetaModel::GetName($sClass);
|
||||
@@ -2867,34 +2869,34 @@ JS
|
||||
}
|
||||
}
|
||||
|
||||
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
|
||||
$oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null);
|
||||
|
||||
$oCancelButton = ButtonUIBlockFactory::MakeForCancel();
|
||||
$oCancelButton->AddCSSClasses(['action', 'cancel']);
|
||||
$oToolbarButtons->AddSubBlock($oCancelButton);
|
||||
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
|
||||
$oApplyButton->AddCSSClass('action');
|
||||
$oToolbarButtons->AddSubBlock($oApplyButton);
|
||||
$bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true;
|
||||
$aTransitions = $this->EnumTransitions();
|
||||
if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) {
|
||||
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
|
||||
$oSetToCheckRights = DBObjectSet::FromObject($this);
|
||||
$oCancelButton = ButtonUIBlockFactory::MakeForCancel();
|
||||
$oCancelButton->AddCSSClasses(['action', 'cancel']);
|
||||
$oToolbarButtons->AddSubBlock($oCancelButton);
|
||||
$oApplyButton = ButtonUIBlockFactory::MakeForPrimaryAction($sApplyButton, null, null, true);
|
||||
$oApplyButton->AddCSSClass('action');
|
||||
$oToolbarButtons->AddSubBlock($oApplyButton);
|
||||
$bAreTransitionsHidden = isset($aExtraParams['hide_transitions']) && $aExtraParams['hide_transitions'] === true;
|
||||
$aTransitions = $this->EnumTransitions();
|
||||
if (!isset($aExtraParams['custom_operation']) && !$bAreTransitionsHidden && count($aTransitions)) {
|
||||
// Transitions are displayed only for the standard new/modify actions, not for modify_all or any other case...
|
||||
$oSetToCheckRights = DBObjectSet::FromObject($this);
|
||||
|
||||
$oTransitionPopoverMenu = new PopoverMenu();
|
||||
$sTPMSectionId = 'transitions';
|
||||
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
|
||||
$aStimuli = Metamodel::EnumStimuli($sClass);
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
|
||||
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
// Button to be displayed on its own on large screens
|
||||
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
|
||||
$oButton->AddCSSClass('action');
|
||||
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
|
||||
$oToolbarButtons->AddSubBlock($oButton);
|
||||
$oTransitionPopoverMenu = new PopoverMenu();
|
||||
$sTPMSectionId = 'transitions';
|
||||
$oTransitionPopoverMenu->AddSection($sTPMSectionId);
|
||||
$aStimuli = Metamodel::EnumStimuli($sClass);
|
||||
foreach ($aTransitions as $sStimulusCode => $aTransitionDef) {
|
||||
$iActionAllowed = (get_class($aStimuli[$sStimulusCode]) == 'StimulusUserAction') ? UserRights::IsStimulusAllowed($sClass,
|
||||
$sStimulusCode, $oSetToCheckRights) : UR_ALLOWED_NO;
|
||||
switch ($iActionAllowed) {
|
||||
case UR_ALLOWED_YES:
|
||||
// Button to be displayed on its own on large screens
|
||||
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction($aStimuli[$sStimulusCode]->GetLabel(), 'next_action', $sStimulusCode, true);
|
||||
$oButton->AddCSSClass('action');
|
||||
$oButton->SetColor(Button::ENUM_COLOR_SCHEME_NEUTRAL);
|
||||
$oToolbarButtons->AddSubBlock($oButton);
|
||||
|
||||
// Button to be displayed in a grouped button on smaller screens
|
||||
$oTPMPopupMenuItem = new JSPopupMenuItem('next_action--'.$oButton->GetId(), $oButton->GetLabel(), "$(`#{$oButton->GetId()}`).trigger(`click`);");
|
||||
@@ -3049,16 +3051,21 @@ JS
|
||||
|
||||
$oPage->SetCurrentTab('');
|
||||
|
||||
// Static fields values for wizard helper serialization
|
||||
$aWizardHelperStaticValues = [];
|
||||
|
||||
// Add as hidden inputs values that we want displayed if they're readonly
|
||||
if(isset($aExtraParams['forceFieldsSubmission'])){
|
||||
$aExtraFlags = $aExtraParams['fieldsFlags'] ?? [];
|
||||
foreach ($aExtraParams['forceFieldsSubmission'] as $sAttCode) {
|
||||
if(FormHelper::GetAttributeFlagsForObject($this, $sAttCode, $aExtraFlags) & OPT_ATT_READONLY) {
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('attr_'.$sPrefix.$sAttCode, $this->Get($sAttCode)));
|
||||
$aWizardHelperStaticValues[$sAttCode] = $this->Get($sAttCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sWizardHelperStaticValues = json_encode($aWizardHelperStaticValues);
|
||||
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass));
|
||||
$oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId));
|
||||
foreach ($aExtraParams as $sName => $value) {
|
||||
@@ -3101,6 +3108,7 @@ JS
|
||||
var oWizardHelper$sPrefix = new WizardHelper('$sClass', '$sPrefix', '$sLifecycleStateForWizardHelper');
|
||||
oWizardHelper$sPrefix.SetFieldsMap($sJsonFieldsMap);
|
||||
oWizardHelper$sPrefix.SetFieldsCount($iFieldsCount);
|
||||
oWizardHelper$sPrefix.SetStaticValues($sWizardHelperStaticValues);
|
||||
EOF
|
||||
);
|
||||
$oPage->add_ready_script(
|
||||
@@ -4513,16 +4521,12 @@ HTML;
|
||||
*/
|
||||
public function DBInsertNoReload()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
try {
|
||||
$res = parent::DBInsertNoReload();
|
||||
|
||||
$this->SetWarningsAsSessionMessages('create');
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
}
|
||||
} finally {
|
||||
if (static::IsCrudStackEmpty()) {
|
||||
// Avoid signaling the current object that links were modified
|
||||
@@ -4530,9 +4534,23 @@ HTML;
|
||||
static::FireEventDbLinksChangedForAllObjects();
|
||||
}
|
||||
}
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function PostInsertActions(): void
|
||||
{
|
||||
parent::PostInsertActions();
|
||||
|
||||
// Invoke extensions after insertion (the object must exist, have an id, etc.)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBInsert()");
|
||||
$oExtensionInstance->OnDBInsert($this, self::GetCurrentChange());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* Attaches InlineImages to the current object
|
||||
@@ -4558,57 +4576,39 @@ HTML;
|
||||
|
||||
public function DBUpdate()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
|
||||
try {
|
||||
if (count($this->ListChanges()) === 0) {
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $this->GetKey();
|
||||
}
|
||||
$res = parent::DBUpdate();
|
||||
|
||||
$this->SetWarningsAsSessionMessages('update');
|
||||
|
||||
// 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()
|
||||
if (!MetaModel::StartReentranceProtection($this)) {
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
IssueLog::Debug("CRUD: DBUpdate $sClass::$sKey Rejected (reentrance)", LogChannels::DM_CRUD);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
try {
|
||||
// Invoke extensions after the update (could be before)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
}
|
||||
}
|
||||
finally {
|
||||
MetaModel::StopReentranceProtection($this);
|
||||
}
|
||||
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) != 0) {
|
||||
$this->iUpdateLoopCount++;
|
||||
if ($this->iUpdateLoopCount > self::MAX_UPDATE_LOOP_COUNT) {
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
$aPlugins = [];
|
||||
foreach (MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance) {
|
||||
$aPlugins[] = get_class($oExtensionInstance);
|
||||
}
|
||||
$sPlugins = implode(', ', $aPlugins);
|
||||
IssueLog::Error("CRUD: DBUpdate $sClass::$sKey Update loop detected plugins: $sPlugins", LogChannels::DM_CRUD);
|
||||
} else {
|
||||
return $this->DBUpdate();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (static::IsCrudStackEmpty()) {
|
||||
static::FireEventDbLinksChangedForAllObjects();
|
||||
}
|
||||
}
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function PostUpdateActions(array $aChanges): void
|
||||
{
|
||||
parent::PostUpdateActions($aChanges);
|
||||
|
||||
// Invoke extensions after the update (could be before)
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach (MetaModel::EnumPlugins(iApplicationObjectExtension::class) as $oExtensionInstance) {
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnDBUpdate()");
|
||||
$oExtensionInstance->OnDBUpdate($this, self::GetCurrentChange());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sMessageIdPrefix
|
||||
*
|
||||
@@ -4628,6 +4628,7 @@ HTML;
|
||||
|
||||
public function DBDelete(&$oDeletionPlan = null)
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
try {
|
||||
parent::DBDelete($oDeletionPlan);
|
||||
} finally {
|
||||
@@ -4637,6 +4638,7 @@ HTML;
|
||||
static::FireEventDbLinksChangedForAllObjects();
|
||||
}
|
||||
}
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
@@ -4665,9 +4667,12 @@ HTML;
|
||||
/** @var \iApplicationObjectExtension $oExtensionInstance */
|
||||
foreach(MetaModel::EnumPlugins('iApplicationObjectExtension') as $oExtensionInstance)
|
||||
{
|
||||
if ($oExtensionInstance->OnIsModified($this))
|
||||
{
|
||||
$sExtensionClass = get_class($oExtensionInstance);
|
||||
if ($oExtensionInstance->OnIsModified($this)) {
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> true");
|
||||
return true;
|
||||
} else {
|
||||
$this->LogCRUDDebug(__METHOD__, "Calling $sExtensionClass::OnIsModified() -> false");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6021,14 +6026,16 @@ JS
|
||||
// - we have a EVENT_DB_LINKS_CHANGED listener on Ticket that will update impacted items, so it will create new lnkApplicationSolutionToFunctionalCI
|
||||
// We want to avoid launching the listener twice, first here, and secondly after saving the Ticket in the listener
|
||||
// By disabling the event to be fired, we can remove the current object from the attribute !
|
||||
/** @noinspection PhpRedundantOptionalArgumentInspection */
|
||||
$oObject = MetaModel::GetObject($sClass, $sId, true);
|
||||
self::SetEventDBLinksChangedBlocked(true);
|
||||
MetaModel::StartReentranceProtection($oObject);
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
MetaModel::StopReentranceProtection($oObject);
|
||||
if ($oObject->IsModified()) {
|
||||
$oObject->DBUpdate();
|
||||
$oObject = MetaModel::GetObject($sClass, $sId, false);
|
||||
// N°6408 The object can have been deleted
|
||||
if (!is_null($oObject)) {
|
||||
self::SetEventDBLinksChangedBlocked(true);
|
||||
MetaModel::StartReentranceProtection($oObject);
|
||||
$oObject->FireEvent(EVENT_DB_LINKS_CHANGED);
|
||||
MetaModel::StopReentranceProtection($oObject);
|
||||
if (count($oObject->ListChanges()) !== 0) {
|
||||
$oObject->DBUpdate();
|
||||
}
|
||||
}
|
||||
self::RemoveObjectAwaitingEventDbLinksChanged($sClass, $sId);
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(false);
|
||||
|
||||
@@ -667,7 +667,7 @@ class DashletUnknown extends Dashlet
|
||||
*/
|
||||
public function GetPropertiesFields(DesignerForm $oForm)
|
||||
{
|
||||
$oField = new DesignerLongTextField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML);
|
||||
$oField = new DesignerXMLField('xml', Dict::S('UI:DashletUnknown:Prop-XMLConfiguration'), $this->sOriginalDashletXML);
|
||||
$oForm->AddField($oField);
|
||||
}
|
||||
|
||||
|
||||
@@ -100,20 +100,11 @@
|
||||
<enable_class>URP_Profiles</enable_class>
|
||||
<enable_action>UR_ACTION_MODIFY</enable_action>
|
||||
</menu>
|
||||
<menu id="AuditCategories" xsi:type="DashboardMenuNode" _delta="define">
|
||||
<menu id="AuditCategories" xsi:type="OQLMenuNode" _delta="define">
|
||||
<rank>20</rank>
|
||||
<parent>AdminTools</parent>
|
||||
<definition>
|
||||
<layout>DashboardLayoutOneCol</layout>
|
||||
<title>Menu:WelcomeMenuPage</title>
|
||||
<cells>
|
||||
<cell id="0">
|
||||
<rank>0</rank>
|
||||
<dashlets>
|
||||
</dashlets>
|
||||
</cell>
|
||||
</cells>
|
||||
</definition>
|
||||
<oql><![CDATA[SELECT AuditCategory]]></oql>
|
||||
<do_search>1</do_search>
|
||||
<enable_class>AuditCategory</enable_class>
|
||||
<enable_action>UR_ACTION_MODIFY</enable_action>
|
||||
</menu>
|
||||
|
||||
@@ -1871,7 +1871,13 @@ class MenuBlock extends DisplayBlock
|
||||
/** @var array $aToolkitActions Any "legacy" toolkit menu item, which are now displayed in the same menu as the $aRegularActions, after them */
|
||||
$aToolkitActions = [];
|
||||
|
||||
if (!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == "")) {
|
||||
// Display menu actions only if...
|
||||
if (
|
||||
// ... NOT in a selection mode
|
||||
(!isset($aExtraParams['selection_mode']) || ($aExtraParams['selection_mode'] == ""))
|
||||
// ... "menu" parameter is NOT EXPLICITLY disabled
|
||||
&& (!isset($aExtraParams['menu']) || $aExtraParams['menu'] === "1" || $aExtraParams['menu'] === true)
|
||||
) {
|
||||
$oAppContext = new ApplicationContext();
|
||||
$sContext = $oAppContext->GetForLink();
|
||||
if (utils::IsNotNullOrEmptyString($sContext)) {
|
||||
|
||||
@@ -1110,13 +1110,41 @@ $('#$sId').on('change keyup validate', function() { ValidateWithPattern('$sId',
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".utils::EscapeHtml($this->defaultValue)."</textarea>";
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".$this->PrepareValueForRendering()."</textarea>";
|
||||
}
|
||||
else {
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".utils::EscapeHtml($this->defaultValue)."</div>";
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".$this->PrepareValueForRendering()."</div>";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null The value itself as expected for rendering. May it be encoded, escaped or else.
|
||||
* @since 3.1.0 N°6405
|
||||
*/
|
||||
protected function PrepareValueForRendering(): ?string
|
||||
{
|
||||
return utils::EscapeHtml($this->defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class DesignerXMLField
|
||||
*
|
||||
* Field to display XML content
|
||||
*
|
||||
* @author Guillaume Lajarige <guillaume.lajarige@combodo.com>
|
||||
* @since 3.1.0 N°6405
|
||||
*/
|
||||
class DesignerXMLField extends DesignerLongTextField
|
||||
{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function PrepareValueForRendering(): ?string
|
||||
{
|
||||
return utils::EscapeHtml($this->defaultValue, true);
|
||||
}
|
||||
}
|
||||
|
||||
class DesignerIntegerField extends DesignerFormField
|
||||
|
||||
@@ -74,6 +74,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
"default_value" => 0,
|
||||
"is_null_allowed" => false,
|
||||
"depends_on" => array(),
|
||||
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime("export_last_date", array(
|
||||
@@ -82,6 +83,7 @@ abstract class Query extends cmdbAbstractObject
|
||||
"default_value" => null,
|
||||
"is_null_allowed" => true,
|
||||
"depends_on" => array(),
|
||||
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalKey("export_last_user_id",
|
||||
@@ -93,14 +95,16 @@ abstract class Query extends cmdbAbstractObject
|
||||
"depends_on"=>array(),
|
||||
"display_style"=>'select',
|
||||
"always_load_in_tables"=>false,
|
||||
"on_target_delete"=>DEL_SILENT
|
||||
"on_target_delete"=>DEL_SILENT,
|
||||
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
|
||||
)));
|
||||
|
||||
MetaModel::Init_AddAttribute(new AttributeExternalField("export_last_user_contact",
|
||||
array(
|
||||
"allowed_values"=>null,
|
||||
"extkey_attcode"=> "export_last_user_id",
|
||||
"target_attcode"=>"contactid"
|
||||
"target_attcode"=>"contactid",
|
||||
"tracking_level" => ATTRIBUTE_TRACKING_NONE,
|
||||
)));
|
||||
|
||||
// Display lists
|
||||
|
||||
@@ -168,8 +168,6 @@ class UIExtKeyWidget
|
||||
$sMessage = Dict::S('UI:Message:EmptyList:UseSearchForm');
|
||||
$sAttrFieldPrefix = ($this->bSearchMode) ? '' : 'attr_';
|
||||
|
||||
|
||||
|
||||
$sFilter = addslashes($oAllowedValues->GetFilter()->ToOQL());
|
||||
if ($this->bSearchMode) {
|
||||
$sWizHelper = 'null';
|
||||
@@ -1070,18 +1068,27 @@ JS
|
||||
{
|
||||
$oObj = MetaModel::NewObject($this->sTargetClass);
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm($this->iId);
|
||||
if (count($aErrors) == 0)
|
||||
{
|
||||
$oObj->DBInsert();
|
||||
if (count($aErrors) == 0) {
|
||||
|
||||
// Retrieve JSON data
|
||||
$sJSON = utils::ReadParam('json', '{}', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA);
|
||||
$oJSON = json_decode($sJSON);
|
||||
|
||||
$oObj->SetContextSection('temporary_objects', [
|
||||
'create' => [
|
||||
'transaction_id' => utils::ReadParam('root_transaction_id', '', false, utils::ENUM_SANITIZATION_FILTER_TRANSACTION_ID),
|
||||
'host_class' => $oJSON->m_sClass,
|
||||
'host_att_code' => $this->sAttCode,
|
||||
],
|
||||
]);
|
||||
$oObj->DBInsertNoReload();
|
||||
|
||||
return array('name' => $oObj->GetName(), 'id' => $oObj->GetKey());
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
return array('error' => implode(' ', $aErrors), 'id' => 0);
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
catch (Exception $e) {
|
||||
return array('error' => $e->getMessage(), 'id' => 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1970,6 +1970,7 @@ SQL;
|
||||
|
||||
/**
|
||||
* @param string $sValue
|
||||
* @param bool $bDoubleEncode Whether to double encode the value or not
|
||||
*
|
||||
* @return string passed value with only characters having a special meaning in HTML escaped as entities
|
||||
* Since 3.0.0 we were using for this {@link HtmlEntities} but it was overkill and leads to double escaping !
|
||||
@@ -1977,14 +1978,15 @@ SQL;
|
||||
* @uses \htmlspecialchars()
|
||||
* @link https://www.php.net/manual/fr/function.htmlspecialchars.php
|
||||
* @since 3.0.0 N°3623
|
||||
* @since 3.1.0 N°6405 Add $bDoubleEncode parameter
|
||||
*/
|
||||
public static function EscapeHtml($sValue)
|
||||
public static function EscapeHtml($sValue, bool $bDoubleEncode = false)
|
||||
{
|
||||
return htmlspecialchars(
|
||||
$sValue ?? '',
|
||||
ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5,
|
||||
WebPage::PAGES_CHARSET,
|
||||
false
|
||||
$bDoubleEncode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2045,6 +2047,9 @@ SQL;
|
||||
*/
|
||||
public static function TextToHtml($sText)
|
||||
{
|
||||
if (static::IsNullOrEmptyString($sText)){
|
||||
return '';
|
||||
}
|
||||
$sText = str_replace("\r\n", "\n", $sText);
|
||||
$sText = str_replace("\r", "\n", $sText);
|
||||
|
||||
@@ -2904,7 +2909,8 @@ HTML;
|
||||
if ($sClassNameFilter !== '' && strpos($sPHPClass, $sClassNameFilter) === false) {
|
||||
$bSkipped = true;
|
||||
}
|
||||
else {
|
||||
// For some PHP classes we don't have their file path as they are already in memory, so we never filter on their paths
|
||||
elseif (utils::IsNotNullOrEmptyString($sPHPFile)) {
|
||||
$sPHPFile = self::LocalPath($sPHPFile);
|
||||
if ($sPHPFile !== false) {
|
||||
$sPHPFile = '/'.$sPHPFile; // for regex
|
||||
@@ -3368,5 +3374,22 @@ HTML;
|
||||
{
|
||||
return in_array($sTrait, self::TraitsUsedByClass($sClass, true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get stack trace as string array.
|
||||
*
|
||||
* @return array
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function GetStackTraceAsArray(): array
|
||||
{
|
||||
$e = new Exception();
|
||||
$aTrace = explode("\n", $e->getTraceAsString());
|
||||
// Remove call to this method
|
||||
array_shift($aTrace);
|
||||
// Remove Main
|
||||
array_pop($aTrace);
|
||||
|
||||
return $aTrace;
|
||||
}
|
||||
}
|
||||
|
||||
101
core/TemporaryObjectDescriptor.php
Normal file
101
core/TemporaryObjectDescriptor.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class TemporaryObjectDescriptor
|
||||
*
|
||||
* Descriptor to track a temporary object.
|
||||
*
|
||||
* @experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
class TemporaryObjectDescriptor extends DBObject
|
||||
{
|
||||
public static function Init()
|
||||
{
|
||||
$aParams = array(
|
||||
'category' => 'core',
|
||||
'key_type' => 'autoincrement',
|
||||
'name_attcode' => array('item_class', 'temp_id'),
|
||||
'image_attcode' => '',
|
||||
'state_attcode' => '',
|
||||
'reconc_keys' => array(''),
|
||||
'db_table' => 'priv_temporary_object_descriptor',
|
||||
'db_key_field' => 'id',
|
||||
'db_finalclass_field' => '',
|
||||
'style' => new ormStyle(null, null, null, null, null, null),
|
||||
'indexes' => array(
|
||||
1 =>
|
||||
array(
|
||||
0 => 'temp_id',
|
||||
),
|
||||
2 =>
|
||||
array(
|
||||
0 => 'item_class',
|
||||
1 => 'item_id',
|
||||
),
|
||||
),
|
||||
);
|
||||
MetaModel::Init_Params($aParams);
|
||||
MetaModel::Init_InheritAttributes();
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime('expiration_date', array('sql' => 'expiration_date', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString('temp_id', array('sql' => 'temp_id', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString('item_class', array('sql' => 'item_class', 'is_null_allowed' => false, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeObjectKey('item_id', array('class_attcode' => 'item_class', 'sql' => 'item_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeDateTime('creation_date', array('sql' => 'creation_date', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString('host_class', array('sql' => 'host_class', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeObjectKey('host_id', array('class_attcode' => 'host_class', 'sql' => 'host_id', 'is_null_allowed' => true, 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeString('host_att_code', array('sql' => 'host_att_code', 'is_null_allowed' => true, 'default_value' => '', 'allowed_values' => null, 'depends_on' => array(), 'always_load_in_tables' => false)));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnum("operation", array("allowed_values" => new ValueSetEnum('create,delete'), "sql" => "operation", "default_value" => "create", "is_null_allowed" => true, "depends_on" => array())));
|
||||
|
||||
MetaModel::Init_SetZListItems('details', array(
|
||||
0 => 'temp_id',
|
||||
1 => 'item_class',
|
||||
2 => 'item_id',
|
||||
3 => 'creation_date',
|
||||
4 => 'expiration_date',
|
||||
5 => 'meta',
|
||||
));
|
||||
MetaModel::Init_SetZListItems('standard_search', array(
|
||||
0 => 'temp_id',
|
||||
1 => 'item_class',
|
||||
2 => 'item_id',
|
||||
));
|
||||
MetaModel::Init_SetZListItems('list', array(
|
||||
0 => 'temp_id',
|
||||
1 => 'item_class',
|
||||
2 => 'item_id',
|
||||
3 => 'creation_date',
|
||||
4 => 'expiration_date',
|
||||
));;
|
||||
}
|
||||
|
||||
|
||||
public function DBInsertNoReload()
|
||||
{
|
||||
$this->SetCurrentDateIfNull('creation_date');
|
||||
|
||||
return parent::DBInsertNoReload();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set/Update all of the '_item' fields
|
||||
*
|
||||
* @param object $oItem Container item
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function SetItem($oItem, $bUpdateOnChange = false)
|
||||
{
|
||||
$sClass = get_class($oItem);
|
||||
$iItemId = $oItem->GetKey();
|
||||
|
||||
$this->Set('item_class', $sClass);
|
||||
$this->Set('item_id', $iItemId);
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ use Combodo\iTop\Application\UI\Links\Set\BlockLinkSetDisplayAsProperty;
|
||||
use Combodo\iTop\Form\Field\LabelField;
|
||||
use Combodo\iTop\Form\Field\TextAreaField;
|
||||
use Combodo\iTop\Form\Form;
|
||||
use Combodo\iTop\Form\Validator\LinkedSetValidator;
|
||||
use Combodo\iTop\Form\Validator\NotEmptyExtKeyValidator;
|
||||
use Combodo\iTop\Form\Validator\Validator;
|
||||
use Combodo\iTop\Form\Validator\CustomRegexpValidator;
|
||||
use Combodo\iTop\Renderer\BlockRenderer;
|
||||
use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer;
|
||||
use Combodo\iTop\Service\Links\LinkSetModel;
|
||||
@@ -93,9 +91,6 @@ define('LINKSET_EDITMODE_ACTIONS', 2); // Show the usual 'Actions' popup menu
|
||||
define('LINKSET_EDITMODE_INPLACE', 3); // The "linked" objects can be created/modified/deleted in place
|
||||
define('LINKSET_EDITMODE_ADDREMOVE', 4); // The "linked" objects can be added/removed in place
|
||||
|
||||
define('LINKSET_RELATIONTYPE_PROPERTY', 'property');
|
||||
define('LINKSET_RELATIONTYPE_LINK', 'link');
|
||||
|
||||
define('LINKSET_DISPLAY_STYLE_PROPERTY', 'property');
|
||||
define('LINKSET_DISPLAY_STYLE_TAB', 'tab');
|
||||
|
||||
@@ -1126,7 +1121,7 @@ abstract class AttributeDefinition
|
||||
|
||||
// Validation pattern
|
||||
if ($this->GetValidationPattern() !== '') {
|
||||
$oFormField->AddValidator(new Validator($this->GetValidationPattern()));
|
||||
$oFormField->AddValidator(new CustomRegexpValidator($this->GetValidationPattern()));
|
||||
}
|
||||
|
||||
// Description
|
||||
@@ -1155,6 +1150,13 @@ abstract class AttributeDefinition
|
||||
$oFormField->AddMetadata('value-raw', (string)$oObject->Get($this->GetCode()));
|
||||
}
|
||||
|
||||
// We don't want to invalidate field because of old untouched values that are no longer valid
|
||||
$aModifiedAttCodes = $oObject->ListChanges();
|
||||
$bAttributeHasBeenModified = array_key_exists($this->GetCode(), $aModifiedAttCodes);
|
||||
if (false === $bAttributeHasBeenModified) {
|
||||
$oFormField->SetValidationDisabled(true);
|
||||
}
|
||||
|
||||
return $oFormField;
|
||||
}
|
||||
|
||||
@@ -1697,22 +1699,12 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_EDITMODE_* constants
|
||||
* @since 3.1.0 N°5563 relations are edited using new attributes in details mode, but as nothing changed in edit mode we are still using edit_mode attribute
|
||||
*/
|
||||
public function GetEditMode()
|
||||
{
|
||||
return $this->GetOptional('edit_mode', LINKSET_EDITMODE_ACTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_RELATIONTYPE_* constants
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetRelationType()
|
||||
{
|
||||
return $this->GetOptional('relation_type', LINKSET_RELATIONTYPE_LINK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string see LINKSET_DISPLAY_STYLE_* constants
|
||||
* @since 3.1.0 N°3190
|
||||
@@ -1737,15 +1729,6 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return $this->GetOptional('with_php_constraint', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetReadOnly()
|
||||
{
|
||||
return $this->GetOptional('read_only', false);
|
||||
}
|
||||
|
||||
public function GetLinkedClass()
|
||||
{
|
||||
return $this->Get('linked_class');
|
||||
@@ -1772,7 +1755,53 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
}
|
||||
|
||||
/** @inheritDoc * */
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true): string
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
if($this->GetDisplayStyle() === LINKSET_DISPLAY_STYLE_TAB){
|
||||
return $this->GetAsHTMLForTab($sValue, $oHostObject, $bLocalize);
|
||||
}
|
||||
else{
|
||||
return $this->GetAsHTMLForProperty($sValue, $oHostObject, $bLocalize);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetAsHTMLForTab($sValue, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
if (is_object($sValue) && ($sValue instanceof ormLinkSet))
|
||||
{
|
||||
$sValue->Rewind();
|
||||
$aItems = array();
|
||||
while ($oObj = $sValue->Fetch())
|
||||
{
|
||||
// Show only relevant information (hide the external key to the current object)
|
||||
$aAttributes = array();
|
||||
foreach(MetaModel::ListAttributeDefs($this->GetLinkedClass()) as $sAttCode => $oAttDef)
|
||||
{
|
||||
if ($sAttCode == $this->GetExtKeyToMe())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ($oAttDef->IsExternalField())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$sAttValue = $oObj->GetAsHTML($sAttCode);
|
||||
if (strlen($sAttValue) > 0)
|
||||
{
|
||||
$aAttributes[] = $sAttValue;
|
||||
}
|
||||
}
|
||||
$sAttributes = implode(', ', $aAttributes);
|
||||
$aItems[] = $sAttributes;
|
||||
}
|
||||
|
||||
return implode('<br/>', $aItems);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function GetAsHTMLForProperty($sValue, $oHostObject = null, $bLocalize = true): string
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -2447,8 +2476,6 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
$oFormField->SetLnkAttributesToDisplay($aLnkAttributesToDisplay);
|
||||
}
|
||||
|
||||
$oFormField->AddValidator(new LinkedSetValidator());
|
||||
|
||||
parent::MakeFormField($oObject, $oFormField);
|
||||
|
||||
return $oFormField;
|
||||
@@ -2558,15 +2585,6 @@ class AttributeLinkedSetIndirect extends AttributeLinkedSet
|
||||
return $this->GetOptional("duplicates", false);
|
||||
} // The same object may be linked several times... or not...
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
* @since 3.1.0 N°5563
|
||||
*/
|
||||
public function GetReadOnly()
|
||||
{
|
||||
return $this->GetOptional('read_only', false);
|
||||
}
|
||||
|
||||
public function GetTrackingLevel()
|
||||
{
|
||||
return $this->GetOptional('tracking_level',
|
||||
@@ -3873,6 +3891,12 @@ class AttributeApplicationLanguage extends AttributeString
|
||||
{
|
||||
$aLanguageCodes[$sLangCode] = $aInfo['description'].' ('.$aInfo['localized_description'].')';
|
||||
}
|
||||
|
||||
// N°6462 This should be sorted directly in \Dict during the compilation but we can't for 2 reasons:
|
||||
// - Additional languages can be added on the fly even though it is not recommended
|
||||
// - Formatting is done at run time (just above)
|
||||
natcasesort($aLanguageCodes);
|
||||
|
||||
$aParams["allowed_values"] = new ValueSetEnum($aLanguageCodes);
|
||||
parent::__construct($sCode, $aParams);
|
||||
}
|
||||
@@ -4163,7 +4187,7 @@ class AttributePassword extends AttributeString implements iAttributeNoGroupBy
|
||||
|
||||
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
if (strlen($sValue) == 0)
|
||||
if (utils::IsNullOrEmptyString($sValue))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
@@ -7196,14 +7220,27 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (MetaModel::IsValidObject($proposedValue))
|
||||
{
|
||||
if (MetaModel::IsValidObject($proposedValue)) {
|
||||
return $proposedValue->GetKey();
|
||||
}
|
||||
|
||||
return (int)$proposedValue;
|
||||
}
|
||||
|
||||
/** @inheritdoc @since 3.1 */
|
||||
public function WriteExternalValues(DBObject $oHostObject): void
|
||||
{
|
||||
$sTargetKey = $oHostObject->Get($this->GetCode());
|
||||
$oFilter = DBSearch::FromOQL('SELECT `'.TemporaryObjectDescriptor::class.'` WHERE item_class=:class AND item_id=:id');
|
||||
$oSet = new DBObjectSet($oFilter, [], ['class' => $this->GetTargetClass(), 'id' => $sTargetKey]);
|
||||
while ($oTemporaryObjectDescriptor = $oSet->Fetch()) {
|
||||
$oTemporaryObjectDescriptor->Set('host_class', get_class($oHostObject));
|
||||
$oTemporaryObjectDescriptor->Set('host_id', $oHostObject->GetKey());
|
||||
$oTemporaryObjectDescriptor->Set('host_att_code', $this->GetCode());
|
||||
$oTemporaryObjectDescriptor->DBUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public function GetMaximumComboLength()
|
||||
{
|
||||
return $this->GetOptional('max_combo_length', MetaModel::GetConfig()->Get('max_combo_length'));
|
||||
@@ -7267,6 +7304,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
|
||||
public function MakeFormField(DBObject $oObject, $oFormField = null)
|
||||
{
|
||||
/** @var \Combodo\iTop\Form\Field\Field $oFormField */
|
||||
if ($oFormField === null) {
|
||||
// Later : We should check $this->Get('display_style') and create a Radio / Select / ... regarding its value
|
||||
$sFormFieldClass = static::GetFormFieldClass();
|
||||
@@ -7297,19 +7335,12 @@ class AttributeExternalKey extends AttributeDBFieldVoid
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
$oSearch = DBSearch::FromOQL($this->GetValuesDef()->GetFilterExpression());
|
||||
$oSearch->SetInternalParams(array('this' => $oObject));
|
||||
$oFormField->SetSearch($oSearch);
|
||||
}
|
||||
|
||||
// If ExtKey is mandatory, we add a validator to ensure that the value 0 is not selected
|
||||
if ($oObject->GetAttributeFlags($this->GetCode()) & OPT_ATT_MANDATORY)
|
||||
{
|
||||
$oFormField->AddValidator(new NotEmptyExtKeyValidator());
|
||||
}
|
||||
|
||||
parent::MakeFormField($oObject, $oFormField);
|
||||
|
||||
return $oFormField;
|
||||
@@ -7935,6 +7966,17 @@ class AttributeExternalField extends AttributeDefinition
|
||||
return $oExtAttDef->MakeRealValue($proposedValue, $oHostObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.1.0 N°6271 Delegate to remote attribute to ensure cascading computed values
|
||||
*/
|
||||
public function GetSQLValues($value)
|
||||
{
|
||||
$oExtAttDef = $this->GetExtAttDef();
|
||||
|
||||
return $oExtAttDef->GetSQLValues($value);
|
||||
}
|
||||
|
||||
public function ScalarToSQL($value)
|
||||
{
|
||||
// This one could be used in case of filtering only
|
||||
@@ -8343,7 +8385,7 @@ class AttributeBlob extends AttributeDefinition
|
||||
$aValues[$this->GetCode().'_data'] = '';
|
||||
$aValues[$this->GetCode().'_mimetype'] = '';
|
||||
$aValues[$this->GetCode().'_filename'] = '';
|
||||
$aValues[$this->GetCode().'_downloads_count'] = \ormDocument::DEFAULT_DOWNLOADS_COUNT;
|
||||
$aValues[$this->GetCode().'_downloads_count'] = ormDocument::DEFAULT_DOWNLOADS_COUNT;
|
||||
}
|
||||
|
||||
return $aValues;
|
||||
@@ -8533,6 +8575,22 @@ class AttributeBlob extends AttributeDefinition
|
||||
return utils::IsNotNullOrEmptyString($proposedValue->GetData()) && utils::IsNotNullOrEmptyString($proposedValue->GetFileName());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @param \ormDocument $original
|
||||
* @param \ormDocument $value
|
||||
* @since N°6502
|
||||
*/
|
||||
public function RecordAttChange(DBObject $oObject, $original, $value): void
|
||||
{
|
||||
// N°6502 Don't record history if only the download count has changed
|
||||
if ($original->EqualsExceptDownloadsCount($value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
parent::RecordAttChange($oObject, $original, $value);
|
||||
}
|
||||
|
||||
protected function GetChangeRecordAdditionalData(CMDBChangeOp $oMyChangeOp, DBObject $oObject, $original, $value): void
|
||||
{
|
||||
if (is_null($original)) {
|
||||
@@ -8692,7 +8750,7 @@ class AttributeImage extends AttributeBlob
|
||||
return 'data:'.$value->GetMimeType().';base64,'.base64_encode($value->GetData());
|
||||
}
|
||||
|
||||
return $value->GetDownloadURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode());
|
||||
return $value->GetDisplayURL(get_class($oHostObject), $oHostObject->GetKey(), $this->GetCode());
|
||||
}
|
||||
|
||||
public static function GetFormFieldClass()
|
||||
@@ -8719,8 +8777,11 @@ class AttributeImage extends AttributeBlob
|
||||
}
|
||||
else
|
||||
{
|
||||
$oFormField->SetDownloadUrl($this->Get('default_image'));
|
||||
$oFormField->SetDisplayUrl($this->Get('default_image'));
|
||||
$oDefaultImage = $this->Get('default_image');
|
||||
if (is_object($oDefaultImage) && !$oDefaultImage->IsEmpty()) {
|
||||
$oFormField->SetDownloadUrl($oDefaultImage);
|
||||
$oFormField->SetDisplayUrl($oDefaultImage);
|
||||
}
|
||||
}
|
||||
|
||||
return $oFormField;
|
||||
@@ -11299,6 +11360,9 @@ class AttributeClassAttCodeSet extends AttributeSet
|
||||
}
|
||||
$aAllowedAttributes[$sAttCode] = $sLabel;
|
||||
}
|
||||
// N°6460 Always sort on the labels, not on the datamodel definition order
|
||||
natcasesort($aAllowedAttributes);
|
||||
|
||||
return $aAllowedAttributes;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ MetaModel::IncludeModule('synchro/synchrodatasource.class.inc.php');
|
||||
MetaModel::IncludeModule('core/backgroundtask.class.inc.php');
|
||||
MetaModel::IncludeModule('core/inlineimage.class.inc.php');
|
||||
MetaModel::IncludeModule('core/counter.class.inc.php');
|
||||
MetaModel::IncludeModule('core/TemporaryObjectDescriptor.php');
|
||||
|
||||
MetaModel::IncludeModule('webservices/webservices.basic.php');
|
||||
|
||||
|
||||
@@ -37,6 +37,14 @@ abstract class CellChangeSpec
|
||||
return $this->m_proposedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
*/
|
||||
public function SetDisplayableValue(string $sDisplayableValue)
|
||||
{
|
||||
$this->m_proposedValue = $sDisplayableValue;
|
||||
}
|
||||
|
||||
public function GetOql()
|
||||
{
|
||||
return $this->m_sOql;
|
||||
@@ -136,6 +144,12 @@ class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
/** @var string|null $m_sTargetClass */
|
||||
private $m_sTargetClass;
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @var string $sAllowedValuesSearch
|
||||
*/
|
||||
private $sAllowedValuesSearch;
|
||||
|
||||
/**
|
||||
* CellStatus_SearchIssue constructor.
|
||||
* @since 3.1.0 N°5305
|
||||
@@ -144,13 +158,15 @@ class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
* @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
|
||||
* @param string|null $sAllowedValuesSearch : used to search all allowed values
|
||||
*/
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null)
|
||||
public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null, string $sAllowedValuesSearch=null)
|
||||
{
|
||||
parent::__construct(null, null, $sReason);
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
$this->m_sAllowedValues = $sAllowedValues;
|
||||
$this->m_sTargetClass = $sClass;
|
||||
$this->sAllowedValuesSearch = $sAllowedValuesSearch;
|
||||
}
|
||||
|
||||
public function GetDisplayableValue()
|
||||
@@ -182,6 +198,17 @@ class CellStatus_SearchIssue extends CellStatus_Issue
|
||||
rawurlencode($this->sSerializedSearch)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°5305
|
||||
* @return null|string
|
||||
*/
|
||||
public function GetAllowedValuesLinkUrl(): ?string
|
||||
{
|
||||
return sprintf("UI.php?operation=search&filter=%s",
|
||||
rawurlencode($this->sAllowedValuesSearch)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CellStatus_NullIssue extends CellStatus_Issue
|
||||
@@ -745,6 +772,7 @@ class BulkChange
|
||||
$oDbSearchWithoutAnyCondition->AllowAllData(false);
|
||||
$oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||
$iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count();
|
||||
$sAllowedValuesOql = $oDbSearchWithoutAnyCondition->serialize();
|
||||
|
||||
if ($iCurrentUserRightsObjectCount === 0){
|
||||
// No objects visible by current user
|
||||
@@ -785,7 +813,7 @@ class BulkChange
|
||||
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);
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues, $sAllowedValuesOql);
|
||||
}
|
||||
|
||||
// No match. This is not linked to any right issue
|
||||
@@ -796,7 +824,7 @@ class BulkChange
|
||||
}
|
||||
$value =implode(" ", $aCurrentValueFields);
|
||||
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value);
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues, $sAllowedValuesOql);
|
||||
}
|
||||
|
||||
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
|
||||
|
||||
@@ -469,7 +469,10 @@ abstract class CMDBObject extends DBObject
|
||||
*/
|
||||
public function DBDelete(&$oDeletionPlan = null)
|
||||
{
|
||||
return $this->DBDeleteTracked_Internal($oDeletionPlan);
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
$oDeletionPlan = $this->DBDeleteTracked_Internal($oDeletionPlan);
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -519,15 +522,6 @@ abstract class CMDBObject extends DBObject
|
||||
utils::PopArchiveMode();
|
||||
}
|
||||
}
|
||||
|
||||
public function DBUpdate()
|
||||
{
|
||||
if (count($this->ListChanges()) === 0) {
|
||||
$this->InitPreviousValuesForUpdatedAttributes();
|
||||
return $this->GetKey();
|
||||
}
|
||||
return parent::DBUpdate(); // TODO: Change the autogenerated stub
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'log_purge.max_keep_days' => [
|
||||
'log_purge.max_keep_days' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Optional purge number of days to keep logs.',
|
||||
'default' => 365,
|
||||
@@ -145,7 +145,7 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_events' => [
|
||||
'event_service.debug.filter_events' => [
|
||||
'type' => 'array',
|
||||
'description' => 'List of events name to filter Event Service debug messages',
|
||||
'default' => [],
|
||||
@@ -153,7 +153,7 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'event_service.debug.filter_sources' => [
|
||||
'event_service.debug.filter_sources' => [
|
||||
'type' => 'array',
|
||||
'description' => 'List of event sources to filter Event Service debug messages',
|
||||
'default' => '',
|
||||
@@ -161,6 +161,38 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'temporary_object.force_creation' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'If true, all the objects created by the external key are temporary',
|
||||
'default' => false,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'temporary_object.lifetime' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Seconds for temporary objects created',
|
||||
'default' => 300,
|
||||
'value' => 300,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'temporary_object.watchdog_interval' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Seconds between watchdog signals',
|
||||
'default' => 60,
|
||||
'value' => false,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'temporary_object.garbage_interval' => [
|
||||
'type' => 'integer',
|
||||
'description' => 'Seconds between garbage collections',
|
||||
'default' => 60,
|
||||
'value' => false,
|
||||
'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")',
|
||||
@@ -185,7 +217,7 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'db_host' => [
|
||||
'db_host' => [
|
||||
'type' => 'string',
|
||||
'default' => null,
|
||||
'value' => '',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
// iTop is free software; you can redistribute it and/or modify
|
||||
// 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.
|
||||
@@ -58,6 +58,16 @@ class ContextTag
|
||||
public const TAG_SETUP = 'Setup';
|
||||
public const TAG_SYNCHRO = 'Synchro';
|
||||
public const TAG_REST = 'REST/JSON';
|
||||
|
||||
/**
|
||||
* @since 3.1.0 N°6047
|
||||
*/
|
||||
public const TAG_IMPORT = 'Import';
|
||||
/**
|
||||
* @since 3.1.0 N°6047
|
||||
*/
|
||||
public const TAG_EXPORT = 'Export';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0 N°3200
|
||||
@@ -75,7 +85,7 @@ class ContextTag
|
||||
{
|
||||
static::$aStack[] = $sTag;
|
||||
}
|
||||
|
||||
|
||||
public static function AddContext($sTag)
|
||||
{
|
||||
static::$aStack[] = $sTag;
|
||||
|
||||
@@ -137,7 +137,7 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$aSep['other'] = Dict::S('UI:CSVImport:SeparatorOther').' <input type="text" size="3" name="other-separator" value="'.utils::EscapeHtml($sOtherSeparator).'"/>';
|
||||
|
||||
foreach ($aSep as $sVal => $sLabel) {
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", utils::EscapeHtml($sVal), $sLabel, "radio");
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "separator", $sVal, $sLabel, "radio");
|
||||
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
|
||||
$oRadio->SetBeforeInput(false);
|
||||
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
|
||||
@@ -163,8 +163,8 @@ class CSVBulkExport extends TabularBulkExport
|
||||
$aQualifiers['other'] = Dict::S('UI:CSVImport:QualifierOther').' <input type="text" size="3" name="other-text-qualifier" value="'.utils::EscapeHtml($sOtherQualifier).'"/>';
|
||||
|
||||
foreach ($aQualifiers as $sVal => $sLabel) {
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", utils::EscapeHtml($sVal), $sLabel, "radio");
|
||||
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawSeparator));
|
||||
$oRadio = InputUIBlockFactory::MakeForInputWithLabel($sLabel, "text-qualifier", $sVal, $sLabel, "radio");
|
||||
$oRadio->GetInput()->SetIsChecked(($sVal == $sRawQualifier));
|
||||
$oRadio->SetBeforeInput(false);
|
||||
$oRadio->GetInput()->AddCSSClass('ibo-input--label-right');
|
||||
$oRadio->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
<properties>
|
||||
<category>addon/userrights,grant_by_profile</category>
|
||||
<is_link>1</is_link>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="userid" xsi:type="AttributeExternalKey">
|
||||
@@ -488,6 +489,12 @@
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</property>
|
||||
<property id="create_temporary_object">
|
||||
<php_param>create_temporary_object</php_param>
|
||||
<mandatory>false</mandatory>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</property>
|
||||
<property id="on_target_delete">
|
||||
<php_param>on_target_delete</php_param>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
use Combodo\iTop\Core\MetaModel\FriendlyNameType;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Service\TemporaryObjects\TemporaryObjectManager;
|
||||
|
||||
/**
|
||||
* All objects to be displayed in the application (either as a list or as details)
|
||||
@@ -194,24 +195,33 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
protected static array $m_aCrudStack = [];
|
||||
|
||||
/** @var array Context for update insert operations */
|
||||
private array $aContext = [];
|
||||
|
||||
// Protect DBUpdate against infinite loop
|
||||
protected $iUpdateLoopCount;
|
||||
|
||||
const MAX_UPDATE_LOOP_COUNT = 10;
|
||||
|
||||
/**
|
||||
* DBObject constructor.
|
||||
*
|
||||
* You should preferably use MetaModel::NewObject() instead of this constructor.
|
||||
* The whole collection of parameters is [*optional*] please refer to DBObjectSet::FromRow()
|
||||
*
|
||||
* @internal The availability of this method is not guaranteed in the long term, you should preferably use MetaModel::NewObject().
|
||||
* @see MetaModel::NewObject()
|
||||
*
|
||||
* @param null|array $aRow If given : DBObjectSet::FromRow() will be used to fetch the object
|
||||
* @param string $sClassAlias
|
||||
* @param null|array $aAttToLoad
|
||||
* @param null|array $aExtendedDataSpec
|
||||
*
|
||||
* @throws CoreException
|
||||
*/
|
||||
* DBObject constructor.
|
||||
*
|
||||
* You should preferably use MetaModel::NewObject() instead of this constructor.
|
||||
* The whole collection of parameters is [*optional*] please refer to DBObjectSet::FromRow()
|
||||
*
|
||||
* @internal The availability of this method is not guaranteed in the long term, you should preferably use MetaModel::NewObject().
|
||||
* @see MetaModel::NewObject()
|
||||
*
|
||||
* @param null|array $aRow If given : DBObjectSet::FromRow() will be used to fetch the object
|
||||
* @param string $sClassAlias
|
||||
* @param null|array $aAttToLoad
|
||||
* @param null|array $aExtendedDataSpec
|
||||
*
|
||||
* @throws CoreException
|
||||
*/
|
||||
public function __construct($aRow = null, $sClassAlias = '', $aAttToLoad = null, $aExtendedDataSpec = null)
|
||||
{
|
||||
$this->iUpdateLoopCount = 0;
|
||||
if (!empty($aRow))
|
||||
{
|
||||
$this->FromRow($aRow, $sClassAlias, $aAttToLoad, $aExtendedDataSpec);
|
||||
@@ -601,6 +611,14 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function Set($sAttCode, $value)
|
||||
{
|
||||
if (!utils::StartsWith(get_class($this), 'CMDBChange') && $this->GetKey() > 0) {
|
||||
if (is_object($value) || is_array($value)) {
|
||||
$this->LogCRUDEnter(__METHOD__, "$sAttCode => object or array");
|
||||
} else {
|
||||
$this->LogCRUDEnter(__METHOD__, "$sAttCode => ".print_r($value, true));
|
||||
}
|
||||
}
|
||||
|
||||
$sMessage = $this->IsReadOnly();
|
||||
if ($sMessage !== false) {
|
||||
throw new CoreException($sMessage);
|
||||
@@ -622,6 +640,9 @@ abstract class DBObject implements iDisplay
|
||||
// First time Set is called... ensure that the object gets fully loaded
|
||||
// Otherwise we would lose the values on a further Reload
|
||||
// + consistency does not make sense !
|
||||
$sFullyLoaded = $this->m_bFullyLoaded ? 'true' : 'false';
|
||||
$sDirty = $this->m_bDirty ? 'true' : 'false';
|
||||
$this->LogCRUDDebug(__METHOD__, "IsInDB: $this->m_bIsInDB, FullyLoaded: $sFullyLoaded, Dirty: $sDirty");
|
||||
$this->Reload();
|
||||
}
|
||||
|
||||
@@ -1413,7 +1434,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
$sPreview = '';
|
||||
if(SummaryCardService::IsAllowedForClass($sObjClass) && $bIgnorePreview === false){
|
||||
$sPreview = SummaryCardService::GetHyperlinkMarkup($sObjClass, $sObjKey);
|
||||
$sPreview = SummaryCardService::GetHyperlinkMarkup($sObjClass, $sObjKey);
|
||||
}
|
||||
$sRet = "<span class=\"object-ref $sSpanClass\" $sPreview title=\"$sHint\">$sHLink</span>";
|
||||
return $sRet;
|
||||
@@ -1503,7 +1524,7 @@ abstract class DBObject implements iDisplay
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return int|null
|
||||
* @return string|null
|
||||
*/
|
||||
public function GetKey()
|
||||
{
|
||||
@@ -1658,7 +1679,7 @@ abstract class DBObject implements iDisplay
|
||||
*
|
||||
* @return string
|
||||
* @throws \CoreException
|
||||
* @since 3.0.0 N°4106 Method should be overloaded anymore for performances reasons. It will be set final in 3.1.0 (N°4107)
|
||||
* @since 3.0.0 N°4106 Method should not be overloaded anymore for performances reasons. It will be set final in 3.1.0 (N°4107)
|
||||
* @since 3.0.0 N°580 New $sType parameter
|
||||
*
|
||||
*/
|
||||
@@ -2820,7 +2841,7 @@ abstract class DBObject implements iDisplay
|
||||
*
|
||||
* @param string $sTableClass
|
||||
*
|
||||
* @return bool|int false if nothing to persist (no change), new key value otherwise
|
||||
* @return bool|string false if nothing to persist (no change), new key value otherwise
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
@@ -2845,11 +2866,10 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
$aHierarchicalKeys = array();
|
||||
|
||||
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
|
||||
{
|
||||
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) {
|
||||
// Skip this attribute if not defined in this table
|
||||
if (!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode) && !$oAttDef->CopyOnAllTables())
|
||||
{
|
||||
if ((!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode) && !$oAttDef->CopyOnAllTables())
|
||||
|| $oAttDef->IsExternalField()) {
|
||||
continue;
|
||||
}
|
||||
$aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
|
||||
@@ -2903,7 +2923,7 @@ abstract class DBObject implements iDisplay
|
||||
if (empty($this->m_iKey))
|
||||
{
|
||||
// Take the autonumber
|
||||
$this->m_iKey = $iNewKey;
|
||||
$this->m_iKey = "$iNewKey";
|
||||
}
|
||||
return $this->m_iKey;
|
||||
}
|
||||
@@ -2926,6 +2946,8 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function DBInsert()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
|
||||
$this->DBInsertNoReload();
|
||||
|
||||
foreach ($this->m_aLoadedAtt as $sAttCode => $bLoaded) {
|
||||
@@ -2939,6 +2961,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
@@ -2970,10 +2993,13 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
$aHierarchicalKeys = array();
|
||||
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
|
||||
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode => $oAttDef)
|
||||
{
|
||||
// Skip this attribute if not defined in this table
|
||||
if (!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode)) continue;
|
||||
if ((!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode))
|
||||
|| $oAttDef->IsExternalField()) {
|
||||
continue;
|
||||
};
|
||||
// Skip link set that can still be undefined though the object is 100% loaded
|
||||
if ($oAttDef->IsLinkSet()) continue;
|
||||
|
||||
@@ -3055,7 +3081,7 @@ abstract class DBObject implements iDisplay
|
||||
* @api
|
||||
* @see DBWrite
|
||||
*
|
||||
* @return int|null inserted object key
|
||||
* @return string|null inserted object key
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
@@ -3072,13 +3098,12 @@ abstract class DBObject implements iDisplay
|
||||
$this->AddCurrentObjectInCrudStack('INSERT');
|
||||
|
||||
try {
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
$sErrorMessage = "Cannot Insert object of class '$sClass' because of an ongoing maintenance: the database is in ReadOnly mode";
|
||||
if (MetaModel::DBIsReadOnly()) {
|
||||
$sErrorMessage = "Cannot Insert object of class '$sClass' because of an ongoing maintenance: the database is in ReadOnly mode";
|
||||
|
||||
IssueLog::Error("$sErrorMessage\n".MyHelpers::get_callstack_text(1));
|
||||
throw new CoreException("$sErrorMessage (see the log for more information)");
|
||||
}
|
||||
IssueLog::Error("$sErrorMessage\n".MyHelpers::get_callstack_text(1));
|
||||
throw new CoreException("$sErrorMessage (see the log for more information)");
|
||||
}
|
||||
|
||||
if ($this->m_bIsInDB) {
|
||||
throw new CoreException('The object already exists into the Database, you may want to use the clone function');
|
||||
@@ -3112,7 +3137,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
$this->ComputeStopWatchesDeadline(true);
|
||||
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled) {
|
||||
@@ -3151,6 +3176,8 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$this->HandleTemporaryDescriptor();
|
||||
|
||||
// Write object creation history within the transaction
|
||||
$this->RecordObjCreation();
|
||||
|
||||
@@ -3192,32 +3219,13 @@ abstract class DBObject implements iDisplay
|
||||
MetaModel::StartReentranceProtection($this);
|
||||
|
||||
try {
|
||||
$this->FireEventAfterWrite([], true);
|
||||
$this->AfterInsert();
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN (:class_list)'), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectCreate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// - TriggerOnObjectMention
|
||||
$this->ActivateOnMentionTriggers(true);
|
||||
$this->PostInsertActions();
|
||||
}
|
||||
finally {
|
||||
MetaModel::StopReentranceProtection($this);
|
||||
}
|
||||
|
||||
if ($this->IsModified()) {
|
||||
if ((count($this->ListChanges()) !== 0)) {
|
||||
$this->DBUpdate();
|
||||
}
|
||||
}
|
||||
@@ -3228,6 +3236,38 @@ abstract class DBObject implements iDisplay
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function PostInsertActions(): void
|
||||
{
|
||||
$this->FireEventAfterWrite([], true);
|
||||
$this->AfterInsert();
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectCreate AS t WHERE t.target_class IN (:class_list)'), array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectCreate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs('this'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// - TriggerOnObjectMention
|
||||
$this->ActivateOnMentionTriggers(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the current object into the database
|
||||
*
|
||||
@@ -3258,8 +3298,8 @@ abstract class DBObject implements iDisplay
|
||||
* This function is automatically called after cloning an object with the "clone" PHP language construct
|
||||
* The purpose of this method is to reset the appropriate attributes of the object in
|
||||
* order to make sure that the newly cloned object is really distinct from its clone
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
@@ -3268,7 +3308,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_iKey = self::GetNextTempId(get_class($this));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update an object in DB
|
||||
*
|
||||
@@ -3283,210 +3322,183 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function DBUpdate()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
if (!$this->m_bIsInDB)
|
||||
{
|
||||
throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
|
||||
}
|
||||
// Protect against reentrance (e.g. cascading the update of ticket logs)
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
if (!MetaModel::StartReentranceProtection($this)) {
|
||||
IssueLog::Debug("CRUD: DBUpdate $sClass::$sKey Rejected (reentrance)", LogChannels::DM_CRUD);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->AddCurrentObjectInCrudStack('UPDATE');
|
||||
|
||||
if (!MetaModel::StartReentranceProtection($this)) {
|
||||
$this->RemoveCurrentObjectInCrudStack();
|
||||
$this->LogCRUDExit(__METHOD__, 'Rejected (reentrance)');
|
||||
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$this->DoComputeValues();
|
||||
$this->ComputeStopWatchesDeadline(false);
|
||||
$this->OnUpdate();
|
||||
// Protect against infinite loop
|
||||
$this->iUpdateLoopCount++;
|
||||
|
||||
$this->FireEventBeforeWrite();
|
||||
|
||||
// Freeze the changes at this point
|
||||
$this->InitPreviousValuesForUpdatedAttributes();
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0)
|
||||
{
|
||||
// Attempting to update an unchanged object
|
||||
IssueLog::Debug("CRUD: DBUpdate $sClass::$sKey Aborted (no change)", LogChannels::DM_CRUD);
|
||||
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
list($bRes, $aIssues) = $this->CheckToWrite(false);
|
||||
if (!$bRes)
|
||||
{
|
||||
throw new CoreCannotSaveObjectException(['issues' => $aIssues, 'class' => $sClass, 'id' => $this->GetKey()]);
|
||||
}
|
||||
|
||||
// Save the original values (will be reset to the new values when the object get written to the DB)
|
||||
$aOriginalValues = $this->m_aOrigValues;
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
|
||||
$aHierarchicalKeys = array();
|
||||
$aDBChanges = array();
|
||||
foreach ($aChanges as $sAttCode => $currentValue)
|
||||
{
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsBasedOnDBColumns())
|
||||
{
|
||||
$aDBChanges[$sAttCode] = $currentValue;
|
||||
}
|
||||
if ($oAttDef->IsHierarchicalKey())
|
||||
{
|
||||
$aHierarchicalKeys[$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
|
||||
while ($iTransactionRetry > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
if (!MetaModel::DBIsReadOnly())
|
||||
{
|
||||
// Update the left & right indexes for each hierarchical key
|
||||
foreach ($aHierarchicalKeys as $sAttCode => $oAttDef)
|
||||
{
|
||||
$sTable = MetaModel::DBGetTable($sClass, $sAttCode);
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
$iMyLeft = $aRes[0]['left'];
|
||||
$iMyRight = $aRes[0]['right'];
|
||||
$iDelta = $iMyRight - $iMyLeft + 1;
|
||||
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
|
||||
|
||||
if ($aDBChanges[$sAttCode] == 0) {
|
||||
// No new parent, insert completely at the right of the tree
|
||||
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
if (count($aRes) == 0)
|
||||
{
|
||||
$iNewLeft = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
$iNewLeft = $aRes[0]['max'] + 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Insert at the right of the specified parent
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aDBChanges[$sAttCode]);
|
||||
$iNewLeft = CMDBSource::QueryToScalar($sSQL);
|
||||
}
|
||||
|
||||
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
|
||||
|
||||
$aHKChanges = [];
|
||||
$aHKChanges[$sAttCode] = $aDBChanges[$sAttCode];
|
||||
$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
|
||||
$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
|
||||
$aDBChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
|
||||
}
|
||||
|
||||
// Update scalar attributes
|
||||
if (count($aDBChanges) != 0)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oFilter->AddCondition('id', $this->m_iKey, '=');
|
||||
$oFilter->AllowAllData();
|
||||
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
}
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
if (count($aChanges) != 0) {
|
||||
$this->RecordAttChanges($aChanges, $aOriginalValues);
|
||||
}
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (MySQLException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e))
|
||||
{
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0)
|
||||
{
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
IssueLog::Error("Update Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
$aErrors = array($e->getMessage());
|
||||
throw new CoreCannotSaveObjectException(['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors], $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled)
|
||||
{
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
$aErrors = [$e->getMessage()];
|
||||
throw new CoreCannotSaveObjectException(['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors,]);
|
||||
}
|
||||
}
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
$bModifiedByUpdateDone = false;
|
||||
try {
|
||||
$this->FireEventAfterWrite($aChanges, false);
|
||||
$this->AfterUpdate();
|
||||
// Save the status as it is reset just after...
|
||||
$bModifiedByUpdateDone = $this->IsModified();
|
||||
$this->DoComputeValues();
|
||||
$this->ComputeStopWatchesDeadline(false);
|
||||
$this->OnUpdate();
|
||||
|
||||
$this->FireEventBeforeWrite();
|
||||
|
||||
// Freeze the changes at this point
|
||||
$this->InitPreviousValuesForUpdatedAttributes();
|
||||
$aChanges = $this->ListChanges();
|
||||
if (count($aChanges) == 0) {
|
||||
// Attempting to update an unchanged object
|
||||
$this->LogCRUDExit(__METHOD__, 'Aborted (no change)');
|
||||
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
list($bRes, $aIssues) = $this->CheckToWrite(false);
|
||||
if (!$bRes) {
|
||||
throw new CoreCannotSaveObjectException(['issues' => $aIssues, 'class' => $sClass, 'id' => $this->GetKey()]);
|
||||
}
|
||||
|
||||
// Save the original values (will be reset to the new values when the object get written to the DB)
|
||||
$aOriginalValues = $this->m_aOrigValues;
|
||||
|
||||
// Activate any existing trigger
|
||||
$sClass = get_class($this);
|
||||
|
||||
$aHierarchicalKeys = array();
|
||||
$aDBChanges = array();
|
||||
foreach ($aChanges as $sAttCode => $currentValue) {
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
if ($oAttDef->IsBasedOnDBColumns()) {
|
||||
$aDBChanges[$sAttCode] = $currentValue;
|
||||
}
|
||||
if ($oAttDef->IsHierarchicalKey()) {
|
||||
$aHierarchicalKeys[$sAttCode] = $oAttDef;
|
||||
}
|
||||
}
|
||||
|
||||
$iTransactionRetry = 1;
|
||||
$bIsTransactionEnabled = MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
if ($bIsTransactionEnabled) {
|
||||
// TODO Deep clone this object before the transaction (to use it in case of rollback)
|
||||
// $iTransactionRetryCount = MetaModel::GetConfig()->Get('db_core_transactions_retry_count');
|
||||
$iTransactionRetryCount = 1;
|
||||
$iIsTransactionRetryDelay = MetaModel::GetConfig()->Get('db_core_transactions_retry_delay_ms');
|
||||
$iTransactionRetry = $iTransactionRetryCount;
|
||||
}
|
||||
|
||||
while ($iTransactionRetry > 0) {
|
||||
try {
|
||||
$iTransactionRetry--;
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('START TRANSACTION');
|
||||
}
|
||||
if (!MetaModel::DBIsReadOnly()) {
|
||||
// Update the left & right indexes for each hierarchical key
|
||||
foreach ($aHierarchicalKeys as $sAttCode => $oAttDef) {
|
||||
$sTable = MetaModel::DBGetTable($sClass, $sAttCode);
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` AS `right`, `".$oAttDef->GetSQLLeft()."` AS `left` FROM `$sTable` WHERE id=".$this->GetKey();
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
$iMyLeft = $aRes[0]['left'];
|
||||
$iMyRight = $aRes[0]['right'];
|
||||
$iDelta = $iMyRight - $iMyLeft + 1;
|
||||
MetaModel::HKTemporaryCutBranch($iMyLeft, $iMyRight, $oAttDef, $sTable);
|
||||
|
||||
if ($aDBChanges[$sAttCode] == 0) {
|
||||
// No new parent, insert completely at the right of the tree
|
||||
$sSQL = "SELECT max(`".$oAttDef->GetSQLRight()."`) AS max FROM `$sTable`";
|
||||
$aRes = CMDBSource::QueryToArray($sSQL);
|
||||
if (count($aRes) == 0) {
|
||||
$iNewLeft = 1;
|
||||
} else {
|
||||
$iNewLeft = $aRes[0]['max'] + 1;
|
||||
}
|
||||
} else {
|
||||
// Insert at the right of the specified parent
|
||||
$sSQL = "SELECT `".$oAttDef->GetSQLRight()."` FROM `$sTable` WHERE id=".((int)$aDBChanges[$sAttCode]);
|
||||
$iNewLeft = CMDBSource::QueryToScalar($sSQL);
|
||||
}
|
||||
|
||||
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
|
||||
|
||||
$aHKChanges = [];
|
||||
$aHKChanges[$sAttCode] = $aDBChanges[$sAttCode];
|
||||
$aHKChanges[$oAttDef->GetSQLLeft()] = $iNewLeft;
|
||||
$aHKChanges[$oAttDef->GetSQLRight()] = $iNewLeft + $iDelta - 1;
|
||||
$aDBChanges[$sAttCode] = $aHKChanges; // the 3 values will be stored by MakeUpdateQuery below
|
||||
}
|
||||
|
||||
// Update scalar attributes
|
||||
if (count($aDBChanges) != 0) {
|
||||
$oFilter = new DBObjectSearch($sClass);
|
||||
$oFilter->AddCondition('id', $this->m_iKey, '=');
|
||||
$oFilter->AllowAllData();
|
||||
|
||||
$sSQL = $oFilter->MakeUpdateQuery($aDBChanges);
|
||||
CMDBSource::Query($sSQL);
|
||||
}
|
||||
}
|
||||
$this->DBWriteLinks();
|
||||
$this->WriteExternalAttributes();
|
||||
|
||||
$this->HandleTemporaryDescriptor();
|
||||
|
||||
if (count($aChanges) != 0) {
|
||||
$this->RecordAttChanges($aChanges, $aOriginalValues);
|
||||
}
|
||||
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('COMMIT');
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (MySQLException $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
if (!CMDBSource::IsInsideTransaction() && CMDBSource::IsDeadlockException($e)) {
|
||||
// Deadlock found when trying to get lock; try restarting transaction (only in main transaction)
|
||||
if ($iTransactionRetry > 0) {
|
||||
// wait and retry
|
||||
IssueLog::Error("Update TRANSACTION Retrying...");
|
||||
usleep(random_int(1, 5) * 1000 * $iIsTransactionRetryDelay * ($iTransactionRetryCount - $iTransactionRetry));
|
||||
continue;
|
||||
} else {
|
||||
IssueLog::Error("Update Deadlock TRANSACTION prevention failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
$aErrors = array($e->getMessage());
|
||||
throw new CoreCannotSaveObjectException(['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors], $e);
|
||||
}
|
||||
catch (CoreCannotSaveObjectException $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
catch (Exception $e) {
|
||||
IssueLog::Error($e->getMessage());
|
||||
if ($bIsTransactionEnabled) {
|
||||
CMDBSource::Query('ROLLBACK');
|
||||
}
|
||||
$aErrors = [$e->getMessage()];
|
||||
throw new CoreCannotSaveObjectException(['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors,]);
|
||||
}
|
||||
}
|
||||
|
||||
// following lines are resetting changes (so after this {@see DBObject::ListChanges()} won't return changes anymore)
|
||||
// new values are already in the object (call {@see DBObject::Get()} to get them)
|
||||
// call {@see DBObject::ListPreviousValuesForUpdatedAttributes()} to get changed fields and previous values
|
||||
$this->m_bDirty = false;
|
||||
$this->m_aTouchedAtt = array();
|
||||
$this->m_aModifiedAtt = array();
|
||||
// Reset original values although the object has not been reloaded
|
||||
foreach ($this->m_aLoadedAtt as $sAttCode => $bLoaded) {
|
||||
if ($bLoaded) {
|
||||
@@ -3502,46 +3514,74 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
// - TriggerOnObjectUpdate
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)'),
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectUpdate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs());
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
try {
|
||||
$this->PostUpdateActions($aChanges, $sClass);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$this->LogCRUDExit(__METHOD__, 'Error: '.$e->getMessage());
|
||||
$aErrors = [$e->getMessage()];
|
||||
throw new CoreException($e->getMessage(), ['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors]);
|
||||
}
|
||||
|
||||
// Activate any existing trigger
|
||||
// - TriggerOnObjectMention
|
||||
// Forgotten by the fix of N°3245
|
||||
$this->ActivateOnMentionTriggers(false, $aChanges);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
$aErrors = [$e->getMessage()];
|
||||
throw new CoreException($e->getMessage(), ['id' => $this->GetKey(), 'class' => $sClass, 'issues' => $aErrors]);
|
||||
finally {
|
||||
MetaModel::StopReentranceProtection($this);
|
||||
}
|
||||
|
||||
if (count($this->ListChanges()) !== 0) {
|
||||
// Controlled reentrance
|
||||
if ($this->iUpdateLoopCount >= self::MAX_UPDATE_LOOP_COUNT) {
|
||||
$this->LogCRUDExit(__METHOD__, 'Max update loop reached');
|
||||
return false;
|
||||
}
|
||||
$this->DBUpdate();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
MetaModel::StopReentranceProtection($this);
|
||||
finally {
|
||||
$this->RemoveCurrentObjectInCrudStack();
|
||||
$this->iUpdateLoopCount--;
|
||||
}
|
||||
|
||||
if ($this->IsModified() || $bModifiedByUpdateDone) {
|
||||
// Controlled reentrance
|
||||
$this->DBUpdate();
|
||||
}
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aChanges
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function PostUpdateActions(array $aChanges): void
|
||||
{
|
||||
$this->FireEventAfterWrite($aChanges, false);
|
||||
$this->AfterUpdate();
|
||||
|
||||
// - TriggerOnObjectUpdate
|
||||
$aParams = array('class_list' => MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL));
|
||||
$oSet = new DBObjectSet(DBObjectSearch::FromOQL('SELECT TriggerOnObjectUpdate AS t WHERE t.target_class IN (:class_list)'),
|
||||
array(), $aParams);
|
||||
while ($oTrigger = $oSet->Fetch()) {
|
||||
/** @var \TriggerOnObjectUpdate $oTrigger */
|
||||
try {
|
||||
$oTrigger->DoActivate($this->ToArgs());
|
||||
}
|
||||
catch (Exception $e) {
|
||||
$oTrigger->LogException($e, $this);
|
||||
utils::EnrichRaisedException($oTrigger, $e);
|
||||
}
|
||||
}
|
||||
|
||||
// Activate any existing trigger
|
||||
// - TriggerOnObjectMention
|
||||
// Forgotten by the fix of N°3245
|
||||
$this->ActivateOnMentionTriggers(false, $aChanges);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Increment attribute with specified value.
|
||||
@@ -3742,6 +3782,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
$this->LogCRUDExit(__METHOD__, 'DB is read-only');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3847,6 +3888,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->LogCRUDError(__METHOD__, ' Error: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
@@ -3885,6 +3927,8 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
public function DBDelete(&$oDeletionPlan = null)
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
|
||||
static $iLoopTimeLimit = null;
|
||||
if ($iLoopTimeLimit == null)
|
||||
{
|
||||
@@ -3900,6 +3944,7 @@ abstract class DBObject implements iDisplay
|
||||
if ($oDeletionPlan->FoundStopper())
|
||||
{
|
||||
$aIssues = $oDeletionPlan->GetIssues();
|
||||
$this->LogCRUDError(__METHOD__, ' Errors: '.implode(', ', $aIssues));
|
||||
throw new DeleteException('Found issue(s)', array('target_class' => get_class($this), 'target_id' => $this->GetKey(), 'issues' => implode(', ', $aIssues)));
|
||||
}
|
||||
|
||||
@@ -3950,6 +3995,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
set_time_limit(intval($iPreviousTimeLimit));
|
||||
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
@@ -5986,11 +6032,8 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$aSQLValues = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
|
||||
$aSQLValues = $oAttDef->GetSQLValues($this->Get($sAttCode));
|
||||
$value = reset($aSQLValues);
|
||||
if ($oAttDef->IsNull($value)) {
|
||||
return '';
|
||||
}
|
||||
$aArgs[$sFieldDesc] = $value;
|
||||
}
|
||||
|
||||
@@ -6321,5 +6364,129 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
return count(self::$m_aCrudStack) === 0;
|
||||
}
|
||||
|
||||
protected function LogCRUDEnter($sFunction, $sComment = '')
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
$sPadding = str_pad('', count(self::$m_aCrudStack), '-');
|
||||
IssueLog::Debug("CRUD +$sPadding> $sFunction $sClass:$sKey $sComment", LogChannels::DM_CRUD);
|
||||
}
|
||||
|
||||
protected function LogCRUDExit($sFunction, $sComment = '')
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
$sPadding = str_pad('', count(self::$m_aCrudStack), '-');
|
||||
if (strlen($sComment) === 0) {
|
||||
IssueLog::Trace("CRUD <$sPadding+ $sFunction $sClass:$sKey", LogChannels::DM_CRUD);
|
||||
} else {
|
||||
IssueLog::Debug("CRUD <$sPadding+ $sFunction $sClass:$sKey $sComment", LogChannels::DM_CRUD);
|
||||
}
|
||||
}
|
||||
|
||||
protected function LogCRUDDebug($sFunction, $sComment = '')
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
$sPadding = str_pad('', count(self::$m_aCrudStack), '-');
|
||||
IssueLog::Debug("CRUD --$sPadding $sFunction $sClass:$sKey $sComment", LogChannels::DM_CRUD);
|
||||
}
|
||||
|
||||
protected function LogCRUDError($sFunction, $sComment = '')
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
$sKey = $this->GetKey();
|
||||
$sPadding = str_pad('', count(self::$m_aCrudStack), '!');
|
||||
IssueLog::Error("CRUD !!$sPadding Error $sFunction $sClass:$sKey $sComment", LogChannels::DM_CRUD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle temporary descriptors.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*
|
||||
* @experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
private function HandleTemporaryDescriptor()
|
||||
{
|
||||
if ($this->HasContextSection('temporary_objects')) {
|
||||
TemporaryObjectManager::GetInstance()->HandleTemporaryObjects($this, $this->GetContextSection('temporary_objects'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return context information.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
public function GetContext(): array
|
||||
{
|
||||
return $this->aContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set context section data.
|
||||
*
|
||||
* @param string $sSection
|
||||
* @param $value
|
||||
*
|
||||
* @experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*
|
||||
*/
|
||||
public function SetContextSection(string $sSection, $value)
|
||||
{
|
||||
$this->aContext[$sSection] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get context section data.
|
||||
*
|
||||
* @param string $sSection
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
public function GetContextSection(string $sSection)
|
||||
{
|
||||
if ($this->HasContextSection($sSection)) {
|
||||
return $this->aContext[$sSection];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test context section existence.
|
||||
*
|
||||
* @param string $sSection
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* experimental do not use, this feature will be part of a future version
|
||||
*
|
||||
* @since 3.1
|
||||
*/
|
||||
public function HasContextSection(string $sSection): bool
|
||||
{
|
||||
return array_key_exists($sSection, $this->aContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -583,6 +583,12 @@ class LogChannels
|
||||
*/
|
||||
public const DM_CRUD = 'DMCRUD';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the datamodel CRUD
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public const WEB_REQUEST = 'WebRequest';
|
||||
|
||||
/**
|
||||
* @var string Everything related to the event service
|
||||
* @since 3.1.0
|
||||
@@ -606,6 +612,8 @@ class LogChannels
|
||||
|
||||
public const PORTAL = 'portal';
|
||||
|
||||
public const TEMPORARY_OBJECTS = 'TemporaryObjects';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.1.0
|
||||
@@ -1230,7 +1238,9 @@ class DeprecatedCallsLog extends LogAPI
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \ConfigException
|
||||
* @since 3.0.1 3.1.0 N°4725 silently handles ConfigException
|
||||
* @since 3.0.4 3.1.0 N°4725 remove forgotten throw PHPDoc annotation
|
||||
*
|
||||
* @link https://www.php.net/debug_backtrace
|
||||
* @uses \debug_backtrace()
|
||||
*/
|
||||
@@ -1404,7 +1414,7 @@ class LogFileRotationProcess implements iScheduledProcess
|
||||
$iMaxDays = MetaModel::GetConfig()->Get(LogAPI::ENUM_CONFIG_PARAM_PURGE_MAX_KEEP_DAYS);
|
||||
|
||||
// Files iterator (*.*)
|
||||
$oIterator = new \GlobIterator(APPROOT.'log'.DIRECTORY_SEPARATOR.'/*.*');
|
||||
$oIterator = new \GlobIterator(APPROOT.'log'.DIRECTORY_SEPARATOR.'*.*');
|
||||
$aLogFiles = iterator_to_array($oIterator);
|
||||
|
||||
// Reference date
|
||||
@@ -1416,6 +1426,11 @@ class LogFileRotationProcess implements iScheduledProcess
|
||||
// File real path
|
||||
$sFileRealPath = $oLogFile->getRealPath();
|
||||
|
||||
// Check file extension
|
||||
if(!in_array($oLogFile->getExtension(), ['log','sql','xml'])){
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute number of days since last modification
|
||||
$oDateFileLastModification = new DateTime();
|
||||
$oDateFileLastModification->setTimestamp($oLogFile->getMTime());
|
||||
|
||||
@@ -2927,52 +2927,7 @@ abstract class MetaModel
|
||||
}
|
||||
|
||||
self::$m_sTablePrefix = $sTablePrefix;
|
||||
|
||||
// Build the list of available extensions
|
||||
//
|
||||
$aInterfaces = [
|
||||
'iApplicationUIExtension',
|
||||
'iPreferencesExtension',
|
||||
'iApplicationObjectExtension',
|
||||
'iLoginFSMExtension',
|
||||
'iLoginUIExtension',
|
||||
'iLogoutExtension',
|
||||
'iQueryModifier',
|
||||
'iOnClassInitialization',
|
||||
'iPopupMenuExtension',
|
||||
'iPageUIExtension',
|
||||
'iPageUIBlockExtension',
|
||||
'iBackofficeLinkedScriptsExtension',
|
||||
'iBackofficeEarlyScriptExtension',
|
||||
'iBackofficeScriptExtension',
|
||||
'iBackofficeInitScriptExtension',
|
||||
'iBackofficeReadyScriptExtension',
|
||||
'iBackofficeLinkedStylesheetsExtension',
|
||||
'iBackofficeStyleExtension',
|
||||
'iBackofficeDictEntriesExtension',
|
||||
'iBackofficeDictEntriesPrefixesExtension',
|
||||
'iPortalUIExtension',
|
||||
'ModuleHandlerApiInterface',
|
||||
'iNewsroomProvider',
|
||||
'iModuleExtension',
|
||||
];
|
||||
foreach($aInterfaces as $sInterface)
|
||||
{
|
||||
self::$m_aExtensionClassNames[$sInterface] = array();
|
||||
}
|
||||
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
$oExtensionInstance = null;
|
||||
foreach($aInterfaces as $sInterface)
|
||||
{
|
||||
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable())
|
||||
{
|
||||
self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
self::InitExtensions();
|
||||
|
||||
// Initialize the classes (declared attributes, etc.)
|
||||
//
|
||||
@@ -7642,6 +7597,56 @@ abstract class MetaModel
|
||||
unset(self::$m_aReentranceProtection[get_class($oObject)][$oObject->GetKey()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For test purpose
|
||||
* @throws \ReflectionException
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public static function InitExtensions()
|
||||
{
|
||||
// Build the list of available extensions
|
||||
//
|
||||
$aInterfaces = [
|
||||
'iApplicationUIExtension',
|
||||
'iPreferencesExtension',
|
||||
'iApplicationObjectExtension',
|
||||
'iLoginFSMExtension',
|
||||
'iLoginUIExtension',
|
||||
'iLogoutExtension',
|
||||
'iQueryModifier',
|
||||
'iOnClassInitialization',
|
||||
'iPopupMenuExtension',
|
||||
'iPageUIExtension',
|
||||
'iPageUIBlockExtension',
|
||||
'iBackofficeLinkedScriptsExtension',
|
||||
'iBackofficeEarlyScriptExtension',
|
||||
'iBackofficeScriptExtension',
|
||||
'iBackofficeInitScriptExtension',
|
||||
'iBackofficeReadyScriptExtension',
|
||||
'iBackofficeLinkedStylesheetsExtension',
|
||||
'iBackofficeStyleExtension',
|
||||
'iBackofficeDictEntriesExtension',
|
||||
'iBackofficeDictEntriesPrefixesExtension',
|
||||
'iPortalUIExtension',
|
||||
'ModuleHandlerApiInterface',
|
||||
'iNewsroomProvider',
|
||||
'iModuleExtension',
|
||||
];
|
||||
foreach ($aInterfaces as $sInterface) {
|
||||
self::$m_aExtensionClassNames[$sInterface] = array();
|
||||
}
|
||||
|
||||
foreach (get_declared_classes() as $sPHPClass) {
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
$oExtensionInstance = null;
|
||||
foreach ($aInterfaces as $sInterface) {
|
||||
if ($oRefClass->implementsInterface($sInterface) && $oRefClass->isInstantiable()) {
|
||||
self::$m_aExtensionClassNames[$sInterface][$sPHPClass] = $sPHPClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3526,6 +3526,7 @@ class CharConcatWSExpression extends CharConcatExpression
|
||||
$aRes = array();
|
||||
foreach ($this->m_aExpressions as $oExpr)
|
||||
{
|
||||
// TODO: Seems weird, this should rather be $aRes[] = $oExpr->Evaluate($aArgs);
|
||||
$aRes .= $oExpr->Evaluate($aArgs);
|
||||
}
|
||||
return implode($this->m_separator, $aRes);
|
||||
|
||||
@@ -86,6 +86,33 @@ class ormDocument
|
||||
{
|
||||
return ($this->m_data == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ormDocument $oCompared
|
||||
*
|
||||
* @return bool True if the current ormDocument is equals to $oCompared EXCEPT for its download count. False if any other property is different OR if count is the same.
|
||||
* @since 3.1.0 N°6502
|
||||
*/
|
||||
public function EqualsExceptDownloadsCount(ormDocument $oCompared): bool
|
||||
{
|
||||
// First checking equality on others properties
|
||||
if ($oCompared->GetData() !== $this->GetData()) {
|
||||
return false;
|
||||
}
|
||||
if ($oCompared->GetMimeType() !== $this->GetMimeType()) {
|
||||
return false;
|
||||
}
|
||||
if ($oCompared->GetFileName() !== $this->GetFileName()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finally check equality of the download count
|
||||
if ($oCompared->GetDownloadsCount() === $this->GetDownloadsCount()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public function GetMimeType()
|
||||
{
|
||||
|
||||
@@ -98,4 +98,14 @@ class PluginManager
|
||||
|
||||
return $oInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* For test purpose
|
||||
* @return void
|
||||
* @since 3.1.0
|
||||
*/
|
||||
protected static function ResetPlugins()
|
||||
{
|
||||
self::$m_aExtensionClasses = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@
|
||||
|
||||
class SimpleCrypt
|
||||
{
|
||||
/**
|
||||
* @var \SimpleCrypt
|
||||
* @since 3.1.0 N°5388
|
||||
*/
|
||||
protected $oEngine;
|
||||
|
||||
public static function GetNewDefaultParams()
|
||||
{
|
||||
if(function_exists('sodium_crypto_secretbox_open') && function_exists('random_bytes')){
|
||||
@@ -62,6 +68,7 @@ class SimpleCrypt
|
||||
$sEngineName = 'SimpleCrypt' . $sEngineName . 'Engine';
|
||||
return $sEngineName::GetNewDefaultParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param string $sEngineName Engine for encryption. Values: Simple, Mcrypt, Sodium or OpenSSL
|
||||
|
||||
@@ -218,8 +218,8 @@ class SQLObjectQueryBuilder
|
||||
continue;
|
||||
}
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
// Skip this attribute if not made of SQL columns
|
||||
if (count($oAttDef->GetSQLExpressions()) == 0)
|
||||
// Skip this attribute if not made of SQL columns nor in current table
|
||||
if (count($oAttDef->GetSQLExpressions()) == 0 || $oAttDef->IsExternalField())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ abstract class Trigger extends cmdbAbstractObject
|
||||
MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("action_list",
|
||||
array("linked_class" => "lnkTriggerAction", "ext_key_to_me" => "trigger_id", "ext_key_to_remote" => "action_id", "allowed_values" => null, "count_min" => 1, "count_max" => 0, "depends_on" => array())));
|
||||
$aTags = ContextTag::GetTags();
|
||||
MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
|
||||
MetaModel::Init_AddAttribute(new AttributeEnumSet("context", array("allowed_values" => null, "possible_values" => new ValueSetEnumPadded($aTags, true), "sql" => "context", "depends_on" => array(), "is_null_allowed" => true, "max_items" => 12)));
|
||||
// "complement" is a computed field, fed by Trigger sub-classes, in general in ComputeValues method, for eg. the TriggerOnObject fed it with target_class info
|
||||
MetaModel::Init_AddAttribute(new AttributeString("complement", array("allowed_values" => null, "sql" => "complement", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ abstract class ValueSetDefinition
|
||||
public function SortValues(array &$aValues): void
|
||||
{
|
||||
// Sort alphabetically on values
|
||||
asort($aValues);
|
||||
natcasesort($aValues);
|
||||
}
|
||||
|
||||
abstract protected function LoadValues($aArgs);
|
||||
@@ -465,11 +465,11 @@ class ValueSetEnum extends ValueSetDefinition
|
||||
{
|
||||
protected $m_values;
|
||||
/**
|
||||
* @var bool $bSortByValue If true, values will be sorted at runtime, otherwise it is sorted at compile time in a predefined order.
|
||||
* @var bool $bSortByValues If true, values will be sorted at runtime (on their values, not their keys), otherwise it is sorted at compile time in a predefined order.
|
||||
* {@see \MFCompiler::CompileAttributeEnumValues()} for complete reasons.
|
||||
* @since 3.1.0 N°1646
|
||||
*/
|
||||
protected bool $bSortByValue;
|
||||
protected bool $bSortByValues;
|
||||
|
||||
/**
|
||||
* @param array|string $Values
|
||||
@@ -477,20 +477,20 @@ class ValueSetEnum extends ValueSetDefinition
|
||||
*
|
||||
* @since 3.1.0 N°1646 Add $bLocalizedSort parameter
|
||||
*/
|
||||
public function __construct($Values, bool $bSortByValue = false)
|
||||
public function __construct($Values, bool $bSortByValues = false)
|
||||
{
|
||||
$this->m_values = $Values;
|
||||
$this->bSortByValue = $bSortByValue;
|
||||
$this->bSortByValues = $bSortByValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see \ValueSetEnum::$bSortByValue
|
||||
* @see \ValueSetEnum::$bSortByValues
|
||||
* @return bool
|
||||
* @since 3.1.0 N°1646
|
||||
*/
|
||||
public function IsSortedByValues(): bool
|
||||
{
|
||||
return $this->bSortByValue;
|
||||
return $this->bSortByValues;
|
||||
}
|
||||
|
||||
// Helper to export the data model
|
||||
@@ -507,8 +507,8 @@ class ValueSetEnum extends ValueSetDefinition
|
||||
public function SortValues(array &$aValues): void
|
||||
{
|
||||
// Force sort by values only if necessary
|
||||
if ($this->bSortByValue) {
|
||||
asort($aValues);
|
||||
if ($this->bSortByValues) {
|
||||
natcasesort($aValues);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -548,9 +548,13 @@ class ValueSetEnum extends ValueSetDefinition
|
||||
|
||||
class ValueSetEnumPadded extends ValueSetEnum
|
||||
{
|
||||
public function __construct($Values)
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @since 3.1.0 N°6448 Add $bSortByValues parameter
|
||||
*/
|
||||
public function __construct($Values, bool $bSortByValues = false)
|
||||
{
|
||||
parent::__construct($Values);
|
||||
parent::__construct($Values, $bSortByValues);
|
||||
if (is_string($Values))
|
||||
{
|
||||
$this->LoadValues(null);
|
||||
@@ -562,6 +566,7 @@ class ValueSetEnumPadded extends ValueSetEnum
|
||||
$aPaddedValues = array();
|
||||
foreach ($this->m_aValues as $sKey => $sVal)
|
||||
{
|
||||
// Pad keys to the min. length required by the \AttributeSet
|
||||
$sKey = str_pad($sKey, 3, '_', STR_PAD_LEFT);
|
||||
$aPaddedValues[$sKey] = $sVal;
|
||||
}
|
||||
@@ -610,7 +615,7 @@ class ValueSetEnumClasses extends ValueSetEnum
|
||||
public function __construct($sCategories = '', $sAdditionalValues = '')
|
||||
{
|
||||
$this->m_sCategories = $sCategories;
|
||||
parent::__construct($sAdditionalValues);
|
||||
parent::__construct($sAdditionalValues, true /* Classes are always sorted alphabetically */);
|
||||
}
|
||||
|
||||
protected function LoadValues($aArgs)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
/**
|
||||
* Spanish Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
@@ -7,7 +7,6 @@
|
||||
* @traductor Miguel Turrubiates <miguel_tf@yahoo.com>
|
||||
* @notas Utilizar codificación UTF-8 para mostrar acentos y otros caracteres especiales
|
||||
*/
|
||||
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'CAS:Error:UserNotAllowed' => 'Usuario no permitido',
|
||||
'CAS:Login:SignIn' => 'Iniciar sesión con CAS',
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
* @copyright Copyright (C) 2013 XXXXX
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'CAS:Error:UserNotAllowed' => 'Użytkownik niedozwolony',
|
||||
'CAS:Login:SignIn' => 'Zaloguj się za pomocą CAS',
|
||||
|
||||
@@ -15,7 +15,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/>
|
||||
/**
|
||||
/**
|
||||
* Spanish Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
@@ -30,11 +29,9 @@
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
|
||||
//
|
||||
// Class: UserExternal
|
||||
//
|
||||
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'Class:UserExternal' => 'Użytkownik zewnętrzny',
|
||||
'Class:UserExternal+' => 'Użytkownik uwierzytelniony poza '.ITOP_APPLICATION_SHORT,
|
||||
|
||||
@@ -38,4 +38,5 @@
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Class:UserLDAP' => 'LDAP uživatel',
|
||||
'Class:UserLDAP+' => 'Uživatel ověřen přes LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -23,4 +23,5 @@
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Class:UserLDAP' => 'LDAP-Bruger',
|
||||
'Class:UserLDAP+' => 'Bruger der godkendes via LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -25,4 +25,7 @@
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:UserLDAP' => 'LDAP-Benutzer',
|
||||
'Class:UserLDAP+' => 'Benutzer, der via LDAP authentifiziert wird',
|
||||
'Class:UserLDAP/Attribute:ldap_server' => 'LDAP-Server',
|
||||
'Class:UserLDAP/Attribute:ldap_server+' => 'Optional: LDAP-Server, der zur Authentifizierung verwendet werden soll, falls mehrere LDAP-Server konfiguriert sind.',
|
||||
'UserLDAP:server' => 'LDAP-Einstellungen',
|
||||
));
|
||||
|
||||
@@ -15,7 +15,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/>
|
||||
/**
|
||||
/**
|
||||
* Spanish Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
@@ -37,4 +37,5 @@
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:UserLDAP' => 'Usuario LDAP',
|
||||
'Class:UserLDAP+' => 'Usuario autenticado vía LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -22,4 +22,5 @@
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'Class:UserLDAP' => 'LDAP felhasználó',
|
||||
'Class:UserLDAP+' => '',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -36,4 +36,5 @@
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'Class:UserLDAP' => 'Utente LDAP',
|
||||
'Class:UserLDAP+' => 'Utente autenticato da LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -23,4 +23,5 @@
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'Class:UserLDAP' => 'LDAP ユーザー',
|
||||
'Class:UserLDAP+' => 'LDAP認証ユーザー',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -37,4 +37,5 @@
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'Class:UserLDAP' => 'LDAP-gebruiker',
|
||||
'Class:UserLDAP+' => 'Gebruiker die aanmeldt via LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Dictionnay conventions
|
||||
// Class:<class_name>
|
||||
// Class:<class_name>+
|
||||
@@ -30,12 +29,11 @@
|
||||
// Class:<class_name>/Attribute:<attribute_code>/Value:<value>+
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>
|
||||
// Class:<class_name>/Stimulus:<stimulus_code>+
|
||||
|
||||
//
|
||||
// Class: UserLDAP
|
||||
//
|
||||
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'Class:UserLDAP' => 'Użytkownik LDAP',
|
||||
'Class:UserLDAP+' => 'Użytkownik uwierzytelniony przez LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -22,4 +22,5 @@
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'Class:UserLDAP' => 'Usuário externo via LDAP',
|
||||
'Class:UserLDAP+' => '',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -14,4 +14,5 @@
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'Class:UserLDAP' => 'Пользователь LDAP',
|
||||
'Class:UserLDAP+' => 'Пользователь, аутентифицируемый через LDAP',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -35,4 +35,5 @@
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'Class:UserLDAP' => 'LDAP užívateľ',
|
||||
'Class:UserLDAP+' => '',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -37,4 +37,5 @@
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'Class:UserLDAP' => 'LDAP kullanıcısı',
|
||||
'Class:UserLDAP+' => 'Yetki kontrolü LDAP tarafından yapılan',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -36,4 +36,5 @@
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:UserLDAP' => 'LDAP 用户',
|
||||
'Class:UserLDAP+' => '用户身份由LDAP 认证',
|
||||
'UserLDAP:server' => 'LDAP specifics~~',
|
||||
));
|
||||
|
||||
@@ -40,7 +40,6 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Class:UserLocal+' => 'Uživatel ověřen interně v '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Heslo',
|
||||
'Class:UserLocal/Attribute:password+' => '',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -53,9 +52,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -25,7 +25,6 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Class:UserLocal+' => 'Bruger der godkendes af '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Password',
|
||||
'Class:UserLocal/Attribute:password+' => 'Brugerens password',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -38,9 +37,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -27,7 +27,6 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:UserLocal+' => 'Benutzer, der von '.ITOP_APPLICATION_SHORT.' authentifiziert wird',
|
||||
'Class:UserLocal/Attribute:password' => 'Passwort',
|
||||
'Class:UserLocal/Attribute:password+' => 'Benutzerpasswort',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Passwortablauf',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Passwortablaufstatus (statusabhängige Effekte müssen per Extension implementiert werden)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'kann ablaufen',
|
||||
@@ -40,9 +39,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Letzte Passworterneuerung',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Letztes Änderungsdatum',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Das Passwort entspricht nicht dem in den Konfigurationsregeln hinterlegten RegEx-Ausdruck',
|
||||
|
||||
'UserLocal:password:expiration' => 'Die folgenden Felder benötigen eine '.ITOP_APPLICATION_SHORT.' Erweiterung',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Das setzen des Passwortablaufs auf "Einmalpasswort" ist für den eigenen Benutzer nicht erlaubt.',
|
||||
));
|
||||
|
||||
@@ -39,7 +39,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:UserLocal+' => 'Usuario Autenticado vía '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Contraseña',
|
||||
'Class:UserLocal/Attribute:password+' => 'Contraseña',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Expiración de contraseña',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Estatus de expiración de contraseña (requiere de una extensión para que tenga efecto)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Puede expirar',
|
||||
@@ -52,9 +51,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'El usuario no puede cambiar la contraseña.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Renovación de contraseña',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Cuando fue el último cambio de contraseña',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'La contraseña debe ser de al menos 8 caracteres e incluír mayúsculas, minúsculas, números y caracteres especiales.',
|
||||
|
||||
'UserLocal:password:expiration' => 'El siguiente campo requiere una extensión',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Configurar expiración de contraseña para "ontraseña de un solo uso" no está permitido para su propio Usuario',
|
||||
));
|
||||
|
||||
@@ -24,7 +24,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:UserLocal+' => 'Utilisateur authentifié par '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Mot de passe',
|
||||
'Class:UserLocal/Attribute:password+' => '',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Validité du mot de passe',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Statut du mot de passe (nécessite une extension pour avoir un effet)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Durée limitée',
|
||||
@@ -37,7 +36,6 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Mot de passe changé le',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Dernière date à laquelle le mot de passe a été changé',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Le mot de passe doit contenir au moins 8 caractères, avec minuscule, majuscule, nombre et caractère spécial.',
|
||||
'UserLocal:password:expiration' => 'Les champs ci-dessous nécessitent une extension',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Impossible de mettre "Usage unique" comme validité du mot de passe pour son propre utilisateur.',
|
||||
|
||||
@@ -24,7 +24,6 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'Class:UserLocal+' => '',
|
||||
'Class:UserLocal/Attribute:password' => 'Jelszó',
|
||||
'Class:UserLocal/Attribute:password+' => '',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Jelszó lejárati ideje',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Jelszó lejárati státusz (bővítmény szükséges hozzá)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Lejár',
|
||||
@@ -35,11 +34,9 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:force_expire+' => '~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire' => 'Egyszeri jelszó',
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'A felhasználó nem változtathat jelszót.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Jelszó megújítás ideje',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Jelszó megújítás ideje',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'A jelszó legutóbbi módosításának időpontja',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A jelszónak legalább 8 karakterből kell állnia, és tartalmaznia kell nagybetűket, kisbetűket, numerikus és speciális karaktereket.',
|
||||
|
||||
'UserLocal:password:expiration' => 'Az alábbi mezőkhöz egy bővítmény szükséges',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'A jelszó lejárati idejének beállítása "Egyszeri jelszóra" nem engedélyezett a saját Felhasználó számára.',
|
||||
));
|
||||
|
||||
@@ -38,7 +38,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'Class:UserLocal+' => 'Utente autenticato da '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Password',
|
||||
'Class:UserLocal/Attribute:password+' => 'user authentication string',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -51,9 +50,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -25,7 +25,6 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'Class:UserLocal+' => ITOP_APPLICATION_SHORT.'ローカル認証ユーザー',
|
||||
'Class:UserLocal/Attribute:password' => 'パスワード',
|
||||
'Class:UserLocal/Attribute:password+' => '認証文字列',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -38,9 +37,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -29,7 +29,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'Class:UserLocal+' => 'Gebruiker die aanmeldt met gegevens aangemaakt in het gebruikersbeheer van '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Wachtwoord',
|
||||
'Class:UserLocal/Attribute:password+' => 'Het wachtwoord waarmee de gebruiker zich aanmeldt bij '.ITOP_APPLICATION_SHORT,
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Wachtwoord verloopt',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Of het wachtwoord al dan niet verlopen is (vereist een extensie vooraleer dit werkt)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Kan verlopen',
|
||||
@@ -42,9 +41,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'De gebruiker kan dit wachtwoord niet veranderen.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Wachtwoord laatst aangepast',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Tijdstip waarop het wachtwoord het laatst aangepast werd.',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Het wachtwoord bestaat uit minstens 8 tekens en bestaat uit een mix van minstens 1 hoofdletter, kleine letter, cijfer en speciaal teken.',
|
||||
|
||||
'UserLocal:password:expiration' => 'De velden hieronder vereisen een extensie.',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Je kan geen eenmalig wachtwoord instellen voor je eigen gebruiker.',
|
||||
));
|
||||
|
||||
@@ -37,7 +37,6 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'Class:UserLocal+' => 'Użytkownik uwierzytelniony przez '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Hasło',
|
||||
'Class:UserLocal/Attribute:password+' => 'Ciąg uwierzytelniania użytkownika',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Wygaśnięcie hasła',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Stan wygaśnięcia hasła (wymaga rozszerzenia, aby zadziałało)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Może wygasnąć',
|
||||
@@ -50,9 +49,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Hasło nie może być zmienione przez użytkownika.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Odnowienie hasła',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Kiedy ostatnio zmieniano hasło',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Hasło musi mieć co najmniej 8 znaków i zawierać duże, małe litery, cyfry i znaki specjalne.',
|
||||
|
||||
'UserLocal:password:expiration' => 'Poniższe pola wymagają rozszerzenia',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Ustawienie wygaśnięcia hasła "Hasło jednorazowe" nie jest dozwolone dla własnego użytkownika',
|
||||
));
|
||||
|
||||
@@ -24,7 +24,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'Class:UserLocal+' => '',
|
||||
'Class:UserLocal/Attribute:password' => 'Senha',
|
||||
'Class:UserLocal/Attribute:password+' => '',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Expiração da senha',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Status de expiração de senha (Requer uma extensão para fazer efeito)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Senha expira',
|
||||
@@ -37,9 +36,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Senha não pode ser alterada pelo usuário',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Data da última alteração de senha',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Quando a senha foi alterada anteriormente',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'A senha deve ter no mínimo 8 caracteres e incluir letras maiúsculas, minúsculas, números e símbolos',
|
||||
|
||||
'UserLocal:password:expiration' => 'O campo abaixo requer uma extensão',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Definir a expiração da senha para One-Time Password (OTP) não é permitido para o seu próprio usuário',
|
||||
));
|
||||
|
||||
@@ -16,7 +16,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'Class:UserLocal+' => 'Пользователь, аутентифицируемый через '.ITOP_APPLICATION_SHORT,
|
||||
'Class:UserLocal/Attribute:password' => 'Пароль',
|
||||
'Class:UserLocal/Attribute:password+' => 'Строка аутентификации пользователя',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Срок действия пароля',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Статус срока действия пароля (требуется расширение для эффекта)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Органиченный',
|
||||
@@ -29,9 +28,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Дата изменения пароля',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'Когда пароль был изменен в последний раз',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Пароль должен содержать не менее 8 символов и включать прописные, строчные, числовые и специальные символы.',
|
||||
|
||||
'UserLocal:password:expiration' => 'Поля требуют наличия доп. расширения',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -37,7 +37,6 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'Class:UserLocal+' => '',
|
||||
'Class:UserLocal/Attribute:password' => 'Heslo',
|
||||
'Class:UserLocal/Attribute:password+' => '',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -50,9 +49,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -39,7 +39,6 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'Class:UserLocal+' => 'Yetki kontorlünü '.ITOP_APPLICATION_SHORT.' tarafından yapılan kullanıcı',
|
||||
'Class:UserLocal/Attribute:password' => 'Şifre',
|
||||
'Class:UserLocal/Attribute:password+' => 'şifre',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => 'Password expiration~~',
|
||||
'Class:UserLocal/Attribute:expiration+' => 'Password expiration status (requires an extension to have an effect)~~',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => 'Can expire~~',
|
||||
@@ -52,9 +51,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => 'Password cannot be changed by the user.~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => 'Password renewal~~',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => 'When the password was last changed~~',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => 'Password must be at least 8 characters and include uppercase, lowercase, numeric and special characters.~~',
|
||||
|
||||
'UserLocal:password:expiration' => 'The fields below require an extension~~',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => 'Setting password expiration to "One-time password" is not allowed for your own User~~',
|
||||
));
|
||||
|
||||
@@ -38,7 +38,6 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:UserLocal+' => '用户由 '.ITOP_APPLICATION_SHORT.' 验证身份',
|
||||
'Class:UserLocal/Attribute:password' => '密码',
|
||||
'Class:UserLocal/Attribute:password+' => '用于验证用户身份的字符串',
|
||||
|
||||
'Class:UserLocal/Attribute:expiration' => '密码过期',
|
||||
'Class:UserLocal/Attribute:expiration+' => '密码过期状态(需要一个扩展才能生效)',
|
||||
'Class:UserLocal/Attribute:expiration/Value:can_expire' => '允许过期',
|
||||
@@ -51,9 +50,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'Class:UserLocal/Attribute:expiration/Value:otp_expire+' => '用户不允许修改密码.',
|
||||
'Class:UserLocal/Attribute:password_renewed_date' => '密码更新',
|
||||
'Class:UserLocal/Attribute:password_renewed_date+' => '上次修改密码的时间',
|
||||
|
||||
'Error:UserLocalPasswordValidator:UserPasswordPolicyRegex:ValidationFailed' => '密码必须至少8个字符,包含大小写,数字和特殊字符.',
|
||||
|
||||
'UserLocal:password:expiration' => '下面的区域需要插件扩展',
|
||||
'Class:UserLocal/Error:OneTimePasswordChangeIsNotAllowed' => '不允许用户为自己设置"一次性密码"的失效期限',
|
||||
));
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'theme:darkmoon' => 'Dark moon',
|
||||
));
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'theme:darkmoon' => 'Luna Obscura',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'theme:darkmoon' => 'Dark moon',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'theme:darkmoon' => 'Dark moon',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'theme:darkmoon' => 'Dark moon',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -20,7 +20,6 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'theme:darkmoon' => 'Dark moon~~',
|
||||
));
|
||||
@@ -462,6 +462,12 @@ class DatabaseAnalyzer
|
||||
$aCols = $oAttDef->GetSQLExpressions(); // Workaround a PHP bug: sometimes issuing a Notice if invoking current(somefunc())
|
||||
$sMyAttributeField = current($aCols); // get the first column for the moment
|
||||
$sFilter = "FROM `$sTable` WHERE `$sTable`.`$sMyAttributeField` NOT IN ($sExpectedValues)";
|
||||
if ($oAttDef->IsNullAllowed()) {
|
||||
// NotEmptyToSql should have been in AttributeDefinition, as a workaround the search type is used
|
||||
$sSearchType = $oAttDef->GetSearchType();
|
||||
$sCondition = $this->NotEmptyToSql("`$sTable`.`$sMyAttributeField`", $sSearchType);
|
||||
$sFilter .= " AND $sCondition";
|
||||
}
|
||||
$sDelete = "DELETE `$sTable`";
|
||||
$sSelect = "SELECT DISTINCT `$sTable`.`$sKeyField` AS id, `$sTable`.`$sMyAttributeField` AS value";
|
||||
$sSelWrongRecs = "$sSelect $sFilter";
|
||||
@@ -492,6 +498,26 @@ class DatabaseAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $sRef
|
||||
* @param string $sSearchType
|
||||
*
|
||||
* @return string
|
||||
* @since 3.1.0 N°6442
|
||||
*/
|
||||
private function NotEmptyToSql($sRef, string $sSearchType)
|
||||
{
|
||||
switch ($sSearchType) {
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_NUMERIC:
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_EXTERNAL_FIELD:
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE:
|
||||
case AttributeDefinition::SEARCH_WIDGET_TYPE_DATE_TIME:
|
||||
return "ISNULL({$sRef}) = 0";
|
||||
}
|
||||
|
||||
return "({$sRef} != '')";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user accounts without profile
|
||||
*
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -38,14 +38,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'DBTools:IntegrityCheck' => 'Integritätscheck',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (dauert länger)',
|
||||
'DBTools:SelectAnalysisType' => 'Analysetyp auswählen',
|
||||
|
||||
'DBTools:Analyze' => 'Analysiere',
|
||||
'DBTools:Details' => 'Details anzeigen',
|
||||
'DBTools:ShowAll' => 'Alle Fehler anzeigen',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Datenbank-Inkonsistenzen',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s Fehler(s) in der Klasse %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'Liste auf %1$s Fehler begrenzt',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Verwaister Eintrag in `%1$s`, er sollte eine Entsprechung in Tabelle `%2$s` haben',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Ungültiger Externer Key %1$s (Spalte: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Fehlender Externer Key %1$s (Spalte: `%2$s.%3$s`)',
|
||||
@@ -68,24 +66,19 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'DBTools:LostAttachments' => 'Verlorene Attachments',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Hier können Sie Ihre Datenbank nach verlorenen oder falsch platzierten Attachments durchsuchen. Dies ist kein Recovery-Tool - es stellt keine gelöschten Daten wieder her.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analysieren',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Wiederherstellen',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Diese Aktion kann nicht rückgängig gemacht werden, bitte bestätigen Sie, dass Sie die ausgewählten Dateien wiederherstellen möchten.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Bitte warten...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Suche zunächst nach verlorenen / falsch platzierten Attachments mittels einer Analyse der Datenbank',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyseergebnisse:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Toll! Alles scheint am richtigen Ort zu sein.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Manche Attachments scheinen am falschen Ort zu sein. Werfen Sie einen Blick auf die folgende Liste und wählen Sie diejenigen aus, die Sie gerne verschieben möchten.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Dateiname',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Derzeitiger Ort',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Verschieben nach...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore-Ergebnisse:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d Attachments wurden wiederhergestellt.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Als Inline-Bild gespeichert',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" mit DB-Tools wiederhergestellt'
|
||||
));
|
||||
|
||||
@@ -42,14 +42,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'DBTools:IntegrityCheck' => 'Verificación de integridad',
|
||||
'DBTools:FetchCheck' => 'Verificación de búsqueda (larga)',
|
||||
'DBTools:SelectAnalysisType' => 'Seleccionar tipo de análisis',
|
||||
|
||||
'DBTools:Analyze' => 'Analizar',
|
||||
'DBTools:Details' => 'Mostrar detalles',
|
||||
'DBTools:ShowAll' => 'Mostrar todos los errores',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Inconsistencias de Base de Datos',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(es) en clase %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Registro huérfano en `%1$s`, debería tener su contraparte en la tabla `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Llave externa inválida %1$s (columna: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Llave externa perdida %1$s (columna: `%2$s.%3$s`)',
|
||||
@@ -72,24 +70,19 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'DBTools:LostAttachments' => 'Adjuntos perdidos',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Aquí usted puede buscar anexos perdidos o fuera de lugar. Esta NO es una herramienta de recuperación de datos, no obtiene datos borrados.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analizar',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restaurar',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Esta acción no se puede deshacer, por favor confirme que quiere restaurar los archivos seleccionados.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Por favor espere...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Primero, buscar anexos perdidos o fuera de lugar analizando la base de datos.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analizar resultados:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => '¡Genial! Todo parece estar en el lugar correcto.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Algunos adjuntos (%1$d) parecen estar desplazados. Mire la siguiente lista y verifique los que quiera mover.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Nombre de archivo',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Ubicación actual',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Mover a...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Resultados de restauración:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d adjuntos fueron restaurados.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Almacenado como imagen en línea',
|
||||
'DBTools:LostAttachments:History' => 'Adjunto "%1$s" restaurado con herramientas de base de datos'
|
||||
));
|
||||
|
||||
@@ -37,15 +37,12 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'DBTools:IntegrityCheck' => 'Contrôle d\'intégrité',
|
||||
'DBTools:FetchCheck' => 'Contrôle de récupération (long)',
|
||||
'DBTools:SelectAnalysisType' => 'Type d\'analyse',
|
||||
|
||||
'DBTools:Analyze' => 'Analyser',
|
||||
'DBTools:Details' => 'Afficher détails',
|
||||
'DBTools:ShowAll' => 'Afficher toutes les erreurs',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Incohérences de base de données',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s erreur(s) dans la classe %1$s : %3$s',
|
||||
'DBTools:DetailedErrorLimit' => 'Liste limitée à %1$s erreurs',
|
||||
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Enregistrement orphelin dans `%1$s`, il devrait avoir son équivalent dans la table `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Clé externe invalide %1$s (colonne: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Clé externe manquante %1$s (colonne: `%2$s.%3$s`)',
|
||||
@@ -68,24 +65,19 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'DBTools:LostAttachments' => 'Pièces jointes perdues',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Ici vous pouvez retrouver des pièces jointes perdues ou égarées dans votre base de données. Ceci n\'est PAS un outil de récupération des données, il ne récupère pas les données effacées.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyser',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restaurer',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Cet action ne peut être annuler, veuillez confirmer que vous voulez restaurer les fichiers sélectionnés.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Patientez ...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Tout d\'abord, scannez la base de données à la recherche de pièces jointes perdues/égarées.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Résultat de l\'analyse :',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Parfait ! Il semble que tout soit en ordre.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Certaines pièces jointes (%1$d) semblent être au mauvais endroit. Examinez la liste suivante et cochez celles que vous souhaitez déplacer.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Nom de fichier',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Emplacement actuel',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Déplacer vers ...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Résultats de la restauration :',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d pièces jointes ont été restaurées.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stockée comme "InlineImage"',
|
||||
'DBTools:LostAttachments:History' => 'Pièce jointe "%1$s" restaurée avec l\'outil de BDD'
|
||||
));
|
||||
|
||||
@@ -41,15 +41,13 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'DBTools:IntegrityCheck' => 'Integritás ellenőrzés',
|
||||
'DBTools:FetchCheck' => 'Lehívás ellenőrzés (hosszú)',
|
||||
'DBTools:SelectAnalysisType' => 'Válasszon elemzés típust',
|
||||
|
||||
'DBTools:Analyze' => 'Elemzés',
|
||||
'DBTools:Details' => 'Részletek mutatása',
|
||||
'DBTools:ShowAll' => 'Minden hiba mutatása',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Adatbázis inkonzisztenciák',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s hiba a %1$s osztályban: %3$s',
|
||||
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Árva rekord a `%1$s` -ban, kell hogy legyen megfelelője a `%2$s` táblázatban',
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Árva rekord a `%1$s` -ban, kell hogy legyen megfelelője a `%2$s` táblázatban',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Érvénytelen a %1$s külső kulcs (oszlop: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Hiányzik a %1$s külső külcs (oszlop: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-InvalidValue' => '%1$s értéke érvénytelen (oszlop: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
|
||||
'DBTools:LostAttachments' => 'Elveszett mellékletek',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Itt kereshet az adatbázisban elveszett vagy elkeveredett mellékletek után. Ez NEM egy adat-visszaállítási eszköz, nem állítja vissza a törölt adatokat.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Elemzés',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Visszaállítás',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Ez a művelet nem vonható vissza, kérjük, erősítse meg, hogy vissza kívánja-e állítani a kiválasztott fájlokat.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Kérem várjon...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Először az adatbázis elemzésével keresse meg az elveszett/áthelyezett mellékleteket.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Elemzés eredménye:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Nagyszerű! Úgy tűnik, minden a helyén van.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Úgy tűnik, hogy néhány melléklet (%1$d) rossz helyen van. Nézze meg az alábbi listát, és ellenőrizze azokat, amelyeket szeretne áthelyezni.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Fájlnév',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Jelenlegi helye',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Áthelyezés...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Visszaállítás eredménye:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d melléklet lett visszaállítva.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Soron belüli képként tárolva',
|
||||
'DBTools:LostAttachments:History' => 'A "%1$s" melléklet visszaállítva a DB eszközzel'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -43,14 +43,12 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'DBTools:IntegrityCheck' => 'Integriteitscheck',
|
||||
'DBTools:FetchCheck' => 'Opvraag-check (fetch) (long)',
|
||||
'DBTools:SelectAnalysisType' => 'Selecteer soort analyse',
|
||||
|
||||
'DBTools:Analyze' => 'Analyseer',
|
||||
'DBTools:Details' => 'Toon details',
|
||||
'DBTools:ShowAll' => 'Toon alle fouten',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Inconsistenties in database',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s fout(en) in klasse %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Wees-record in "%1$s", het zou een verwant record moeten hebben in de tabel "%2$s"',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Ongeldige externe sleutel %1$s (kolom: "%2$s.%3$s")',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Ontbrekende externe sleutel %1$s (kolom: "%2$s.%3$s")',
|
||||
@@ -73,24 +71,19 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', array(
|
||||
'DBTools:LostAttachments' => 'Verloren bijlages',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Zoek hier verloren or verkeerd geplaatste bijlages. Dit is geen recovery-tool, het kan geen gewiste data herstellen.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyseer',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Herstel',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Deze actie kan niet ongedaan worden gemaakt. Bevestig dat je de bijlages wil herstellen.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Even geduld...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Zoek eerst verloren/verkeerd geplaatste bijlages door de database te analyseren.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Resultaten analyse:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Perfect, alles lijkt op de juiste plaats te staan!',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Somme bijlages (%1$d) lijken verkeerd te staan. Overloop de lijst en duid aan welke je wil verplaatsen.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Bestandsnaam',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Huidige locatie',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Verplaats naar ...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Resultaten herstel:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d bijlages werden hersteld.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Opgeslagen als afbeelding in tekst',
|
||||
'DBTools:LostAttachments:History' => 'Bijlage "%1$s" werd hersteld met de databasetools'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'DBTools:IntegrityCheck' => 'Sprawdzanie integralności',
|
||||
'DBTools:FetchCheck' => 'Sprawdzenie przestrzeni (długie)',
|
||||
'DBTools:SelectAnalysisType' => 'Wybierz typ analizy',
|
||||
|
||||
'DBTools:Analyze' => 'Analiza',
|
||||
'DBTools:Details' => 'Pokaż szczegóły',
|
||||
'DBTools:ShowAll' => 'Pokaż wszystkie błędy',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Niespójności bazy danych',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s błąd(y) w klasie %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Osierocony rekord w `%1$s`, powinien mieć swój odpowiednik w tabeli `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Nieprawidłowy klucz zewnętrzny %1$s (kolumna: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Brak klucza zewnętrznego %1$s (kolumna: `%2$s.%3$s`)',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', array(
|
||||
'DBTools:LostAttachments' => 'Utracone załączniki',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Tutaj możesz przeszukiwać bazę danych w poszukiwaniu zagubionych załączników. To NIE jest narzędzie do odzyskiwania danych, nie pobiera usuniętych danych.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analiza',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Przywróć',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Tej czynności nie można cofnąć, potwierdź, że chcesz przywrócić wybrane pliki.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Proszę czekać...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Najpierw wyszukaj zagubione załączniki, analizując bazę danych.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Wynik analizy:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Świetnie! Wszystko wydaje się być na właściwym miejscu.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Niektóre załączniki (%1$d) wydają się być zagubione. Spójrz na poniższą listę i zaznacz te, które chcesz przenieść.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Nazwa pliku',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Aktualna lokalizacja',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Przenieś do...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Wyniki przywracania:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d załączniki zostały przywrócone.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Zapisane jako obraz w treści',
|
||||
'DBTools:LostAttachments:History' => 'Załącznik "%1$s" przywrócony za pomocą narzędzi DB'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'DBTools:IntegrityCheck' => 'Verificação de integridade',
|
||||
'DBTools:FetchCheck' => 'Verificação de consulta (longa)',
|
||||
'DBTools:SelectAnalysisType' => 'Selecione o tipo de análise',
|
||||
|
||||
'DBTools:Analyze' => 'Analisar',
|
||||
'DBTools:Details' => 'Exibir detalhes',
|
||||
'DBTools:ShowAll' => 'Exibir todos os erros',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Inconsistências no banco de dados',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s erro(s) na classe %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Item orfão em `%1$s`, ele deveria ter seu registro irmão na tabela `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Chave externa inválida %1$s (coluna: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Chave externa ausente %1$s (coluna: `%2$s.%3$s`)',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
|
||||
'DBTools:LostAttachments' => 'Anexos perdidos',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Aqui você procurará no seu banco de dados por anexos perdidos. Isto NÃO é uma ferramenta de recuperação de dados, pois não busca dados apagados',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analisar',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Recuperar',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Esta ação não pode ser desfeita, você confirma que quer recuperar os arquivos selecionados?',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Aguarde...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Primeiro, vamos procurar por anexos perdidos através da análise do banco de dados',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Resultado da análise:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Ótimo! Tudo parece estar nos seus devidos lugares (Nenhum anexo perdido foi encontrado)',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Alguns anexos (%1$d) parecem estar perdidos. Verifique a lista abaixo e escolha os que você deseja mover',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Nome do arquivo',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Local atual',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Mover para',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Resultado da restauração:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d anexo(s) recuperado(s)',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Armazenar como imagem embutida',
|
||||
'DBTools:LostAttachments:History' => 'Anexo "%1$s" recuperado com as Ferramentas de Banco de Dados'
|
||||
));
|
||||
|
||||
@@ -28,14 +28,12 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'DBTools:IntegrityCheck' => 'Проверка целостности',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Анализировать',
|
||||
'DBTools:Details' => 'Показать подробности',
|
||||
'DBTools:ShowAll' => 'Показать все ошибки',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Несоответствия базы данных',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Сиротская запись в `%1$s`, она должна иметь свой аналог в таблице `%2$s`',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Недопустимый внешний ключ %1$s (столбец: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Отсутствует внешний ключ %1$s (столбец: `%2$s.%3$s`)',
|
||||
@@ -58,24 +56,19 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'DBTools:LostAttachments' => 'Потерянные вложения',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Здесь вы можете найти потерянные или ошибочно перемещённые вложения в вашей базе данных. Это не инструмент восстановления данных, он не восстанавливает удаленные данные.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Анализировать',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Восстановить',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'Это действие не может быть отменено. Пожалуйста, подтвердите, что вы хотите восстановить выбранные файлы.',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Пожалуйста, подождите...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'Для начала просканируйте базу данных на наличие потерянных вложений.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Результат анализа:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Отлично! Похоже, все в порядке.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Некоторые вложения (%1$d), похоже, находятся не в том месте. Просмотрите следующий список и отметьте те, которые вы хотите переместить.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Файл',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Текущее местоположение',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Переместить в...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Результат восстановления:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d вложения были восстановлены.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Хранится в качестве "InlineImage"',
|
||||
'DBTools:LostAttachments:History' => 'Вложение "%1$s" восстановлено с помощью инструментов обслуживания БД'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'DBTools:IntegrityCheck' => 'Integrity check~~',
|
||||
'DBTools:FetchCheck' => 'Fetch Check (long)~~',
|
||||
'DBTools:SelectAnalysisType' => 'Select analysis type~~',
|
||||
|
||||
'DBTools:Analyze' => 'Analyze~~',
|
||||
'DBTools:Details' => 'Show Details~~',
|
||||
'DBTools:ShowAll' => 'Show All Errors~~',
|
||||
|
||||
'DBTools:Inconsistencies' => 'Database inconsistencies~~',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s error(s) in class %1$s: %3$s~~',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => 'Invalid external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => 'Missing external key %1$s (column: `%2$s.%3$s`)~~',
|
||||
@@ -71,24 +69,19 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', array(
|
||||
'DBTools:LostAttachments' => 'Lost attachments~~',
|
||||
'DBTools:LostAttachments:Disclaimer' => 'Here you can search your database for lost or misplaced attachments. This is NOT a data recovery tool, is does not retrieve deleted data.~~',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => 'Analyze~~',
|
||||
'DBTools:LostAttachments:Button:Restore' => 'Restore~~',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => 'This action cannot be undone, please confirm that you want to restore the selected files.~~',
|
||||
'DBTools:LostAttachments:Button:Busy' => 'Please wait...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => 'First, search for lost/misplaced attachments by analyzing the database.~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => 'Analyze results:~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => 'Great! Every thing seems to be at the right place.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => 'Some attachments (%1$d) seem to be misplaced. Take a look at the following list and check the ones you would like to move.~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => 'Filename~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => 'Current location~~',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => 'Move to...~~',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => 'Restore results:~~',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d attachments were restored.~~',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => 'Stored as inline image~~',
|
||||
'DBTools:LostAttachments:History' => 'Attachment "%1$s" restored with DB tools~~'
|
||||
));
|
||||
|
||||
@@ -41,14 +41,12 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'DBTools:IntegrityCheck' => '完整性检查',
|
||||
'DBTools:FetchCheck' => '提取检查(耗时长)',
|
||||
'DBTools:SelectAnalysisType' => '请选择分析类型',
|
||||
|
||||
'DBTools:Analyze' => '分析',
|
||||
'DBTools:Details' => '显示详情',
|
||||
'DBTools:ShowAll' => '显示所有错误',
|
||||
|
||||
'DBTools:Inconsistencies' => '数据库不一致',
|
||||
'DBTools:DetailedErrorTitle' => '%2$s 个错误在类 %1$s: %3$s',
|
||||
|
||||
'DBTools:DetailedErrorLimit' => 'List limited to %1$s errors~~',
|
||||
'DBAnalyzer-Integrity-OrphanRecord' => 'Orphan record in `%1$s`, it should have its counterpart in table `%2$s`~~',
|
||||
'DBAnalyzer-Integrity-InvalidExtKey' => '无效的外键 %1$s (列: `%2$s.%3$s`)',
|
||||
'DBAnalyzer-Integrity-MissingExtKey' => '外键丢失 %1$s (列: `%2$s.%3$s`)',
|
||||
@@ -69,26 +67,21 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
|
||||
// Lost attachments
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
||||
'DBTools:LostAttachments' => '缺失附件',
|
||||
'DBTools:LostAttachments' => '附件缺失',
|
||||
'DBTools:LostAttachments:Disclaimer' => '可以在此搜索数据库中丢失或错放的附件.这不是数据恢复工具,其无法恢复已删除的数据.',
|
||||
|
||||
'DBTools:LostAttachments:Button:Analyze' => '分析',
|
||||
'DBTools:LostAttachments:Button:Restore' => '还原',
|
||||
'DBTools:LostAttachments:Button:Restore:Confirm' => '此操作无法回退, 请确认是否继续还原.',
|
||||
'DBTools:LostAttachments:Button:Busy' => '请稍后...',
|
||||
|
||||
'DBTools:LostAttachments:Step:Analyze' => '首先, 通过分析数据库来搜索丢失或误挪动的附件.',
|
||||
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults' => '分析结果:',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:None' => '非常好! 所有附件都是正常的.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Some' => '某些附件 (%1$d) 看起来放错了位置. 请检查下面的列表并选择要挪动的文件.',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:Filename' => '文件名',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:CurrentLocation' => '当前位置',
|
||||
'DBTools:LostAttachments:Step:AnalyzeResults:Item:TargetLocation' => '移动到...',
|
||||
|
||||
'DBTools:LostAttachments:Step:RestoreResults' => '还原结果:',
|
||||
'DBTools:LostAttachments:Step:RestoreResults:Results' => '%1$d/%2$d 的附件被还原.',
|
||||
|
||||
'DBTools:LostAttachments:StoredAsInlineImage' => '存储为内嵌图像',
|
||||
'DBTools:LostAttachments:History' => '附件 "%1$s" 已使用数据库工具还原'
|
||||
));
|
||||
|
||||
@@ -75,6 +75,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Attachments:File:Uploader' => 'Uploaded by~~',
|
||||
'Attachments:File:Size' => 'Size~~',
|
||||
'Attachments:File:MimeType' => 'Type~~',
|
||||
'Attachments:File:DownloadsCount' => 'Downloads~~',
|
||||
));
|
||||
//
|
||||
// Class: Attachment
|
||||
@@ -88,3 +89,12 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Class:Attachment/Attribute:contact_id' => 'Contact id~~',
|
||||
'Class:Attachment/Attribute:contact_id+' => '~~',
|
||||
));
|
||||
|
||||
//
|
||||
// Class: TriggerOnAttachmentDownload
|
||||
//
|
||||
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', array(
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
));
|
||||
|
||||
@@ -72,6 +72,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Attachments:File:Uploader' => 'Uploaded by~~',
|
||||
'Attachments:File:Size' => 'Size~~',
|
||||
'Attachments:File:MimeType' => 'Type~~',
|
||||
'Attachments:File:DownloadsCount' => 'Downloads~~',
|
||||
));
|
||||
//
|
||||
// Class: Attachment
|
||||
@@ -85,3 +86,12 @@ Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Class:Attachment/Attribute:contact_id' => 'Contact id~~',
|
||||
'Class:Attachment/Attribute:contact_id+' => '~~',
|
||||
));
|
||||
|
||||
//
|
||||
// Class: TriggerOnAttachmentDownload
|
||||
//
|
||||
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', array(
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
));
|
||||
|
||||
@@ -74,6 +74,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Attachments:File:Uploader' => 'hochgeladen von',
|
||||
'Attachments:File:Size' => 'Größe',
|
||||
'Attachments:File:MimeType' => 'Typ',
|
||||
'Attachments:File:DownloadsCount' => 'Downloads',
|
||||
));
|
||||
//
|
||||
// Class: Attachment
|
||||
@@ -87,3 +88,12 @@ Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:Attachment/Attribute:contact_id' => 'Kontakt ID',
|
||||
'Class:Attachment/Attribute:contact_id+' => '',
|
||||
));
|
||||
|
||||
//
|
||||
// Class: TriggerOnAttachmentDownload
|
||||
//
|
||||
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', array(
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (beim Herunterladen eines Attachment eines Objekts)',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger für das Herunterladen des Attachments der angegebenen Klasse oder einer Unterklasse',
|
||||
));
|
||||
|
||||
@@ -15,7 +15,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/>
|
||||
/**
|
||||
/**
|
||||
* Spanish Localized data
|
||||
*
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
@@ -74,6 +74,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Attachments:File:Uploader' => 'Cargado por',
|
||||
'Attachments:File:Size' => 'Tamaño',
|
||||
'Attachments:File:MimeType' => 'Tipo',
|
||||
'Attachments:File:DownloadsCount' => 'Downloads~~',
|
||||
));
|
||||
//
|
||||
// Class: Attachment
|
||||
@@ -87,3 +88,12 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:Attachment/Attribute:contact_id' => 'Id del Contacto',
|
||||
'Class:Attachment/Attribute:contact_id+' => '',
|
||||
));
|
||||
|
||||
//
|
||||
// Class: TriggerOnAttachmentDownload
|
||||
//
|
||||
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array(
|
||||
'Class:TriggerOnAttachmentDownload' => 'Trigger (on object\'s attachment download)~~',
|
||||
'Class:TriggerOnAttachmentDownload+' => 'Trigger on object\'s attachment download of [a child class of] the given class~~',
|
||||
));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user