diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index 92e585897..497072390 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -3113,10 +3113,22 @@ EOF $sClassIconUrl = MetaModel::GetClassIcon($sClass, false); $oPanel = PanelUIBlockFactory::MakeForClass($sClass, $sTitle) ->SetIcon($sClassIconUrl); + $oPanel->AddMainBlock(self::DisplayFormBlockSelectClassToCreate($sClass, $sClassLabel, $oAppContext, $aPossibleClasses, $aHiddenFields)); + $oP->AddSubBlock($oPanel); + } + /** + * @param string $sClass + * @param string $sClassLabel + * @param array $aPossibleClasses + * + * @return \Combodo\iTop\Application\UI\Base\Component\Form\Form + * @throws \CoreException + */ + public static function DisplayFormBlockSelectClassToCreate( string $sClass, string $sClassLabel, ApplicationContext $oAppContext, array $aPossibleClasses, array $aHiddenFields): Form + { $oClassForm = FormUIBlockFactory::MakeStandard(); - $oPanel->AddMainBlock($oClassForm); $oClassForm->AddHtml($oAppContext->GetForForm()) ->AddSubBlock(InputUIBlockFactory::MakeForHidden('checkSubclass', '0')) @@ -3149,10 +3161,8 @@ EOF } $oClassForm->AddSubBlock(self::DisplayBlockSelectClassToCreate($sClass, $sClassLabel, $aPossibleClasses)); - - $oP->AddSubBlock($oPanel); + return $oClassForm; } - /** * @param string $sClassLabel * @param array $aPossibleClasses diff --git a/css/backoffice/vendors/_selectize.scss b/css/backoffice/vendors/_selectize.scss index 58cd73402..d7621f7a8 100644 --- a/css/backoffice/vendors/_selectize.scss +++ b/css/backoffice/vendors/_selectize.scss @@ -33,15 +33,19 @@ $ibo-vendors-selectize--input-error--border: 1px solid $ibo-color-red-600 !defau display: flex; .selectize-add-option { - position: absolute; - right: $ibo-vendors-selectize-control--plugin-add-button--add-option--right; display: inline-flex; justify-content: center; align-items: center; + + position: absolute; + right: $ibo-vendors-selectize-control--plugin-add-button--add-option--right; + height: $ibo-vendors-selectize-control--plugin-add-button--add-option--height; width: $ibo-vendors-selectize-control--plugin-add-button--add-option--width; z-index: 1; + color: $ibo-vendors-selectize-control--plugin-add-button--add-option--color; + @extend %ibo-font-size-100; } } diff --git a/dictionaries/ui/application/object/en.dictionary.itop.object.php b/dictionaries/ui/application/object/en.dictionary.itop.object.php new file mode 100644 index 000000000..ed9358bf3 --- /dev/null +++ b/dictionaries/ui/application/object/en.dictionary.itop.object.php @@ -0,0 +1,22 @@ + 'Create an object', +)); \ No newline at end of file diff --git a/js/links/links_set_worker.js b/js/links/links-set-worker.js similarity index 98% rename from js/links/links_set_worker.js rename to js/links/links-set-worker.js index abfa8200a..940fad976 100644 --- a/js/links/links_set_worker.js +++ b/js/links/links-set-worker.js @@ -1,4 +1,4 @@ -let CombodoLinkSetWorker = new function(){ +const iTopLinkSetWorker = new function(){ // defines const ROUTER_BASE_URL = '../pages/ajax.render.php'; diff --git a/js/links/links_set.js b/js/links/links-set.js similarity index 60% rename from js/links/links_set.js rename to js/links/links-set.js index 383c2f66e..2e8b11a76 100644 --- a/js/links/links_set.js +++ b/js/links/links-set.js @@ -1,4 +1,4 @@ -let CombodoLinkSet = new function () { +const iTopLinkSet = new function () { /** * Create a new link object and add it to set widget. @@ -12,18 +12,17 @@ let CombodoLinkSet = new function () { * @param oWidget * @constructor */ - const CallCreateLinkedObject = function(sLinkedClass, sCode, sHostObjectClass, sHostObjectKey, sRemoteExtKey, sRemoteClass, oWidget) + const CallCreateLinkedObject = function(sLinkedClass, oWidget) { // Create link object - CombodoLinkSetWorker.CreateLinkedObject(sLinkedClass, sCode, sHostObjectClass, sHostObjectKey, - function(){ + iTopObjectWorker.CreateObject(sLinkedClass, function(){ $(this).find("form").remove(); $(this).dialog('destroy'); }, function(event, data){ // We have just create a link object, now request the remote object - CombodoLinkSetWorker.GetRemoteObject(data.data.object.class_name, data.data.object.key, sRemoteExtKey, sRemoteClass, function(data){ + iTopObjectWorker.GetObject(data.data.object.class_name, data.data.object.key, function(data){ // Add the new remote object in widget set options list const selectize = oWidget[0].selectize; @@ -32,9 +31,6 @@ let CombodoLinkSet = new function () { // Select the new remote object selectize.addItem(data.data.object.key); - - // Add to initial values, to handle remove action - selectize.addInitialValue(data.data.object.key); }); }); } diff --git a/js/links/links_view_table_widget.js b/js/links/links_view_table_widget.js index 488d767b6..b71f372d7 100644 --- a/js/links/links_view_table_widget.js +++ b/js/links/links_view_table_widget.js @@ -34,7 +34,7 @@ $(function() const me = this; // link object deletion - CombodoLinkSetWorker.DeleteLinkedObject(this.options.link_class, sLinkedObjectKey, function (data) { + iTopLinkSetWorker.DeleteLinkedObject(this.options.link_class, sLinkedObjectKey, function (data) { if (data.data.success === true) { me.$tableSettingsDialog.DataTableSettings('DoRefresh'); } else { @@ -55,7 +55,7 @@ $(function() const me = this; // link object unlink - CombodoLinkSetWorker.DetachLinkedObject(this.options.link_class, sLinkedObjectKey, this.options.external_key_to_me, function (data) { + iTopLinkSetWorker.DetachLinkedObject(this.options.link_class, sLinkedObjectKey, this.options.external_key_to_me, function (data) { if (data.data.success === true) { me.$tableSettingsDialog.DataTableSettings('DoRefresh'); } else { @@ -82,7 +82,7 @@ $(function() const sHostObjectId = $Table.closest('[data-role="ibo-object-details"]').attr('data-object-id'); // link object creation - CombodoLinkSetWorker.CreateLinkedObject(sClass, sAttCode, sHostObjectClass, sHostObjectId, function(){ + iTopLinkSetWorker.CreateLinkedObject(sClass, sAttCode, sHostObjectClass, sHostObjectId, function(){ $(this).find("form").remove(); $(this).dialog('destroy'); },function (event, data) { @@ -102,7 +102,7 @@ $(function() const me = this; // link object modification - ObjectWorker.ModifyObject(this.options.link_class, sLinkedObjectKey, function () { + iTopObjectWorker.ModifyObject(this.options.link_class, sLinkedObjectKey, function () { $(this).find("form").remove(); $(this).dialog('destroy'); }, function(event, data){ diff --git a/js/objects/objects_worker.js b/js/object/object-worker.js similarity index 66% rename from js/objects/objects_worker.js rename to js/object/object-worker.js index 216562ef7..f389e9c95 100644 --- a/js/objects/objects_worker.js +++ b/js/object/object-worker.js @@ -1,10 +1,32 @@ -let ObjectWorker = new function(){ +const iTopObjectWorker = new function(){ // defines const ROUTER_BASE_URL = '../pages/ajax.render.php'; + const ROUTE_CREATE_OBJECT = 'object.new'; const ROUTE_MODIFY_OBJECT = 'object.modify'; const ROUTE_GET_OBJECT = 'object.get'; + const CallAjaxCreateObject = function(sClass, oOnModalCloseCallback = null, oOnFormSubmittedCallback = null){ + + let oOptions = { + title: Dict.S('UI:Object:Modal:Title'), + content: { + endpoint: `${ROUTER_BASE_URL}?route=${ROUTE_CREATE_OBJECT}`, + data: { + class: sClass, + } + }, + extra_options: { + callback_on_modal_close: oOnModalCloseCallback + }, + } + + const oModal = CombodoModal.OpenModal(oOptions); + if(oOnFormSubmittedCallback !== null){ + oModal.on('itop.form.submitted', 'form', oOnFormSubmittedCallback); + } + }; + /** * CallAjaxModifyObject. * @@ -40,7 +62,7 @@ let ObjectWorker = new function(){ * CallAjaxGetObject. * * @param {string} sObjectClass - * @param {string} sObjectKey + * @param {string} sObjectId * @param oOnResponseCallback * @constructor */ @@ -54,6 +76,7 @@ let ObjectWorker = new function(){ return { + CreateObject: CallAjaxCreateObject, ModifyObject: CallAjaxModifyObject, GetObject: CallAjaxGetObject } diff --git a/js/selectize/plugin_combodo_add_button.js b/js/selectize/plugin_combodo_add_button.js index b3671a375..f357b4477 100644 --- a/js/selectize/plugin_combodo_add_button.js +++ b/js/selectize/plugin_combodo_add_button.js @@ -28,7 +28,7 @@ Selectize.define("combodo_add_button", function (aOptions) { label: "+", html: function () { return ( - '' + '' ); }, }, diff --git a/pages/UI.php b/pages/UI.php index 976a1ef67..12c356f75 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -704,80 +704,12 @@ try break; /////////////////////////////////////////////////////////////////////////////////////////// - + + /** @deprecated 3.1.0 Use the "object.new" route instead */ + // Kept for backward compatibility case 'new': // Form to create a new object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sStateCode = utils::ReadParam('state', ''); - $bCheckSubClass = utils::ReadParam('checkSubclass', true); - if ( empty($sClass) ) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - - /* - $aArgs = utils::ReadParam('default', array(), false, 'raw_data'); - $aContext = $oAppContext->GetAsHash(); - foreach( $oAppContext->GetNames() as $key) - { - $aArgs[$key] = $oAppContext->GetCurrentValue($key); - } - */ - // If the specified class has subclasses, ask the user an instance of which class to create - $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself - $aPossibleClasses = array(); - $sRealClass = ''; - if ($bCheckSubClass) - { - foreach($aSubClasses as $sCandidateClass) - { - if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) - { - $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); - } - } - // Only one of the subclasses can be instantiated... - if (count($aPossibleClasses) == 1) - { - $aKeys = array_keys($aPossibleClasses); - $sRealClass = $aKeys[0]; - } - } - else - { - $sRealClass = $sClass; - } - - if (!empty($sRealClass)) - { - // Set all the default values in an object and clone this "default" object - $oObjToClone = MetaModel::NewObject($sRealClass); - // 1st - set context values - $oAppContext->InitObjectFromContext($oObjToClone); - // 2nd - set values from the page argument 'default' - $oObjToClone->UpdateObjectFromArg('default'); - $aPrefillFormParam = array( - 'user' => Session::Get('auth_user'), - 'context' => $oAppContext->GetAsHash(), - 'default' => utils::ReadParam('default', array(), '', 'raw_data'), - 'origin' => 'console', - ); - // 3rd - prefill API - $oObjToClone->PrefillForm('creation_from_0', $aPrefillFormParam); - - // Display the creation form - $sClassLabel = MetaModel::GetName($sRealClass); - $sClassIcon = MetaModel::GetClassIcon($sRealClass); - $sObjectTmpKey = $oObjToClone->GetKey(); - $sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); - // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found - $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); - $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObjToClone, cmdbAbstractObject::ENUM_DISPLAY_MODE_CREATE)); - cmdbAbstractObject::DisplayCreationForm($oP, $sRealClass, $oObjToClone, array(), array('wizard_container' => 1, 'keep_source_object' => true)); // wizard_container: Display the title above the form - } else { - // Select the derived class to create - cmdbAbstractObject::DisplaySelectClassToCreate($sClass, $oP, $oAppContext, $aPossibleClasses,['state' => $sStateCode]); - } + $oController = new ObjectController(); + $oP = $oController->OperationNew(); break; /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/sources/Application/UI/Base/Component/Input/Set/Set.php b/sources/Application/UI/Base/Component/Input/Set/Set.php index 2f9663007..92f920e2f 100644 --- a/sources/Application/UI/Base/Component/Input/Set/Set.php +++ b/sources/Application/UI/Base/Component/Input/Set/Set.php @@ -37,7 +37,8 @@ class Set extends AbstractInput public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'base/components/input/set/layout'; public const DEFAULT_JS_FILES_REL_PATH = [ - 'js/links/links_set_worker.js', + 'js/links/links-set-worker.js', + 'js/object/object-worker.js', 'js/selectize/plugin_combodo_add_button.js', 'js/selectize/plugin_combodo_auto_position.js', 'js/selectize/plugin_combodo_update_operations.js', diff --git a/sources/Application/UI/Links/AbstractBlockLinksViewTable.php b/sources/Application/UI/Links/AbstractBlockLinksViewTable.php index bb37d984e..6f698ee26 100644 --- a/sources/Application/UI/Links/AbstractBlockLinksViewTable.php +++ b/sources/Application/UI/Links/AbstractBlockLinksViewTable.php @@ -36,8 +36,8 @@ abstract class AbstractBlockLinksViewTable extends UIContentBlock public const DEFAULT_JS_ON_READY_TEMPLATE_REL_PATH = 'application/links/layout'; public const DEFAULT_JS_FILES_REL_PATH = [ 'js/links/links_view_table_widget.js', - 'js/links/links_set_worker.js', - 'js/objects/objects_worker.js', + 'js/links/links-set-worker.js', + 'js/object/object-worker.js', 'js/wizardhelper.js', ]; diff --git a/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php b/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php index e53a4975b..d8b045ad4 100644 --- a/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php +++ b/sources/Application/UI/Links/Set/LinksSetUIBlockFactory.php @@ -59,18 +59,14 @@ class LinksSetUIBlockFactory extends SetUIBlockFactory // Set UI block for OQL $oSetUIBlock = SetUIBlockFactory::MakeForOQL($sId, $sTargetClass, $oAttDef->GetValuesDef()->GetFilterExpression(), $sWizardHelperJsVarName); - $oSetUIBlock->AddJsFileRelPath('js/links/links_set.js'); + $oSetUIBlock->AddJsFileRelPath('js/links/links-set.js'); -// Remove add button for 3_1_lot1 -// Linkset controller OperationCreateLinkedObject need the host object to exist, so if we are in creation of the host object (id=-1) the linked object creation doesn't work. -// -// // Add button behaviour -// if (in_array($oAttDef->GetEditMode(), [LINKSET_EDITMODE_ADDREMOVE, LINKSET_EDITMODE_ADDONLY, LINKSET_EDITMODE_INPLACE, LINKSET_EDITMODE_ACTIONS]) -// && $oHostDbObject !== null) { -// $sHostClass = get_class($oHostDbObject); -// $oSetUIBlock->SetHasAddOptionButton(true); -// $oSetUIBlock->SetAddOptionButtonJsOnClick("CombodoLinkSet.CreateLinkedObject('{$oAttDef->GetLinkedClass()}', '{$oAttDef->GetCode()}', '{$sHostClass}', '{$oHostDbObject->GetKey()}', '{$sTargetField}', '{$sTargetClass}', oWidget{$oSetUIBlock->GetId()} );"); -// } + // Add button behaviour + if (in_array($oAttDef->GetEditMode(), [LINKSET_EDITMODE_ADDREMOVE, LINKSET_EDITMODE_ADDONLY, LINKSET_EDITMODE_INPLACE, LINKSET_EDITMODE_ACTIONS]) + && $oHostDbObject !== null) { + $oSetUIBlock->SetHasAddOptionButton(true); + $oSetUIBlock->SetAddOptionButtonJsOnClick("iTopLinkSet.CreateLinkedObject('{$sTargetClass}', oWidget{$oSetUIBlock->GetId()} );"); + } // Current value $aCurrentValues = LinkSetDataTransformer::Decode($oDbObjectSet, $sTargetClass, $sTargetField); diff --git a/sources/Application/WebPage/iTopWebPage.php b/sources/Application/WebPage/iTopWebPage.php index 8f7d29aa4..ee41131fa 100644 --- a/sources/Application/WebPage/iTopWebPage.php +++ b/sources/Application/WebPage/iTopWebPage.php @@ -219,6 +219,7 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage // Modals $this->add_dict_entries('UI:Modal:'); $this->add_dict_entries('UI:Links:'); + $this->add_dict_entries('UI:Object:'); $this->add_dict_entry('UI:Layout:ObjectDetails:New:Modal:Title'); } diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php index 2e6beeb2a..46827e424 100644 --- a/sources/Controller/Base/Layout/ObjectController.php +++ b/sources/Controller/Base/Layout/ObjectController.php @@ -7,9 +7,11 @@ namespace Combodo\iTop\Controller\Base\Layout; use AjaxPage; +use ApplicationContext; use ApplicationException; use cmdbAbstractObject; use CMDBObjectSet; +use Combodo\iTop\Application\Helper\Session; use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory; use Combodo\iTop\Application\UI\Base\Component\QuickCreate\QuickCreateHelper; use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; @@ -40,6 +42,161 @@ class ObjectController extends AbstractController { public const ROUTE_NAMESPACE = 'object'; + /** + * @throws \CoreException + * @throws \MySQLHasGoneAwayException + * @throws \MySQLException + * @throws \DictExceptionMissingString + * @throws \CoreUnexpectedValue + * @throws \ConfigException + * @throws \ApplicationException + * @throws \MissingQueryArgument + */ + public function OperationNew() + { + $bPrintable = utils::ReadParam('printable', '0') === '1'; + $sClass = utils::ReadParam('class', '', false, 'class'); + $sStateCode = utils::ReadParam('state', ''); + $bCheckSubClass = utils::ReadParam('checkSubclass', true); + $oAppContext = new ApplicationContext(); + + if ($this->IsHandlingXmlHttpRequest()) { + $oPage = new AjaxPage(''); + } else { + $oPage = new iTopWebPage('', $bPrintable); + $oPage->DisableBreadCrumb(); + $this->AddRequiredForModificationJsFilesToPage($oPage); + } + + + if (empty($sClass)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + + // If the specified class has subclasses, ask the user an instance of which class to create + $aSubClasses = MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL); // Including the specified class itself + $aPossibleClasses = array(); + $sRealClass = ''; + if ($bCheckSubClass) + { + foreach($aSubClasses as $sCandidateClass) + { + if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) + { + $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); + } + } + // Only one of the subclasses can be instantiated... + if (count($aPossibleClasses) === 1) + { + $aKeys = array_keys($aPossibleClasses); + $sRealClass = $aKeys[0]; + } + } + else + { + $sRealClass = $sClass; + } + + if (!empty($sRealClass)) + { + // Set all the default values in an object and clone this "default" object + $oObjToClone = MetaModel::NewObject($sRealClass); + // 1st - set context values + $oAppContext->InitObjectFromContext($oObjToClone); + // 2nd - set values from the page argument 'default' + $oObjToClone->UpdateObjectFromArg('default'); + $aPrefillFormParam = array( + 'user' => Session::Get('auth_user'), + 'context' => $oAppContext->GetAsHash(), + 'default' => utils::ReadParam('default', array(), '', 'raw_data'), + 'origin' => 'console', + ); + // 3rd - prefill API + $oObjToClone->PrefillForm('creation_from_0', $aPrefillFormParam); + + // Display the creation form + $sClassLabel = MetaModel::GetName($sRealClass); + $sClassIcon = MetaModel::GetClassIcon($sRealClass); + $sObjectTmpKey = $oObjToClone->GetKey(); + $sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); + // Note: some code has been duplicated to the case 'apply_new' when a data integrity issue has been found + + $aFormExtraParams = array('wizard_container' => 1, 'keep_source_object' => true); + + if ($this->IsHandlingXmlHttpRequest()) { + $aFormExtraParams['js_handlers'] = []; + $aFormExtraParams['noRelations'] = true; + $aFormExtraParams['hide_transitions'] = true; + // Add a random prefix to avoid ID collision for form elements + $aFormExtraParams['formPrefix'] = utils::Sanitize(uniqid('', true), '', utils::ENUM_SANITIZATION_FILTER_ELEMENT_IDENTIFIER).'_'; + // We display this form in a modal, once we submit (in ajax) we probably want to only close the modal + $aFormExtraParams['js_handlers']['form_on_submit'] = + <<set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); + $oPage->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObjToClone, cmdbAbstractObject::ENUM_DISPLAY_MODE_CREATE)); + } + cmdbAbstractObject::DisplayCreationForm($oPage, $sRealClass, $oObjToClone, array(), $aFormExtraParams); + } else { + if ($this->IsHandlingXmlHttpRequest()) { + $oClassForm = cmdbAbstractObject::DisplayFormBlockSelectClassToCreate($sClass, MetaModel::GetName($sClass), $oAppContext, $aPossibleClasses, ['state' => $sStateCode]); + $sCurrentUrl = utils::GetAbsoluteUrlAppRoot().'/pages/UI.php?route=object.new'; + $oClassForm->SetOnSubmitJsCode( + <<AddUiBlock($oClassForm); + } + else{ + cmdbAbstractObject::DisplaySelectClassToCreate($sClass, $oPage, $oAppContext, $aPossibleClasses,['state' => $sStateCode]); + } + } + return $oPage; + } + /** * @return \iTopWebPage|\AjaxPage Object edit form in its webpage * @throws \ApplicationException @@ -603,7 +760,7 @@ JS; // Retrieve query params $sObjectClass = utils::ReadParam('object_class', '', false, utils::ENUM_SANITIZATION_FILTER_STRING); - $sObjectKey = utils::ReadParam('object_key', '', false, utils::ENUM_SANITIZATION_FILTER_STRING); + $sObjectKey = utils::ReadParam('object_key', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER); // Retrieve object try { diff --git a/sources/Controller/Links/LinksetController.php b/sources/Controller/Links/LinksetController.php index f25e151ad..f1040c352 100644 --- a/sources/Controller/Links/LinksetController.php +++ b/sources/Controller/Links/LinksetController.php @@ -270,7 +270,7 @@ JS try { $oObject = MetaModel::GetObject($sObjectClass, $sObjectKey); $sLinkKey = $oObject->GetKey(); - if ($sRemoteExtKey !== null) { + if (!utils::IsNullOrEmptyString($sRemoteExtKey)) { $oObject = MetaModel::GetObject($sRemoteClass, $oObject->Get($sRemoteExtKey)); } $aObjectData = ObjectRepository::ConvertObjectToArray($oObject, $sObjectClass);