Merge branch 'support/3.1.0' into feature/5324-powerportaluser-repairprofiles

This commit is contained in:
odain
2023-09-01 15:15:16 +02:00
762 changed files with 26686 additions and 20666 deletions

View File

@@ -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;
}

View File

@@ -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(

View File

@@ -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();

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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())));

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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)) {

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View 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);
}
}

View File

@@ -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;
}

View File

@@ -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');

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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' => '',

View File

@@ -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;

View File

@@ -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');

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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;
}
}
}
}
}

View File

@@ -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);

View File

@@ -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()
{

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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())));

View File

@@ -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)

View File

@@ -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',

View File

@@ -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',

View File

@@ -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

View File

@@ -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,

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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',
));

View File

@@ -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~~',
));

View File

@@ -22,4 +22,5 @@
Dict::Add('HU HU', 'Hungarian', 'Magyar', array(
'Class:UserLDAP' => 'LDAP felhasználó',
'Class:UserLDAP+' => '',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -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~~',
));

View File

@@ -23,4 +23,5 @@
Dict::Add('JA JP', 'Japanese', '日本語', array(
'Class:UserLDAP' => 'LDAP ユーザー',
'Class:UserLDAP+' => 'LDAP認証ユーザー',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -22,4 +22,5 @@
Dict::Add('PT BR', 'Brazilian', 'Brazilian', array(
'Class:UserLDAP' => 'Usuário externo via LDAP',
'Class:UserLDAP+' => '',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -14,4 +14,5 @@
Dict::Add('RU RU', 'Russian', 'Русский', array(
'Class:UserLDAP' => 'Пользователь LDAP',
'Class:UserLDAP+' => 'Пользователь, аутентифицируемый через LDAP',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -35,4 +35,5 @@
Dict::Add('SK SK', 'Slovak', 'Slovenčina', array(
'Class:UserLDAP' => 'LDAP užívateľ',
'Class:UserLDAP+' => '',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -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~~',
));

View File

@@ -36,4 +36,5 @@
Dict::Add('ZH CN', 'Chinese', '简体中文', array(
'Class:UserLDAP' => 'LDAP 用户',
'Class:UserLDAP+' => '用户身份由LDAP 认证',
'UserLDAP:server' => 'LDAP specifics~~',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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.',
));

View File

@@ -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',
));

View File

@@ -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.',

View File

@@ -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.',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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.',
));

View File

@@ -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',
));

View File

@@ -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',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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' => '不允许用户为自己设置"一次性密码"的失效期限',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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',
));
));

View File

@@ -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',
));

View File

@@ -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~~',
));

View File

@@ -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',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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',
));

View File

@@ -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',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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
*

View File

@@ -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~~'
));

View File

@@ -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~~'
));

View File

@@ -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'
));

View File

@@ -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'
));

View File

@@ -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'
));

View File

@@ -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'
));

View File

@@ -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~~'
));

View File

@@ -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~~'
));

View File

@@ -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'
));

View File

@@ -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'
));

View File

@@ -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'
));

View File

@@ -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" восстановлено с помощью инструментов обслуживания БД'
));

View File

@@ -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~~'
));

View File

@@ -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~~'
));

View File

@@ -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" 已使用数据库工具还原'
));

View File

@@ -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~~',
));

View File

@@ -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~~',
));

View File

@@ -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',
));

View File

@@ -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