From a553616ea2b7049b0639eba3b8fcd9de3e399d30 Mon Sep 17 00:00:00 2001 From: Molkobain Date: Thu, 10 Nov 2022 10:44:51 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B05655=20-=20Introduce=20auto-routing=20me?= =?UTF-8?q?chanism=20for=20backoffice=20pages=20(Part=201)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/composer/autoload_classmap.php | 2 + lib/composer/autoload_static.php | 2 + pages/UI.php | 2794 +++++------ pages/ajax.render.php | 4391 ++++++++--------- sources/Controller/AbstractController.php | 10 +- .../Base/Layout/ObjectController.php | 13 +- sources/Controller/iController.php | 30 + sources/Router/Router.php | 183 + test/sources/Router/RouterTest.php | 205 + 9 files changed, 3965 insertions(+), 3665 deletions(-) create mode 100644 sources/Controller/iController.php create mode 100644 sources/Router/Router.php create mode 100644 test/sources/Router/RouterTest.php diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index e43f2704e..4880cf5a7 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -352,6 +352,7 @@ return array( 'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => $baseDir . '/sources/Controller/Base/Layout/ObjectController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => $baseDir . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => $baseDir . '/sources/Controller/PreferencesController.php', + 'Combodo\\iTop\\Controller\\iController' => $baseDir . '/sources/Controller/iController.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => $baseDir . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', @@ -415,6 +416,7 @@ return array( 'Combodo\\iTop\\Renderer\\FieldRenderer' => $baseDir . '/sources/Renderer/FieldRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => $baseDir . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => $baseDir . '/sources/Renderer/RenderingOutput.php', + 'Combodo\\iTop\\Router\\Router' => $baseDir . '/sources/Router/Router.php', 'Combodo\\iTop\\Service\\EventData' => $baseDir . '/sources/Application/Service/EventData.php', 'Combodo\\iTop\\Service\\EventHelper' => $baseDir . '/sources/Application/Service/EventHelper.php', 'Combodo\\iTop\\Service\\EventService' => $baseDir . '/sources/Application/Service/EventService.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index cca9b11d4..4a4902037 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -717,6 +717,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Controller\\Base\\Layout\\ObjectController' => __DIR__ . '/../..' . '/sources/Controller/Base/Layout/ObjectController.php', 'Combodo\\iTop\\Controller\\OAuth\\OAuthLandingController' => __DIR__ . '/../..' . '/sources/Controller/OAuth/OAuthLandingController.php', 'Combodo\\iTop\\Controller\\PreferencesController' => __DIR__ . '/../..' . '/sources/Controller/PreferencesController.php', + 'Combodo\\iTop\\Controller\\iController' => __DIR__ . '/../..' . '/sources/Controller/iController.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\IOAuthClientProvider' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/IOAuthClientProvider.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAbstract' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAbstract.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderAzure' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderAzure.php', @@ -780,6 +781,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\Renderer\\FieldRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FieldRenderer.php', 'Combodo\\iTop\\Renderer\\FormRenderer' => __DIR__ . '/../..' . '/sources/Renderer/FormRenderer.php', 'Combodo\\iTop\\Renderer\\RenderingOutput' => __DIR__ . '/../..' . '/sources/Renderer/RenderingOutput.php', + 'Combodo\\iTop\\Router\\Router' => __DIR__ . '/../..' . '/sources/Router/Router.php', 'Combodo\\iTop\\Service\\EventData' => __DIR__ . '/../..' . '/sources/Application/Service/EventData.php', 'Combodo\\iTop\\Service\\EventHelper' => __DIR__ . '/../..' . '/sources/Application/Service/EventHelper.php', 'Combodo\\iTop\\Service\\EventService' => __DIR__ . '/../..' . '/sources/Application/Service/EventService.php', diff --git a/pages/UI.php b/pages/UI.php index 90b4a54b2..c1e503e42 100644 --- a/pages/UI.php +++ b/pages/UI.php @@ -20,6 +20,7 @@ use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock; use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory; use Combodo\iTop\Controller\Base\Layout\ObjectController; +use Combodo\iTop\Router\Router; /** * Displays a popup welcome message, once per session at maximum @@ -299,15 +300,12 @@ require_once(APPROOT.'/application/wizardhelper.class.inc.php'); require_once(APPROOT.'/application/startup.inc.php'); - try { - $operation = utils::ReadParam('operation', '', false, utils::ENUM_SANITIZATION_FILTER_OPERATION); - $bPrintable = (utils::ReadParam('printable', 0) == '1'); - $oKPI = new ExecutionKPI(); $oKPI->ComputeAndReport('Data model loaded'); + $oKPI = new ExecutionKPI(); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); $sLoginMessage = LoginWebPage::DoLogin(); // Check user rights and prompt if needed @@ -315,1527 +313,1549 @@ try $oKPI->ComputeAndReport('User login'); - $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable); - $oP->SetMessage($sLoginMessage); + // First check if we can redirect the route to a dedicated controller + $sRoute = utils::ReadParam('route', '', false, utils::ENUM_SANITIZATION_FILTER_ROUTE); + $oRouter = Router::GetInstance(); + if ($oRouter->CanDispatchRoute($sRoute)) { + $mResponse = $oRouter->DispatchRoute($sRoute); - // All the following actions use advanced forms that require more javascript to be loaded - switch($operation) - { - case 'new': // Form to create a new object - case 'apply_new': // Creation of a new object - case 'apply_modify': // Applying the modifications to an existing object - case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) - case 'bulk_stimulus': // For to apply a stimulus to multiple objects - case 'stimulus': // Form displayed when applying a stimulus (state change) - case 'apply_stimulus': // Form displayed when applying a stimulus (state change) - foreach (ObjectController::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { - $oP->add_linked_script("../$sJsFileRelPath"); - } - break; + // If response isn't a \WebPage, it is most likely that the output already occured, stop the script. + // Note that this is done here and not directly in the Router::DispatchRoute() so custom endpoint can handle null responses their own way. + if (false === ($mResponse instanceof WebPage)) { + die(); + } + + // Response is a \WebPage, let's handle it like legacy operations + $oP = $mResponse; + // TODO 3.1: If no route match, die instead of fallback to legacy operation dispatch and dump available routes if in dev env. } - - switch($operation) - { - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'details': // Details of an object - $sClass = utils::ReadParam('class', '', false, 'class'); + // Otherwise, use legacy operation + else { + $operation = utils::ReadParam('operation', '', false, utils::ENUM_SANITIZATION_FILTER_OPERATION); + $bPrintable = (utils::ReadParam('printable', 0) == '1'); - if (empty($sClass)) { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } + $oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable); + $oP->SetMessage($sLoginMessage); - $id = utils::ReadParam('id', null); - if (false === is_null($id)) { - if (is_numeric($id)) { - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + // All the following actions use advanced forms that require more javascript to be loaded + switch($operation) + { + case 'new': // Form to create a new object + case 'apply_new': // Creation of a new object + case 'apply_modify': // Applying the modifications to an existing object + case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) + case 'bulk_stimulus': // For to apply a stimulus to multiple objects + case 'stimulus': // Form displayed when applying a stimulus (state change) + case 'apply_stimulus': // Form displayed when applying a stimulus (state change) + foreach (ObjectController::EnumRequiredForModificationJsFilesRelPaths() as $sJsFileRelPath) { + $oP->add_linked_script("../$sJsFileRelPath"); + } + break; + } + + switch($operation) + { + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'details': // Details of an object + $sClass = utils::ReadParam('class', '', false, 'class'); + + if (empty($sClass)) { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + + $id = utils::ReadParam('id', null); + if (false === is_null($id)) { + if (is_numeric($id)) { + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); + } else { + $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); + } } else { - $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); - } - } else { - $sAttCode = utils::ReadParam('attcode', '', false, utils::ENUM_SANITIZATION_FILTER_FIELD_NAME); - $sAttValue = utils::ReadParam('attvalue', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + $sAttCode = utils::ReadParam('attcode', '', false, utils::ENUM_SANITIZATION_FILTER_FIELD_NAME); + $sAttValue = utils::ReadParam('attvalue', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); - if ((strlen($sAttCode) === 0) || (strlen($sAttValue) === 0)) { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id')); + if ((strlen($sAttCode) === 0) || (strlen($sAttValue) === 0)) { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'id')); + } + + $oObj = MetaModel::GetObjectByColumn($sClass, $sAttCode, $sAttValue, true); } - $oObj = MetaModel::GetObjectByColumn($sClass, $sAttCode, $sAttValue, true); - } - - if (is_null($oObj)) { - // Check anyhow if there is a message for this object (like you've just created it) - $sMessageKey = $sClass.'::'.$id; - DisplayMessages($sMessageKey, $oP); - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - - // Attempt to load the object in archive mode - utils::PushArchiveMode(true); - if (is_numeric($id)) { - $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); - } - else - { - $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); - } - utils::PopArchiveMode(); - if (is_null($oObj)) - { - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - else - { - SetObjectBreadCrumbEntry($oObj, $oP); - $oP->P(Dict::S('UI:ObjectArchived')); - } - } - else - { - try - { - $oObj->Reload(); - } - catch(Exception $e) - { - // Probably not allowed to see this instance of a derived class - + if (is_null($oObj)) { // Check anyhow if there is a message for this object (like you've just created it) $sMessageKey = $sClass.'::'.$id; DisplayMessages($sMessageKey, $oP); - - $oObj = null; $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - if (!is_null($oObj)) - { - SetObjectBreadCrumbEntry($oObj, $oP); - // The object could be listed, check if it is actually allowed to view it - $oSet = CMDBObjectSet::FromObject($oObj); - if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) { - throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); + // Attempt to load the object in archive mode + utils::PushArchiveMode(true); + if (is_numeric($id)) { + $oObj = MetaModel::GetObject($sClass, $id, false /* MustBeFound */); } - - $sClassLabel = MetaModel::GetName($sClass); - $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj, $oP->IsPrintableVersion() ? cmdbAbstractObject::ENUM_DISPLAY_MODE_PRINT : cmdbAbstractObject::ENUM_DISPLAY_MODE_VIEW)); - $oObj->DisplayDetails($oP); - } - } - break; - - case 'release_lock_and_details': - $oP->DisableBreadCrumb(); - - // Retrieve object - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - $oObj = MetaModel::GetObject($sClass, $id); - - // Retrieve ownership token - $sToken = utils::ReadParam('token', ''); - if ($sToken != '') - { - iTopOwnershipLock::ReleaseLock($sClass, $id, $sToken); - } - - $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj, cmdbAbstractObject::ENUM_DISPLAY_MODE_VIEW)); - cmdbAbstractObject::ReloadAndDisplay($oP, $oObj, array('operation' => 'details')); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search_oql': // OQL query - $sOQLClass = utils::ReadParam('oql_class', '', false, 'class'); - $sBaseClass = utils::ReadParam('base_class', $sOQLClass, false, 'class'); - $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - $sTitle = utils::ReadParam('title', 'UI:SearchResultsPageTitle'); - if (empty($sOQLClass)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql_class')); - } - $oP->set_title(Dict::S($sTitle)); - $oP->add('

'.Dict::S($sTitle).'

'); - $sOQL = "SELECT $sOQLClass $sOQLClause"; - try - { - $oFilter = DBObjectSearch::FromOQL($sOQL); - DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat); - } - catch(CoreException $e) - { - $oFilter = new DBObjectSearch($sOQLClass); - $oSet = new DBObjectSet($oFilter); - if ($bSearchForm) - { - $oBlock = new DisplayBlock($oFilter, 'search', false); - $oBlock->Display($oP, 0, array('table_id' => 'search-widget-result-outer')); - } - $oP->add('

'.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).'

'); - } - catch(Exception $e) - { - $oP->P(''.Dict::Format('UI:Error:AnErrorOccuredWhileRunningTheQuery_Message', $e->getMessage()).''); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search_form': // Search form - $sClass = utils::ReadParam('class', '', false, 'class'); - $sFormat = utils::ReadParam('format', 'html'); - $bSearchForm = utils::ReadParam('search_form', true); - $bDoSearch = utils::ReadParam('do_search', true); - if (empty($sClass)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $oFilter = new DBObjectSearch($sClass); - DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'search': // Serialized DBSearch - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sFormat = utils::ReadParam('format', ''); - $bSearchForm = utils::ReadParam('search_form', true); - if (empty($sFilter)) - { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); - } - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid - $oFilter->UpdateContextFromUser(); - DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'full_text': // Global "google-like" search - $oP->DisableBreadCrumb(); - $sQuery = trim(utils::ReadParam('text', '', false, 'raw_data')); - $iTune = utils::ReadParam('tune', 0); - if (empty($sQuery)) - { - $oP->p(Dict::S('UI:Search:NoSearch')); - } - else - { - $iErrors = 0; - $sFullText = $sQuery; - - // Check if a class name/label is supplied to limit the search - $sClassName = ''; - if (preg_match('/^([^\"]+):(.+)$/', $sFullText, $aMatches)) - { - $sClassName = $aMatches[1]; - if (MetaModel::IsValidClass($sClassName)) + else { - $sFullText = trim($aMatches[2]); + $oObj = MetaModel::GetObjectByName($sClass, $id, false /* MustBeFound */); } - elseif ($sClassName = MetaModel::GetClassFromLabel($sClassName, false /* => not case sensitive */)) + utils::PopArchiveMode(); + if (is_null($oObj)) { - $sFullText = trim($aMatches[2]); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + else + { + SetObjectBreadCrumbEntry($oObj, $oP); + $oP->P(Dict::S('UI:ObjectArchived')); } - } - if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) - { - // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression - $aFullTextNeedles = array($aMatches[1]); } else - { - // Split the text on the blanks and treat this as a search for AND AND - $aExplodedFullTextNeedles = explode(' ', $sFullText); - $aFullTextNeedles = []; - foreach ($aExplodedFullTextNeedles as $sValue) { - if (strlen($sValue) > 0) { - $aFullTextNeedles[] = $sValue; - } - } - } - - // Save search to history - // - Prepare icon - $sQueryIconUrl = null; - if(!empty($sClassName)) { - $sQueryIconUrl = MetaModel::GetClassIcon($sClassName, false); - } - // - Prepare label - $sQueryLabel = null; - if ($sQuery !== $sFullText) { - $sQueryLabel = $sFullText; - } - GlobalSearchHelper::AddQueryToHistory($sQuery, $sQueryIconUrl, $sQueryLabel); - $oP->SetBlockParam('ibo-global-search.sQuery', $sQuery); - - // Check the needle length - $iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min'); - foreach ($aFullTextNeedles as $sNeedle) { - if (strlen($sNeedle) < $iMinLenth) { - $oP->p(Dict::Format('UI:Search:NeedleTooShort', $sNeedle, $iMinLenth)); - $key = array_search($sNeedle, $aFullTextNeedles); - if ($key !== false) - { - unset($aFullTextNeedles[$key]); - } - } - } - if(empty($aFullTextNeedles)) - { - $oP->p(Dict::S('UI:Search:NoSearch')); - break; - } - $sFullText = implode(' ', $aFullTextNeedles); - - // Sanity check of the accelerators - /** @var array $aAccelerators */ - $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); - foreach ($aAccelerators as $sClass => $aAccelerator) { try { - $bSkip = array_key_exists('skip', $aAccelerator) ? $aAccelerator['skip'] : false; - if (!$bSkip) + $oObj->Reload(); + } + catch(Exception $e) + { + // Probably not allowed to see this instance of a derived class + + // Check anyhow if there is a message for this object (like you've just created it) + $sMessageKey = $sClass.'::'.$id; + DisplayMessages($sMessageKey, $oP); + + $oObj = null; + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + if (!is_null($oObj)) + { + SetObjectBreadCrumbEntry($oObj, $oP); + + // The object could be listed, check if it is actually allowed to view it + $oSet = CMDBObjectSet::FromObject($oObj); + if (UserRights::IsActionAllowed($sClass, UR_ACTION_READ, $oSet) == UR_ALLOWED_NO) { + throw new SecurityException('User not allowed to view this object', array('class' => $sClass, 'id' => $id)); + } + + $sClassLabel = MetaModel::GetName($sClass); + $oP->set_title(Dict::Format('UI:DetailsPageTitle', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj, $oP->IsPrintableVersion() ? cmdbAbstractObject::ENUM_DISPLAY_MODE_PRINT : cmdbAbstractObject::ENUM_DISPLAY_MODE_VIEW)); + $oObj->DisplayDetails($oP); + } + } + break; + + case 'release_lock_and_details': + $oP->DisableBreadCrumb(); + + // Retrieve object + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', ''); + $oObj = MetaModel::GetObject($sClass, $id); + + // Retrieve ownership token + $sToken = utils::ReadParam('token', ''); + if ($sToken != '') + { + iTopOwnershipLock::ReleaseLock($sClass, $id, $sToken); + } + + $oP->SetContentLayout(PageContentFactory::MakeForObjectDetails($oObj, cmdbAbstractObject::ENUM_DISPLAY_MODE_VIEW)); + cmdbAbstractObject::ReloadAndDisplay($oP, $oObj, array('operation' => 'details')); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search_oql': // OQL query + $sOQLClass = utils::ReadParam('oql_class', '', false, 'class'); + $sBaseClass = utils::ReadParam('base_class', $sOQLClass, false, 'class'); + $sOQLClause = utils::ReadParam('oql_clause', '', false, 'raw_data'); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + $sTitle = utils::ReadParam('title', 'UI:SearchResultsPageTitle'); + if (empty($sOQLClass)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'oql_class')); + } + $oP->set_title(Dict::S($sTitle)); + $oP->add('

'.Dict::S($sTitle).'

'); + $sOQL = "SELECT $sOQLClass $sOQLClause"; + try + { + $oFilter = DBObjectSearch::FromOQL($sOQL); + DisplaySearchSet($oP, $oFilter, $bSearchForm, $sBaseClass, $sFormat); + } + catch(CoreException $e) + { + $oFilter = new DBObjectSearch($sOQLClass); + $oSet = new DBObjectSet($oFilter); + if ($bSearchForm) + { + $oBlock = new DisplayBlock($oFilter, 'search', false); + $oBlock->Display($oP, 0, array('table_id' => 'search-widget-result-outer')); + } + $oP->add('

'.Dict::Format('UI:Error:IncorrectOQLQuery_Message', $e->getHtmlDesc()).'

'); + } + catch(Exception $e) + { + $oP->P(''.Dict::Format('UI:Error:AnErrorOccuredWhileRunningTheQuery_Message', $e->getMessage()).''); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search_form': // Search form + $sClass = utils::ReadParam('class', '', false, 'class'); + $sFormat = utils::ReadParam('format', 'html'); + $bSearchForm = utils::ReadParam('search_form', true); + $bDoSearch = utils::ReadParam('do_search', true); + if (empty($sClass)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); + } + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $oFilter = new DBObjectSearch($sClass); + DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat, $bDoSearch, true /* Search Form Expanded */); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'search': // Serialized DBSearch + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sFormat = utils::ReadParam('format', ''); + $bSearchForm = utils::ReadParam('search_form', true); + if (empty($sFilter)) + { + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); + } + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid + $oFilter->UpdateContextFromUser(); + DisplaySearchSet($oP, $oFilter, $bSearchForm, '' /* sBaseClass */, $sFormat); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'full_text': // Global "google-like" search + $oP->DisableBreadCrumb(); + $sQuery = trim(utils::ReadParam('text', '', false, 'raw_data')); + $iTune = utils::ReadParam('tune', 0); + if (empty($sQuery)) + { + $oP->p(Dict::S('UI:Search:NoSearch')); + } + else + { + $iErrors = 0; + $sFullText = $sQuery; + + // Check if a class name/label is supplied to limit the search + $sClassName = ''; + if (preg_match('/^([^\"]+):(.+)$/', $sFullText, $aMatches)) + { + $sClassName = $aMatches[1]; + if (MetaModel::IsValidClass($sClassName)) { - $oSearch = DBObjectSearch::FromOQL($aAccelerator['query']); - if ($sClass != $oSearch->GetClass()) - { - $oP->p("Full text accelerator for class '$sClass': searched class mismatch (".$oSearch->GetClass().")"); - $iErrors++; + $sFullText = trim($aMatches[2]); + } + elseif ($sClassName = MetaModel::GetClassFromLabel($sClassName, false /* => not case sensitive */)) + { + $sFullText = trim($aMatches[2]); + } + } + if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) + { + // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression + $aFullTextNeedles = array($aMatches[1]); + } + else + { + // Split the text on the blanks and treat this as a search for AND AND + $aExplodedFullTextNeedles = explode(' ', $sFullText); + $aFullTextNeedles = []; + foreach ($aExplodedFullTextNeedles as $sValue) { + if (strlen($sValue) > 0) { + $aFullTextNeedles[] = $sValue; } } } - catch (OqlException $e) + + // Save search to history + // - Prepare icon + $sQueryIconUrl = null; + if(!empty($sClassName)) { + $sQueryIconUrl = MetaModel::GetClassIcon($sClassName, false); + } + // - Prepare label + $sQueryLabel = null; + if ($sQuery !== $sFullText) { + $sQueryLabel = $sFullText; + } + GlobalSearchHelper::AddQueryToHistory($sQuery, $sQueryIconUrl, $sQueryLabel); + $oP->SetBlockParam('ibo-global-search.sQuery', $sQuery); + + // Check the needle length + $iMinLenth = MetaModel::GetConfig()->Get('full_text_needle_min'); + foreach ($aFullTextNeedles as $sNeedle) { + if (strlen($sNeedle) < $iMinLenth) { + $oP->p(Dict::Format('UI:Search:NeedleTooShort', $sNeedle, $iMinLenth)); + $key = array_search($sNeedle, $aFullTextNeedles); + if ($key !== false) + { + unset($aFullTextNeedles[$key]); + } + } + } + if(empty($aFullTextNeedles)) { - $oP->p("Full text accelerator for class '$sClass': ".$e->getHtmlDesc()); - $iErrors++; + $oP->p(Dict::S('UI:Search:NoSearch')); + break; + } + $sFullText = implode(' ', $aFullTextNeedles); + + // Sanity check of the accelerators + /** @var array $aAccelerators */ + $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); + foreach ($aAccelerators as $sClass => $aAccelerator) + { + try + { + $bSkip = array_key_exists('skip', $aAccelerator) ? $aAccelerator['skip'] : false; + if (!$bSkip) + { + $oSearch = DBObjectSearch::FromOQL($aAccelerator['query']); + if ($sClass != $oSearch->GetClass()) + { + $oP->p("Full text accelerator for class '$sClass': searched class mismatch (".$oSearch->GetClass().")"); + $iErrors++; + } + } + } + catch (OqlException $e) + { + $oP->p("Full text accelerator for class '$sClass': ".$e->getHtmlDesc()); + $iErrors++; + } + } + + if ($iErrors == 0) + { + $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); + $sPageId = "ui-global-search"; + $sLabel = Dict::S('UI:SearchResultsTitle'); + $sDescription = Dict::S('UI:SearchResultsTitle+'); + $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add(' '.Dict::Format('UI:Search:Ongoing', utils::EscapeHtml($sFullText)).''); + $oP->add("
\n"); + $oP->add("
\n"); + $oP->add("
 
\n"); + $oP->add("

".Dict::Format('UI:FullTextSearchTitle_Text', utils::EscapeHtml($sFullText))."

"); + $oP->add("
\n"); + $oP->add("
\n"); + $sJSClass = addslashes($sClassName); + $sJSNeedles = json_encode($aFullTextNeedles); + $oP->add_ready_script( + << 0) + { + $oP->add_script("var oTimeStatistics = {};"); + } } } + break; - if ($iErrors == 0) + /////////////////////////////////////////////////////////////////////////////////////////// + + /** @deprecated 3.1.0 Use the "object.modify" route instead */ + // Kept for backward compatibility with notification URLs, don't remove + case 'modify': // Legacy operation + $oController = new ObjectController(); + $oP = $oController->OperationModify(); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'select_for_modify_all': // Select the list of objects to be modified (bulk modify) + UI::OperationSelectForModifyAll($oP); + break; + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) + UI::OperationFormForModifyAll($oP, $oAppContext); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'preview_or_modify_all': // Preview or apply bulk modify + UI::OperationPreviewOrModifyAll($oP, $oAppContext); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + 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) ) { - $oP->set_title(Dict::S('UI:SearchResultsPageTitle')); - $sPageId = "ui-global-search"; - $sLabel = Dict::S('UI:SearchResultsTitle'); - $sDescription = Dict::S('UI:SearchResultsTitle+'); - $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription, '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add(' '.Dict::Format('UI:Search:Ongoing', utils::EscapeHtml($sFullText)).''); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add("
 
\n"); - $oP->add("

".Dict::Format('UI:FullTextSearchTitle_Text', utils::EscapeHtml($sFullText))."

"); - $oP->add("
\n"); - $oP->add("
\n"); - $sJSClass = addslashes($sClassName); - $sJSNeedles = json_encode($aFullTextNeedles); - $oP->add_ready_script( - <<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', ); - if ($iTune > 0) + // 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]); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_modify': // Applying the modifications to an existing object + $oP->DisableBreadCrumb(); + $sClass = utils::ReadPostedParam('class', '', 'class'); + $sClassLabel = MetaModel::GetName($sClass); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); + if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + { + IssueLog::Trace('Object not updated (empty class or id)', $sClass, array( + '$operation' => $operation, + '$id' => $id, + '$sTransactionId' => $sTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + } + $bDisplayDetails = true; + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj == null) + { + $bDisplayDetails = false; + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + + IssueLog::Trace('Object not updated (id not found)', $sClass, array( + '$operation' => $operation, + '$id' => $id, + '$sTransactionId' => $sTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + } + elseif (!utils::IsTransactionValid($sTransactionId, false)) + { + //TODO: since $bDisplayDetails= true, there will be an redirection, thus, the content generated here is ignored, only the $sMessage and $sSeverity are used afeter the redirection + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); + $sMessage = Dict::Format('UI:Error:ObjectAlreadyUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'error'; + + IssueLog::Trace('Object not updated (invalid transaction_id)', $sClass, array( + '$operation' => $operation, + '$id' => $id, + '$sTransactionId' => $sTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + } + else + { + $aErrors = $oObj->UpdateObjectFromPostedForm(); + $sMessage = ''; + $sSeverity = 'ok'; + + if (!$oObj->IsModified() && empty($aErrors)) { - $oP->add_script("var oTimeStatistics = {};"); + $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding + $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'info'; + + IssueLog::Trace('Object not updated (see either $aErrors or IsModified)', $sClass, array( + '$operation' => $operation, + '$id' => $id, + '$sTransactionId' => $sTransactionId, + '$aErrors' => $aErrors, + 'IsModified' => $oObj->IsModified(), + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + } + else + { + IssueLog::Trace('Object updated', $sClass, array( + '$operation' => $operation, + '$id' => $id, + '$sTransactionId' => $sTransactionId, + '$aErrors' => $aErrors, + 'IsModified' => $oObj->IsModified(), + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + + try + { + if (!empty($aErrors)) + { + throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); + } + // Transactions are now handled in DBUpdate + $oObj->DBUpdate(); + $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'ok'; + } + catch (CoreCannotSaveObjectException $e) + { + // Found issues, explain and give the user a second chance + // + $bDisplayDetails = false; + $aIssues = $e->getIssues(); + $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); + $oObj->DisplayModifyForm($oP, + array('wizard_container' => true)); // wizard_container: display the wizard border and the title + } + catch (DeleteException $e) + { + // Say two things: + // - 1) Don't be afraid nothing was modified + $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'info'; + cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, + $sSeverity, 0, true /* must not exist */); + // - 2) Ok, there was some trouble indeed + $sMessage = $e->getMessage(); + $sSeverity = 'error'; + } + utils::RemoveTransaction($sTransactionId); } } - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'modify': // Legacy operation - /** @internal */ - case 'object.modify': // New operation - $oController = new ObjectController(); - $oP = $oController->Modify(); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'select_for_modify_all': // Select the list of objects to be modified (bulk modify) - UI::OperationSelectForModifyAll($oP); - break; - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'form_for_modify_all': // Form to modify multiple objects (bulk modify) - UI::OperationFormForModifyAll($oP, $oAppContext); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'preview_or_modify_all': // Preview or apply bulk modify - UI::OperationPreviewOrModifyAll($oP, $oAppContext); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - 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 ($bDisplayDetails) { - if (!MetaModel::IsAbstract($sCandidateClass) && (UserRights::IsActionAllowed($sCandidateClass, UR_ACTION_MODIFY) == UR_ALLOWED_YES)) + $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly + $sNextAction = utils::ReadPostedParam('next_action', ''); + if (!empty($sNextAction)) { - $aPossibleClasses[$sCandidateClass] = MetaModel::GetName($sCandidateClass); + try + { + ApplyNextAction($oP, $oObj, $sNextAction); + } + catch (ApplicationException $e) + { + $sMessage = $e->getMessage(); + $sSeverity = 'info'; + ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); + } + } + else + { + // Nothing more to do + $sMessage = isset($sMessage) ? $sMessage : ''; + $sSeverity = isset($sSeverity) ? $sSeverity : null; + ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); + } + + $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); + if ($bLockEnabled) + { + // Release the concurrent lock, if any + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); + if ($sOwnershipToken !== null) + { + // We're done, let's release the lock + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } } } - // Only one of the subclasses can be instantiated... - if (count($aPossibleClasses) == 1) + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'select_for_deletion': // Select multiple objects for deletion + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + if (empty($sFilter)) { - $aKeys = array_keys($aPossibleClasses); - $sRealClass = $aKeys[0]; + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); } - } - 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); + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); - // 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]); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// + $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid + $oFilter->UpdateContextFromUser(); - case 'apply_modify': // Applying the modifications to an existing object + $sClass = $oFilter->GetClass(); + + $aDisplayParams = [ + 'icon' => MetaModel::GetClassIcon($sClass, false), + 'title' => Dict::S('UI:BulkDeleteTitle'), + ]; + $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_DELETE); + DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_delete', $oChecker, [], $aDisplayParams); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'bulk_delete_confirmed': // Confirm bulk deletion of objects + $oP->DisableBreadCrumb(); + $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); + if (!utils::IsTransactionValid($sTransactionId)) + { + $sUser = UserRights::GetUser(); + $sClass = utils::ReadParam('class', '', false, 'class'); + IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); + throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); + } + // Fall through + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'delete': + case 'bulk_delete': // Actual bulk deletion (if confirmed) + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sClassLabel = MetaModel::GetName($sClass); + $aObjects = array(); + if ($operation == 'delete') + { + // Single object + $id = utils::ReadParam('id', ''); + $oObj = MetaModel::GetObject($sClass, $id); + $aObjects[] = $oObj; + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj))) + { + throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel)); + } + } + else + { + // Several objects + $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); + $oFullSetFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFullSetFilter->UpdateContextFromUser(); + $aSelectObject = utils::ReadMultipleSelection($oFullSetFilter); + if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); + } + foreach($aSelectObject as $iId) + { + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + if (count($aObjects) == 1) + { + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) + { + throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); + } + } + else + { + if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) + { + throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); + } + $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); + } + } + // Go for the common part... (delete single, delete bulk, delete confirmed) + cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_new': // Creation of a new object $oP->DisableBreadCrumb(); $sClass = utils::ReadPostedParam('class', '', 'class'); $sClassLabel = MetaModel::GetName($sClass); - $id = utils::ReadPostedParam('id', ''); $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - if ( empty($sClass) || empty($id)) // TO DO: check that the class name is valid ! + $aErrors = array(); + $aWarnings = array(); + if ( empty($sClass) ) // TO DO: check that the class name is valid ! { - IssueLog::Trace('Object not updated (empty class or id)', $sClass, array( - '$operation' => $operation, - '$id' => $id, - '$sTransactionId' => $sTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); + IssueLog::Trace('Object not created (empty class)', $sClass, array( + '$operation' => $operation, + '$sTransactionId' => $sTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'id')); + throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); } - $bDisplayDetails = true; - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj == null) + if (!utils::IsTransactionValid($sTransactionId, false)) { - $bDisplayDetails = false; - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - - IssueLog::Trace('Object not updated (id not found)', $sClass, array( - '$operation' => $operation, - '$id' => $id, - '$sTransactionId' => $sTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - } - elseif (!utils::IsTransactionValid($sTransactionId, false)) - { - //TODO: since $bDisplayDetails= true, there will be an redirection, thus, the content generated here is ignored, only the $sMessage and $sSeverity are used afeter the redirection $sUser = UserRights::GetUser(); IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $oP->p("".Dict::S('UI:Error:ObjectAlreadyUpdated')."\n"); - $sMessage = Dict::Format('UI:Error:ObjectAlreadyUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'error'; + $oP->p("".Dict::S('UI:Error:ObjectAlreadyCreated')."\n"); - IssueLog::Trace('Object not updated (invalid transaction_id)', $sClass, array( - '$operation' => $operation, - '$id' => $id, - '$sTransactionId' => $sTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); + IssueLog::Trace('Object not created (invalid transaction_id)', $sClass, array( + '$operation' => $operation, + '$sTransactionId' => $sTransactionId, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); } else { - $aErrors = $oObj->UpdateObjectFromPostedForm(); - $sMessage = ''; - $sSeverity = 'ok'; - - if (!$oObj->IsModified() && empty($aErrors)) + /** @var \cmdbAbstractObject $oObj */ + $oObj = MetaModel::NewObject($sClass); + if (MetaModel::HasLifecycle($sClass)) { - $oP->set_title(Dict::Format('UI:ModificationPageTitle_Object_Class', $oObj->GetRawName(), $sClassLabel)); // Set title will take care of the encoding - $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'info'; - - IssueLog::Trace('Object not updated (see either $aErrors or IsModified)', $sClass, array( - '$operation' => $operation, - '$id' => $id, - '$sTransactionId' => $sTransactionId, - '$aErrors' => $aErrors, - 'IsModified' => $oObj->IsModified(), - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - } - else - { - IssueLog::Trace('Object updated', $sClass, array( - '$operation' => $operation, - '$id' => $id, - '$sTransactionId' => $sTransactionId, - '$aErrors' => $aErrors, - 'IsModified' => $oObj->IsModified(), - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - - try + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + $sTargetState = utils::ReadPostedParam('obj_state', ''); + if ($sTargetState != '') { - if (!empty($aErrors)) + $sOrigState = utils::ReadPostedParam('obj_state_orig', ''); + if ($sTargetState != $sOrigState) { - throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); + $aWarnings[] = Dict::S('UI:StateChanged'); } - // Transactions are now handled in DBUpdate - $oObj->DBUpdate(); - $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'ok'; + $oObj->Set($sStateAttCode, $sTargetState); } - catch (CoreCannotSaveObjectException $e) + } + $aErrors = $oObj->UpdateObjectFromPostedForm(); + } + if (isset($oObj) && is_object($oObj)) + { + $sClass = get_class($oObj); + $sClassLabel = MetaModel::GetName($sClass); + + try + { + if (!empty($aErrors) || !empty($aWarnings)) { - // Found issues, explain and give the user a second chance - // - $bDisplayDetails = false; - $aIssues = $e->getIssues(); - $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); - $oObj->DisplayModifyForm($oP, - array('wizard_container' => true)); // wizard_container: display the wizard border and the title - } - catch (DeleteException $e) - { - // Say two things: - // - 1) Don't be afraid nothing was modified - $sMessage = Dict::Format('UI:Class_Object_NotUpdated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'info'; - cmdbAbstractObject::SetSessionMessage(get_class($oObj), $oObj->GetKey(), 'UI:Class_Object_NotUpdated', $sMessage, - $sSeverity, 0, true /* must not exist */); - // - 2) Ok, there was some trouble indeed - $sMessage = $e->getMessage(); - $sSeverity = 'error'; + IssueLog::Trace('Object not created (see $aErrors)', $sClass, array( + '$operation' => $operation, + '$sTransactionId' => $sTransactionId, + '$aErrors' => $aErrors, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + + throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); } + + $oObj->DBInsertNoReload();// No need to reload + + IssueLog::Trace('Object created', $sClass, array( + '$operation' => $operation, + '$id' => $oObj->GetKey(), + '$sTransactionId' => $sTransactionId, + '$aErrors' => $aErrors, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + utils::RemoveTransaction($sTransactionId); + $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); + QuickCreateHelper::AddClassToHistory($sClass); + + // Compute the name, by reloading the object, even if it disappeared from the silo + $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); + $sName = $oObj->GetName(); + $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); + + $sNextAction = utils::ReadPostedParam('next_action', ''); + if (!empty($sNextAction)) { + $oP->add("

$sMessage

"); + try { + ApplyNextAction($oP, $oObj, $sNextAction); + } + catch (ApplicationException $e) { + $sMessage = $e->getMessage(); + $sSeverity = 'info'; + ReloadAndDisplay($oP, $oObj, 'create', $sMessage, $sSeverity); + } + } else { + // Nothing more to do + ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok'); + } + } + catch (CoreCannotSaveObjectException $e) { + // Found issues, explain and give the user a second chance + // + $aIssues = $e->getIssues(); + + $sObjKey = $oObj->GetKey(); + $sClassIcon = MetaModel::GetClassIcon($sClass, false); + $sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); + + $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); + if (!empty($aIssues)) { + $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); + } + if (!empty($aWarnings)) { + $sWarnings = implode(', ', $aWarnings); + $oP->AddHeaderMessage($sWarnings, 'message_warning'); + } + cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj, [], ['transaction_id' => $sTransactionId]); } } - if ($bDisplayDetails) - { - $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); //Workaround: reload the object so that the linkedset are displayed properly - $sNextAction = utils::ReadPostedParam('next_action', ''); - if (!empty($sNextAction)) - { - try - { - ApplyNextAction($oP, $oObj, $sNextAction); - } - catch (ApplicationException $e) - { - $sMessage = $e->getMessage(); - $sSeverity = 'info'; - ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); - } - } - else - { - // Nothing more to do - $sMessage = isset($sMessage) ? $sMessage : ''; - $sSeverity = isset($sSeverity) ? $sSeverity : null; - ReloadAndDisplay($oP, $oObj, 'update', $sMessage, $sSeverity); - } - - $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($bLockEnabled) - { - // Release the concurrent lock, if any - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); - if ($sOwnershipToken !== null) - { - // We're done, let's release the lock - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - } - } - break; + break; - /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// - case 'select_for_deletion': // Select multiple objects for deletion + case 'select_bulk_stimulus': // Form displayed when applying a stimulus to many objects $oP->DisableBreadCrumb(); $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - if (empty($sFilter)) + $sStimulus = utils::ReadParam('stimulus', ''); + $sState = utils::ReadParam('state', ''); + if (empty($sFilter) || empty($sStimulus) || empty($sState)) { - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'filter')); + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); } - $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); - - $oFilter = DBSearch::unserialize($sFilter); // TO DO : check that the filter is valid + $oFilter = DBObjectSearch::unserialize($sFilter); $oFilter->UpdateContextFromUser(); - + $sClass = $oFilter->GetClass(); + $aStimuli = MetaModel::EnumStimuli($sClass); + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $oP->set_title($sActionLabel); $sClass = $oFilter->GetClass(); $aDisplayParams = [ 'icon' => MetaModel::GetClassIcon($sClass, false), - 'title' => Dict::S('UI:BulkDeleteTitle'), + 'title' => $sActionLabel, ]; - $oChecker = new ActionChecker($oFilter, UR_ACTION_BULK_DELETE); - DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_delete', $oChecker, [], $aDisplayParams); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'bulk_delete_confirmed': // Confirm bulk deletion of objects - $oP->DisableBreadCrumb(); - $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId)) - { - $sUser = UserRights::GetUser(); - $sClass = utils::ReadParam('class', '', false, 'class'); - IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - throw new ApplicationException(Dict::S('UI:Error:ObjectsAlreadyDeleted')); - } - // Fall through - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'delete': - case 'bulk_delete': // Actual bulk deletion (if confirmed) - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sClassLabel = MetaModel::GetName($sClass); - $aObjects = array(); - if ($operation == 'delete') - { - // Single object - $id = utils::ReadParam('id', ''); - $oObj = MetaModel::GetObject($sClass, $id); - $aObjects[] = $oObj; - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj))) - { - throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel)); - } - } - else - { - // Several objects - $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); - $oFullSetFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFullSetFilter->UpdateContextFromUser(); - $aSelectObject = utils::ReadMultipleSelection($oFullSetFilter); - if ( empty($sClass) || empty($aSelectObject)) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:2ParametersMissing', 'class', 'selectObject[]')); - } - foreach($aSelectObject as $iId) - { - $aObjects[] = MetaModel::GetObject($sClass, $iId); - } - if (count($aObjects) == 1) - { - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) - { - throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); - } - } - else - { - if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects))) - { - throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel)); - } - $oP->set_title(Dict::S('UI:BulkDeletePageTitle')); - } - } - // Go for the common part... (delete single, delete bulk, delete confirmed) - cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed'); + $oChecker = new StimulusChecker($oFilter, $sState, $sStimulus); + $aExtraFormParams = array('stimulus' => $sStimulus, 'state' => $sState); + DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_stimulus', $oChecker, $aExtraFormParams, $aDisplayParams); break; - - /////////////////////////////////////////////////////////////////////////////////////////// - case 'apply_new': // Creation of a new object - $oP->DisableBreadCrumb(); - $sClass = utils::ReadPostedParam('class', '', 'class'); - $sClassLabel = MetaModel::GetName($sClass); - $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - $aErrors = array(); - $aWarnings = array(); - if ( empty($sClass) ) // TO DO: check that the class name is valid ! - { - IssueLog::Trace('Object not created (empty class)', $sClass, array( - '$operation' => $operation, - '$sTransactionId' => $sTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - - throw new ApplicationException(Dict::Format('UI:Error:1ParametersMissing', 'class')); - } - if (!utils::IsTransactionValid($sTransactionId, false)) - { - $sUser = UserRights::GetUser(); - IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - $oP->p("".Dict::S('UI:Error:ObjectAlreadyCreated')."\n"); - - IssueLog::Trace('Object not created (invalid transaction_id)', $sClass, array( - '$operation' => $operation, - '$sTransactionId' => $sTransactionId, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - } - else - { - /** @var \cmdbAbstractObject $oObj */ - $oObj = MetaModel::NewObject($sClass); - if (MetaModel::HasLifecycle($sClass)) + case 'bulk_stimulus': + $oP->DisableBreadCrumb(); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sStimulus = utils::ReadParam('stimulus', ''); + $sState = utils::ReadParam('state', ''); + if (empty($sFilter) || empty($sStimulus) || empty($sState)) { - $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - $sTargetState = utils::ReadPostedParam('obj_state', ''); - if ($sTargetState != '') - { - $sOrigState = utils::ReadPostedParam('obj_state_orig', ''); - if ($sTargetState != $sOrigState) - { - $aWarnings[] = Dict::S('UI:StateChanged'); - } - $oObj->Set($sStateAttCode, $sTargetState); - } + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); } - $aErrors = $oObj->UpdateObjectFromPostedForm(); - } - if (isset($oObj) && is_object($oObj)) - { - $sClass = get_class($oObj); - $sClassLabel = MetaModel::GetName($sClass); - - try - { - if (!empty($aErrors) || !empty($aWarnings)) - { - IssueLog::Trace('Object not created (see $aErrors)', $sClass, array( - '$operation' => $operation, - '$sTransactionId' => $sTransactionId, - '$aErrors' => $aErrors, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - - throw new CoreCannotSaveObjectException(array('id' => $oObj->GetKey(), 'class' => $sClass, 'issues' => $aErrors)); - } - - $oObj->DBInsertNoReload();// No need to reload - - IssueLog::Trace('Object created', $sClass, array( - '$operation' => $operation, - '$id' => $oObj->GetKey(), - '$sTransactionId' => $sTransactionId, - '$aErrors' => $aErrors, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - - utils::RemoveTransaction($sTransactionId); - $oP->set_title(Dict::S('UI:PageTitle:ObjectCreated')); - QuickCreateHelper::AddClassToHistory($sClass); - - // Compute the name, by reloading the object, even if it disappeared from the silo - $oObj = MetaModel::GetObject($sClass, $oObj->GetKey(), true /* Must be found */, true /* Allow All Data*/); - $sName = $oObj->GetName(); - $sMessage = Dict::Format('UI:Title:Object_Of_Class_Created', $sName, $sClassLabel); - - $sNextAction = utils::ReadPostedParam('next_action', ''); - if (!empty($sNextAction)) { - $oP->add("

$sMessage

"); - try { - ApplyNextAction($oP, $oObj, $sNextAction); - } - catch (ApplicationException $e) { - $sMessage = $e->getMessage(); - $sSeverity = 'info'; - ReloadAndDisplay($oP, $oObj, 'create', $sMessage, $sSeverity); - } - } else { - // Nothing more to do - ReloadAndDisplay($oP, $oObj, 'create', $sMessage, 'ok'); - } - } - catch (CoreCannotSaveObjectException $e) { - // Found issues, explain and give the user a second chance - // - $aIssues = $e->getIssues(); - - $sObjKey = $oObj->GetKey(); - $sClassIcon = MetaModel::GetClassIcon($sClass, false); - $sHeaderTitle = Dict::Format('UI:CreationTitle_Class', $sClassLabel); - - $oP->set_title(Dict::Format('UI:CreationPageTitle_Class', $sClassLabel)); - if (!empty($aIssues)) { - $oP->AddHeaderMessage($e->getHtmlMessage(), 'message_error'); - } - if (!empty($aWarnings)) { - $sWarnings = implode(', ', $aWarnings); - $oP->AddHeaderMessage($sWarnings, 'message_warning'); - } - cmdbAbstractObject::DisplayCreationForm($oP, $sClass, $oObj, [], ['transaction_id' => $sTransactionId]); - } - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'select_bulk_stimulus': // Form displayed when applying a stimulus to many objects - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sStimulus = utils::ReadParam('stimulus', ''); - $sState = utils::ReadParam('state', ''); - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $oFilter = DBObjectSearch::unserialize($sFilter); - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $aStimuli = MetaModel::EnumStimuli($sClass); - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $oP->set_title($sActionLabel); - $sClass = $oFilter->GetClass(); - - $aDisplayParams = [ - 'icon' => MetaModel::GetClassIcon($sClass, false), - 'title' => $sActionLabel, - ]; - $oChecker = new StimulusChecker($oFilter, $sState, $sStimulus); - $aExtraFormParams = array('stimulus' => $sStimulus, 'state' => $sState); - DisplayMultipleSelectionForm($oP, $oFilter, 'bulk_stimulus', $oChecker, $aExtraFormParams, $aDisplayParams); - break; - - case 'bulk_stimulus': - $oP->DisableBreadCrumb(); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sStimulus = utils::ReadParam('stimulus', ''); - $sState = utils::ReadParam('state', ''); - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $oFilter = DBObjectSearch::unserialize($sFilter); - // Add user filter - $oFilter->UpdateContextFromUser(); - $sClass = $oFilter->GetClass(); - $aSelectObject = utils::ReadMultipleSelection($oFilter); - if (count($aSelectObject) == 0) - { - // Nothing to do, no object was selected ! - throw new ApplicationException(Dict::S('UI:BulkAction:NoObjectSelected')); - } - else - { - $aTransitions = MetaModel::EnumTransitions($sClass, $sState); - $aStimuli = MetaModel::EnumStimuli($sClass); - - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aStates = MetaModel::EnumStates($sClass); - $aTargetStateDef = $aStates[$sTargetState]; - - $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass)); - $oP->add(<< -
-HTML - ); - $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass))); - if (!empty($sActionDetails)) { - $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails)); - } - - - - $aExpectedAttributes = MetaModel::GetTransitionAttributes($sClass, $sStimulus, $sState); - $aDetails = array(); - $sFormId = 'apply_stimulus'; - $sFormPrefix = $sFormId.'_'; - $iFieldIndex = 0; - $aFieldsMap = array(); - $aValues = array(); - $aObjects = array(); - foreach($aSelectObject as $iId) - { - $aObjects[] = MetaModel::GetObject($sClass, $iId); - } - $oSet = DBObjectSet::FromArray($sClass, $aObjects); - $oObj = $oSet->ComputeCommonObject($aValues); - $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); - $oObj->Set($sStateAttCode,$sTargetState); - $sReadyScript = ''; - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - $sFieldInputId = $sFormPrefix.$sAttCode; - // Prompt for an attribute if - // - the attribute must be changed or must be displayed to the user for confirmation - // - or the field is mandatory and currently empty - if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || - (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) - { - $aAttributesDef = MetaModel::ListAttributeDefs($sClass); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one - if (count($aPrerequisites) > 0) - { - // When 'enabling' a field, all its prerequisites must be enabled too - $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']"; - $oP->add_ready_script("$('#enable_{$sFieldInputId}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); - } - $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one - if (count($aDependents) > 0) - { - // When 'disabling' a field, all its dependent fields must be disabled too - $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']"; - $oP->add_ready_script("$('#enable_{$sFieldInputId}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); - } - $aArgs = array('this' => $oObj); - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sFieldInputId, '', $iExpectCode, $aArgs); - $sComments = ''; - if (!isset($aValues[$sAttCode])) - { - $aValues[$sAttCode] = array(); - } - if (count($aValues[$sAttCode]) == 1) - { - $sComments = '
1'.$sComments.'
'; - } - else - { - // Non-homogenous value - $iMaxCount = 5; - $sTip = "

".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."

    "; - $index = 0; - foreach($aValues[$sAttCode] as $sCurrValue => $aVal) - { - $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']); - $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."
  • "; - $index++; - if ($iMaxCount == $index) - { - $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', count($aValues[$sAttCode]) - $iMaxCount)."
  • "; - break; - } - } - $sTip .= "

"; - $sTip = utils::HtmlEntities($sTip); - $sComments = '
'.count($aValues[$sAttCode]).$sComments.'
'; - } - $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue", 'comments' => $sComments); - $aFieldsMap[$sAttCode] = $sFieldInputId; - $iFieldIndex++; - } - } - $oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']); - $oP->AddUiBlock($oFormContainer); - $oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form($sFormId); - $oFormContainer->AddSubBlock($oForm); - $oForm->SetOnSubmitJsCode("return OnSubmit('{$sFormId}');") - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass)) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'bulk_apply_stimulus')) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus)) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('preview_mode', 1)) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('filter', utils::HtmlEntities($sFilter))) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('state', $sState)) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('selectObject', implode(',',$aSelectObject))) - ->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); - - $aContextInputBlocks = $oAppContext->GetForUIForm(); - foreach ($aContextInputBlocks as $oContextInputBlock){ - $oForm->AddSubBlock($oContextInputBlock); - } - // Note: Remove the table if we want fields to occupy the whole width of the container - $oForm->AddHtml('
'); - $oForm->AddHtml($oP->GetDetails($aDetails)); - $oForm->AddHtml('
'); - - $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel'); - $oCancelButton->SetOnClickJsCode("window.location.href='$sURL'"); - $oForm->AddSubBlock($oCancelButton); - - $oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true); - $oForm->AddSubBlock($oSubmitButton); - - $oP->add(<< -
-HTML - ); - - $iFieldsCount = count($aFieldsMap); - $sJsonFieldsMap = json_encode($aFieldsMap); - - $oP->add_script( -<<add_ready_script( -<<DisableBreadCrumb(); - $bPreviewMode = utils::ReadPostedParam('preview_mode', false); - $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); - $sStimulus = utils::ReadPostedParam('stimulus', ''); - $sState = utils::ReadPostedParam('state', ''); - $sSelectObject = utils::ReadPostedParam('selectObject', '', 'raw_data'); - $aSelectObject = explode(',', $sSelectObject); - - if (empty($sFilter) || empty($sStimulus) || empty($sState)) - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); - } - $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId)) - { - $sUser = UserRights::GetUser(); - IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser'"); - $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); - } - else - { - // For archiving the modification $oFilter = DBObjectSearch::unserialize($sFilter); // Add user filter $oFilter->UpdateContextFromUser(); $sClass = $oFilter->GetClass(); - $aObjects = array(); - foreach($aSelectObject as $iId) + $aSelectObject = utils::ReadMultipleSelection($oFilter); + if (count($aSelectObject) == 0) { - $aObjects[] = MetaModel::GetObject($sClass, $iId); + // Nothing to do, no object was selected ! + throw new ApplicationException(Dict::S('UI:BulkAction:NoObjectSelected')); } - - $aTransitions = MetaModel::EnumTransitions($sClass, $sState); - $aStimuli = MetaModel::EnumStimuli($sClass); - - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - - $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); - - $oSet = DBObjectSet::FromArray($sClass, $aObjects); - - // For reporting - $aHeaders = array( - 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), - 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), - 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), - ); - $aRows = array(); - while ($oObj = $oSet->Fetch()) + else { - $sError = Dict::S('UI:BulkModifyStatusOk'); - try + $aTransitions = MetaModel::EnumTransitions($sClass, $sState); + $aStimuli = MetaModel::EnumStimuli($sClass); + + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aStates = MetaModel::EnumStates($sClass); + $aTargetStateDef = $aStates[$sTargetState]; + + $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass)); + $oP->add(<< +
+ HTML + ); + $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aSelectObject), $sClass))); + if (!empty($sActionDetails)) { + $oP->AddUiBlock(TitleUIBlockFactory::MakeForPage($sActionDetails)); + } + + + + $aExpectedAttributes = MetaModel::GetTransitionAttributes($sClass, $sStimulus, $sState); + $aDetails = array(); + $sFormId = 'apply_stimulus'; + $sFormPrefix = $sFormId.'_'; + $iFieldIndex = 0; + $aFieldsMap = array(); + $aValues = array(); + $aObjects = array(); + foreach($aSelectObject as $iId) { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - if (!isset($aTransitions[$sStimulus])) + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + $oSet = DBObjectSet::FromArray($sClass, $aObjects); + $oObj = $oSet->ComputeCommonObject($aValues); + $sStateAttCode = MetaModel::GetStateAttributeCode($sClass); + $oObj->Set($sStateAttCode,$sTargetState); + $sReadyScript = ''; + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + $sFieldInputId = $sFormPrefix.$sAttCode; + // Prompt for an attribute if + // - the attribute must be changed or must be displayed to the user for confirmation + // - or the field is mandatory and currently empty + if ( ($iExpectCode & (OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT)) || + (($iExpectCode & OPT_ATT_MANDATORY) && ($oObj->Get($sAttCode) == '')) ) { - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /* cureent state */); - $aDetails = array(); - $aErrors = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + $aAttributesDef = MetaModel::ListAttributeDefs($sClass); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aPrerequisites = MetaModel::GetPrerequisiteAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + if (count($aPrerequisites) > 0) { - $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); - if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) + // When 'enabling' a field, all its prerequisites must be enabled too + $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aPrerequisites)."']"; + $oP->add_ready_script("$('#enable_{$sFieldInputId}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, true); } );\n"); + } + $aDependents = MetaModel::GetDependentAttributes($sClass, $sAttCode); // List of attributes that are needed for the current one + if (count($aDependents) > 0) + { + // When 'disabling' a field, all its dependent fields must be disabled too + $sFieldList = "['{$sFormPrefix}".implode("','{$sFormPrefix}", $aDependents)."']"; + $oP->add_ready_script("$('#enable_{$sFieldInputId}').on('change', function(evt, sFormId) { return PropagateCheckBox( this.checked, $sFieldList, false); } );\n"); + } + $aArgs = array('this' => $oObj); + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $oObj->Get($sAttCode), $oObj->GetEditValue($sAttCode), $sFieldInputId, '', $iExpectCode, $aArgs); + $sComments = ''; + if (!isset($aValues[$sAttCode])) + { + $aValues[$sAttCode] = array(); + } + if (count($aValues[$sAttCode]) == 1) + { + $sComments = '
1'.$sComments.'
'; + } + else + { + // Non-homogenous value + $iMaxCount = 5; + $sTip = "

".Dict::Format('UI:BulkModify_Count_DistinctValues', count($aValues[$sAttCode]))."

    "; + $index = 0; + foreach($aValues[$sAttCode] as $sCurrValue => $aVal) { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); - if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) + $sDisplayValue = empty($aVal['display']) ? ''.Dict::S('Enum:Undefined').'' : str_replace(array("\n", "\r"), " ", $aVal['display']); + $sTip .= "
  • ".Dict::Format('UI:BulkModify:Value_Exists_N_Times', $sDisplayValue, $aVal['count'])."
  • "; + $index++; + if ($iMaxCount == $index) { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); - unset($aExpectedAttributes[$sAttCode]); + $sTip .= "
  • ".Dict::Format('UI:BulkModify:N_MoreValues', count($aValues[$sAttCode]) - $iMaxCount)."
  • "; + break; } } + $sTip .= "

"; + $sTip = utils::HtmlEntities($sTip); + $sComments = '
'.count($aValues[$sAttCode]).$sComments.'
'; } - - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - - if (count($aErrors) == 0) + $aDetails[] = array('label' => ''.$oAttDef->GetLabel().'', 'value' => "$sHTMLValue", 'comments' => $sComments); + $aFieldsMap[$sAttCode] = $sFieldInputId; + $iFieldIndex++; + } + } + $oFormContainer = new UIContentBlock(null, ['ibo-wizard-container']); + $oP->AddUiBlock($oFormContainer); + $oForm = new Combodo\iTop\Application\UI\Base\Component\Form\Form($sFormId); + $oFormContainer->AddSubBlock($oForm); + $oForm->SetOnSubmitJsCode("return OnSubmit('{$sFormId}');") + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('class', $sClass)) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('operation', 'bulk_apply_stimulus')) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('stimulus', $sStimulus)) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('preview_mode', 1)) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('filter', utils::HtmlEntities($sFilter))) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('state', $sState)) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('selectObject', implode(',',$aSelectObject))) + ->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', utils::GetNewTransactionId())); + + $aContextInputBlocks = $oAppContext->GetForUIForm(); + foreach ($aContextInputBlocks as $oContextInputBlock){ + $oForm->AddSubBlock($oContextInputBlock); + } + // Note: Remove the table if we want fields to occupy the whole width of the container + $oForm->AddHtml('
'); + $oForm->AddHtml($oP->GetDetails($aDetails)); + $oForm->AddHtml('
'); + + $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $oCancelButton = ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Cancel'), 'cancel', 'cancel'); + $oCancelButton->SetOnClickJsCode("window.location.href='$sURL'"); + $oForm->AddSubBlock($oCancelButton); + + $oSubmitButton = ButtonUIBlockFactory::MakeForPrimaryAction($sActionLabel, 'submit', 'submit', true); + $oForm->AddSubBlock($oSubmitButton); + + $oP->add(<< +
+ HTML + ); + + $iFieldsCount = count($aFieldsMap); + $sJsonFieldsMap = json_encode($aFieldsMap); + + $oP->add_script( + <<add_ready_script( + <<DisableBreadCrumb(); + $bPreviewMode = utils::ReadPostedParam('preview_mode', false); + $sFilter = utils::ReadPostedParam('filter', '', 'raw_data'); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + $sState = utils::ReadPostedParam('state', ''); + $sSelectObject = utils::ReadPostedParam('selectObject', '', 'raw_data'); + $aSelectObject = explode(',', $sSelectObject); + + if (empty($sFilter) || empty($sStimulus) || empty($sState)) + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'filter', 'stimulus', 'state')); + } + $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); + if (!utils::IsTransactionValid($sTransactionId)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser'"); + $oP->p(Dict::S('UI:Error:ObjectAlreadyUpdated')); + } + else + { + // For archiving the modification + $oFilter = DBObjectSearch::unserialize($sFilter); + // Add user filter + $oFilter->UpdateContextFromUser(); + $sClass = $oFilter->GetClass(); + $aObjects = array(); + foreach($aSelectObject as $iId) + { + $aObjects[] = MetaModel::GetObject($sClass, $iId); + } + + $aTransitions = MetaModel::EnumTransitions($sClass, $sState); + $aStimuli = MetaModel::EnumStimuli($sClass); + + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + + $oP->set_title(Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); + + $oSet = DBObjectSet::FromArray($sClass, $aObjects); + + // For reporting + $aHeaders = array( + 'object' => array('label' => MetaModel::GetName($sClass), 'description' => Dict::S('UI:ModifiedObject')), + 'status' => array('label' => Dict::S('UI:BulkModifyStatus'), 'description' => Dict::S('UI:BulkModifyStatus+')), + 'errors' => array('label' => Dict::S('UI:BulkModifyErrors'), 'description' => Dict::S('UI:BulkModifyErrors+')), + ); + $aRows = array(); + while ($oObj = $oSet->Fetch()) + { + $sError = Dict::S('UI:BulkModifyStatusOk'); + try + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + if (!isset($aTransitions[$sStimulus])) { - if ($oObj->ApplyStimulus($sStimulus)) + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /* cureent state */); + $aDetails = array(); + $aErrors = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) { - list($bResult, $aErrors) = $oObj->CheckToWrite(); - $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); - if ($bResult) + $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); + if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) { - $oObj->DBUpdate(); + $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); + if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode)) ) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aErrors[] = Dict::Format('UI:AttemptingToSetASlaveAttribute_Name', $oAttDef->GetLabel(), $sAttCode); + unset($aExpectedAttributes[$sAttCode]); + } + } + } + + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + + if (count($aErrors) == 0) + { + if ($oObj->ApplyStimulus($sStimulus)) + { + list($bResult, $aErrors) = $oObj->CheckToWrite(); + $sStatus = $bResult ? Dict::S('UI:BulkModifyStatusModified') : Dict::S('UI:BulkModifyStatusSkipped'); + if ($bResult) + { + $oObj->DBUpdate(); + } + else + { + $aErrorsToDisplay = array_map(function($sError) { + return utils::HtmlEntities($sError); + }, $aErrors); + $sError = '

'.implode('

',$aErrorsToDisplay)."

\n"; + } } else { - $aErrorsToDisplay = array_map(function($sError) { - return utils::HtmlEntities($sError); - }, $aErrors); - $sError = '

'.implode('

',$aErrorsToDisplay)."

\n"; + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = '

'.Dict::S('UI:FailedToApplyStimuli')."

\n"; } } else { - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - $sError = '

'.Dict::S('UI:FailedToApplyStimuli')."

\n"; + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + $sError = '

'.implode('

',$aErrors)."

\n"; + } + } + } + catch(Exception $e) + { + $sError = $e->getMessage(); + $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); + } + $aRows[] = array( + 'object' => $oObj->GetHyperlink(), + 'status' => $sStatus, + 'errors' => $sError, + ); + } + $oBlock = PanelUIBlockFactory::MakeForClass($sClass, Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); + $oBlock->SetIcon(MetaModel::GetClassIcon($sClass, false)); + + + $oDataTable = DataTableUIBlockFactory::MakeForStaticData('', $aHeaders,$aRows); + $oBlock->AddSubBlock($oDataTable); + $oP->AddUiBlock($oBlock); + + // Back to the list + $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); + $oSubmitButton = ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Done'), 'submit', 'submit', true); + $oSubmitButton->SetOnClickJsCode("window.location.href='$sURL'"); + $oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null); + $oToolbarButtons->AddCSSClass('ibo-toolbar--button'); + $oToolbarButtons->AddSubBlock($oSubmitButton); + $oP->AddSubBlock($oToolbarButtons); + } + break; + + case 'stimulus': // Form displayed when applying a stimulus (state change) + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', ''); + $sStimulus = utils::ReadParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); + } + $aStimuli = MetaModel::EnumStimuli($sClass); + if ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); + throw new ApplicationException(Dict::S('UI:Error:ActionNotAllowed')); + } + + /** @var \cmdbAbstractObject $oObj */ + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj != null) + { + $aPrefillFormParam = [ + 'user' => Session::Get('auth_user'), + 'context' => $oAppContext->GetAsHash(), + 'stimulus' => $sStimulus, + 'origin' => 'console', + ]; + try { + $bApplyTransition = $oObj->DisplayStimulusForm($oP, $sStimulus, $aPrefillFormParam); + } + catch (ApplicationException $e) { + $sMessage = $e->getMessage(); + $sSeverity = 'info'; + ReloadAndDisplay($oP, $oObj, 'stimulus', $sMessage, $sSeverity); + } + if ($bApplyTransition) { + $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'ok'; + //transition is ok, whe can display object with transition message + ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); + } + } + else + { + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); + } + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'apply_stimulus': // Actual state change + $oP->DisableBreadCrumb(); + $sClass = utils::ReadPostedParam('class', '', 'class'); + $id = utils::ReadPostedParam('id', ''); + $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); + $sStimulus = utils::ReadPostedParam('stimulus', ''); + if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! + { + throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); + } + /** @var \cmdbAbstractObject $oObj */ + $oObj = MetaModel::GetObject($sClass, $id, false); + if ($oObj != null) + { + $aTransitions = $oObj->EnumTransitions(); + $aStimuli = MetaModel::EnumStimuli($sClass); + $sMessage = ''; + $sSeverity = 'ok'; + $bDisplayDetails = true; + if (!isset($aTransitions[$sStimulus])) + { + throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); + } + if (!utils::IsTransactionValid($sTransactionId)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); + $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); + $sSeverity = 'info'; + } + elseif ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) + { + $sUser = UserRights::GetUser(); + IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); + $sMessage = Dict::S('UI:Error:ActionNotAllowed'); + $sSeverity = 'error'; + } + else + { + $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); + $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); + $sTargetState = $aTransitions[$sStimulus]['target_state']; + $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); + $aDetails = array(); + $aErrors = array(); + foreach($aExpectedAttributes as $sAttCode => $iExpectCode) + { + $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); + if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) + { + $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); + if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode))) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $aErrors[] = Dict::Format('UI:AttemptingToChangeASlaveAttribute_Name', $oAttDef->GetLabel()); + unset($aExpectedAttributes[$sAttCode]); + } + } + } + + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + + if (count($aErrors) == 0) + { + $sIssues = ''; + $bApplyStimulus = true; + list($bRes, $aIssues) = $oObj->CheckToWrite(); // Check before trying to write the object + if ($bRes) + { + try + { + $bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB + } + catch(CoreException $e) + { + // Rollback to the previous state... by reloading the object from the database and applying the modifications again + $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); + $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); + $sIssues = $e->getMessage(); } } else { - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - $sError = '

'.implode('

',$aErrors)."

\n"; + $sIssues = implode(' ', $aIssues); } - } - } - catch(Exception $e) - { - $sError = $e->getMessage(); - $sStatus = Dict::S('UI:BulkModifyStatusSkipped'); - } - $aRows[] = array( - 'object' => $oObj->GetHyperlink(), - 'status' => $sStatus, - 'errors' => $sError, - ); - } - $oBlock = PanelUIBlockFactory::MakeForClass($sClass, Dict::Format('UI:StimulusModify_N_ObjectsOf_Class', $sActionLabel, count($aObjects), $sClass)); - $oBlock->SetIcon(MetaModel::GetClassIcon($sClass, false)); - - $oDataTable = DataTableUIBlockFactory::MakeForStaticData('', $aHeaders,$aRows); - $oBlock->AddSubBlock($oDataTable); - $oP->AddUiBlock($oBlock); + if (!$bApplyStimulus) + { + $sMessage = Dict::S('UI:FailedToApplyStimuli'); + $sSeverity = 'error'; - // Back to the list - $sURL = "./UI.php?operation=search&filter=".urlencode($sFilter)."&".$oAppContext->GetForLink(); - $oSubmitButton = ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Done'), 'submit', 'submit', true); - $oSubmitButton->SetOnClickJsCode("window.location.href='$sURL'"); - $oToolbarButtons = ToolbarUIBlockFactory::MakeStandard(null); - $oToolbarButtons->AddCSSClass('ibo-toolbar--button'); - $oToolbarButtons->AddSubBlock($oSubmitButton); - $oP->AddSubBlock($oToolbarButtons); - } - break; - - case 'stimulus': // Form displayed when applying a stimulus (state change) - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - $sStimulus = utils::ReadParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); - } - $aStimuli = MetaModel::EnumStimuli($sClass); - if ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) - { - $sUser = UserRights::GetUser(); - IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); - throw new ApplicationException(Dict::S('UI:Error:ActionNotAllowed')); - } - - /** @var \cmdbAbstractObject $oObj */ - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj != null) - { - $aPrefillFormParam = [ - 'user' => Session::Get('auth_user'), - 'context' => $oAppContext->GetAsHash(), - 'stimulus' => $sStimulus, - 'origin' => 'console', - ]; - try { - $bApplyTransition = $oObj->DisplayStimulusForm($oP, $sStimulus, $aPrefillFormParam); - } - catch (ApplicationException $e) { - $sMessage = $e->getMessage(); - $sSeverity = 'info'; - ReloadAndDisplay($oP, $oObj, 'stimulus', $sMessage, $sSeverity); - } - if ($bApplyTransition) { - $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'ok'; - //transition is ok, whe can display object with transition message - ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); - } - } - else - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'apply_stimulus': // Actual state change - $oP->DisableBreadCrumb(); - $sClass = utils::ReadPostedParam('class', '', 'class'); - $id = utils::ReadPostedParam('id', ''); - $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - $sStimulus = utils::ReadPostedParam('stimulus', ''); - if ( empty($sClass) || empty($id) || empty($sStimulus) ) // TO DO: check that the class name is valid ! - { - throw new ApplicationException(Dict::Format('UI:Error:3ParametersMissing', 'class', 'id', 'stimulus')); - } - /** @var \cmdbAbstractObject $oObj */ - $oObj = MetaModel::GetObject($sClass, $id, false); - if ($oObj != null) - { - $aTransitions = $oObj->EnumTransitions(); - $aStimuli = MetaModel::EnumStimuli($sClass); - $sMessage = ''; - $sSeverity = 'ok'; - $bDisplayDetails = true; - if (!isset($aTransitions[$sStimulus])) - { - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); - } - if (!utils::IsTransactionValid($sTransactionId)) - { - $sUser = UserRights::GetUser(); - IssueLog::Error("UI.php '$operation' : invalid transaction_id ! data: user='$sUser', class='$sClass'"); - $sMessage = Dict::S('UI:Error:ObjectAlreadyUpdated'); - $sSeverity = 'info'; - } - elseif ((get_class($aStimuli[$sStimulus]) !== 'StimulusUserAction') || (UserRights::IsStimulusAllowed($sClass, $sStimulus) === UR_ALLOWED_NO)) - { - $sUser = UserRights::GetUser(); - IssueLog::Error("UI.php '$operation' : Stimulus '$sStimulus' not allowed ! data: user='$sUser', class='$sClass'"); - $sMessage = Dict::S('UI:Error:ActionNotAllowed'); - $sSeverity = 'error'; - } - else - { - $sActionLabel = $aStimuli[$sStimulus]->GetLabel(); - $sActionDetails = $aStimuli[$sStimulus]->GetDescription(); - $sTargetState = $aTransitions[$sStimulus]['target_state']; - $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); - $aDetails = array(); - $aErrors = array(); - foreach($aExpectedAttributes as $sAttCode => $iExpectCode) - { - $iFlags = $oObj->GetTransitionFlags($sAttCode, $sStimulus); - if (($iExpectCode & (OPT_ATT_MUSTCHANGE|OPT_ATT_MUSTPROMPT)) || ($oObj->Get($sAttCode) == '') ) - { - $paramValue = utils::ReadPostedParam("attr_$sAttCode", '', 'raw_data'); - if ( ($iFlags & OPT_ATT_SLAVE) && ($paramValue != $oObj->Get($sAttCode))) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aErrors[] = Dict::Format('UI:AttemptingToChangeASlaveAttribute_Name', $oAttDef->GetLabel()); - unset($aExpectedAttributes[$sAttCode]); - } - } - } - - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - - if (count($aErrors) == 0) - { - $sIssues = ''; - $bApplyStimulus = true; - list($bRes, $aIssues) = $oObj->CheckToWrite(); // Check before trying to write the object - if ($bRes) - { - try - { - $bApplyStimulus = $oObj->ApplyStimulus($sStimulus); // will write the object in the DB - } - catch(CoreException $e) - { - // Rollback to the previous state... by reloading the object from the database and applying the modifications again - $oObj = MetaModel::GetObject(get_class($oObj), $oObj->GetKey()); - $oObj->UpdateObjectFromPostedForm('', array_keys($aExpectedAttributes), $aExpectedAttributes); - $sIssues = $e->getMessage(); - } - } - else - { - $sIssues = implode(' ', $aIssues); - } - - if (!$bApplyStimulus) - { - $sMessage = Dict::S('UI:FailedToApplyStimuli'); - $sSeverity = 'error'; - - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); - if ($sOwnershipToken !== null) - { - // Release the concurrent lock, if any - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - } - else if ($sIssues != '') - { - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); - if ($sOwnershipToken !== null) - { - // Release the concurrent lock, if any, a new lock will be re-acquired by DisplayStimulusForm below - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - - $bDisplayDetails = false; - // Found issues, explain and give the user a second chance - // - try - { - $oObj->DisplayStimulusForm($oP, $sStimulus); - } - catch(ApplicationException $e) - { - $sMessage = $e->getMessage(); - $sSeverity = 'info'; - } - $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten',$sIssues); - $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); - } - else - { - $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); - $sSeverity = 'ok'; - utils::RemoveTransaction($sTransactionId); - $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($bLockEnabled) - { - // Release the concurrent lock, if any $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); if ($sOwnershipToken !== null) { - // We're done, let's release the lock + // Release the concurrent lock, if any iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); } } + else if ($sIssues != '') + { + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); + if ($sOwnershipToken !== null) + { + // Release the concurrent lock, if any, a new lock will be re-acquired by DisplayStimulusForm below + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } + + $bDisplayDetails = false; + // Found issues, explain and give the user a second chance + // + try + { + $oObj->DisplayStimulusForm($oP, $sStimulus); + } + catch(ApplicationException $e) + { + $sMessage = $e->getMessage(); + $sSeverity = 'info'; + } + $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten',$sIssues); + $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); + } + else + { + $sMessage = Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName()); + $sSeverity = 'ok'; + utils::RemoveTransaction($sTransactionId); + $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); + if ($bLockEnabled) + { + // Release the concurrent lock, if any + $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); + if ($sOwnershipToken !== null) + { + // We're done, let's release the lock + iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); + } + } + } + } + else + { + $sMessage = implode('

', $aErrors); + $sSeverity = 'error'; } } - else + if ($bDisplayDetails) { - $sMessage = implode('

', $aErrors); - $sSeverity = 'error'; + ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); } } - if ($bDisplayDetails) - { - ReloadAndDisplay($oP, $oObj, 'apply_stimulus', $sMessage, $sSeverity); - } - } - else - { - $oP->set_title(Dict::S('UI:ErrorPageTitle')); - $oP->P(Dict::S('UI:ObjectDoesNotExist')); - } - break; - - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'swf_navigator': /** @deprecated SWF was removed in iTop */ - case 'view_relations': // Graphical display of the relations "impact" / "depends on" - require_once(APPROOT.'core/simplegraph.class.inc.php'); - require_once(APPROOT.'core/relationgraph.class.inc.php'); - require_once(APPROOT.'core/displayablegraph.class.inc.php'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', 0); - $sRelation = utils::ReadParam('relation', 'impact'); - $sDirection = utils::ReadParam('direction', 'down'); - $iGroupingThreshold = utils::ReadParam('g', 5); - - $bDirDown = ($sDirection === 'down'); - $oObj = MetaModel::GetObject($sClass, $id); - $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); - $aSourceObjects = array($oObj); - - $oP->set_title(MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName()); - - $sPageId = "ui-relation-graph-".$sClass.'::'.$id; - $sLabel = $oObj->GetName().' '.MetaModel::GetRelationLabel($sRelation, $bDirDown); - $sDescription = MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName(); - $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription); - - if ($sRelation == 'depends on') { - $sRelation = 'impacts'; - $sDirection = 'up'; - } - if ($sDirection == 'up') { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); - } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); - } - - - $aResults = $oRelGraph->GetObjectsByClass(); - $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); - $oPanel = PanelUIBlockFactory::MakeForClass($sClass, MetaModel::GetRelationDescription($sRelation).' '.$oObj->GetName()); - $sClassIcon = MetaModel::GetClassIcon($sClass, false); - if (strlen($sClassIcon) > 0){ - $oPanel->SetIcon($sClassIcon); - } - - $oP->AddUiBlock($oPanel); - $oP->AddTabContainer('Navigator', '', $oPanel); - $oP->SetCurrentTabContainer('Navigator'); - - $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); - $bLazyLoading = MetaModel::GetConfig()->Get('impact_analysis_lazy_loading'); - $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; - - // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject - $sClassForAttachment = null; - $iIdForAttachment = null; - if (class_exists('Attachment')) { - $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); - foreach ($aAllowedClasses as $sAllowedClass) { - if ($oObj instanceof $sAllowedClass) { - $iIdForAttachment = $id; - $sClassForAttachment = $sClass; - } - } - } - - // Display the tabs - if ($sFirstTab == 'list') - { - DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); - $oP->SetCurrentTab('UI:RelationshipGraph'); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); - DisplayNavigatorGroupTab($oP); - } else { - $oP->SetCurrentTab('UI:RelationshipGraph'); - $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); - DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); - DisplayNavigatorGroupTab($oP); + $oP->set_title(Dict::S('UI:ErrorPageTitle')); + $oP->P(Dict::S('UI:ObjectDoesNotExist')); } - - $oP->SetCurrentTab(''); break; - /////////////////////////////////////////////////////////////////////////////////////////// - - case 'kill_lock': - $oP->DisableBreadCrumb(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $id = utils::ReadParam('id', ''); - iTopOwnershipLock::KillLock($sClass, $id); - $oObj = MetaModel::GetObject($sClass, $id); - ReloadAndDisplay($oP, $oObj, 'concurrent_lock_killed', Dict::S('UI:ConcurrentLockKilled'), 'info'); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// - case 'cancel': // An action was cancelled - $oP->DisableBreadCrumb(); - $oP->set_title(Dict::S('UI:OperationCancelled')); - $oP->add('

'.Dict::S('UI:OperationCancelled').'

'); - break; - - /////////////////////////////////////////////////////////////////////////////////////////// + case 'swf_navigator': /** @deprecated SWF was removed in iTop */ + case 'view_relations': // Graphical display of the relations "impact" / "depends on" + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', 0); + $sRelation = utils::ReadParam('relation', 'impact'); + $sDirection = utils::ReadParam('direction', 'down'); + $iGroupingThreshold = utils::ReadParam('g', 5); - default: // Menu node rendering (templates) - ApplicationMenu::LoadAdditionalMenus(); - $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); - if (is_object($oMenuNode)) - { - $oMenuNode->RenderContent($oP, $oAppContext->GetAsHash()); - $oP->set_title($oMenuNode->GetLabel()); + $bDirDown = ($sDirection === 'down'); + $oObj = MetaModel::GetObject($sClass, $id); + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); + $aSourceObjects = array($oObj); + + $oP->set_title(MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName()); + + $sPageId = "ui-relation-graph-".$sClass.'::'.$id; + $sLabel = $oObj->GetName().' '.MetaModel::GetRelationLabel($sRelation, $bDirDown); + $sDescription = MetaModel::GetRelationDescription($sRelation, $bDirDown).' '.$oObj->GetName(); + $oP->SetBreadCrumbEntry($sPageId, $sLabel, $sDescription); + + if ($sRelation == 'depends on') { + $sRelation = 'impacts'; + $sDirection = 'up'; + } + if ($sDirection == 'up') { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } else { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } + + + $aResults = $oRelGraph->GetObjectsByClass(); + $oDisplayGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + $oPanel = PanelUIBlockFactory::MakeForClass($sClass, MetaModel::GetRelationDescription($sRelation).' '.$oObj->GetName()); + $sClassIcon = MetaModel::GetClassIcon($sClass, false); + if (strlen($sClassIcon) > 0){ + $oPanel->SetIcon($sClassIcon); + } + + $oP->AddUiBlock($oPanel); + $oP->AddTabContainer('Navigator', '', $oPanel); + $oP->SetCurrentTabContainer('Navigator'); + + $sFirstTab = MetaModel::GetConfig()->Get('impact_analysis_first_tab'); + $bLazyLoading = MetaModel::GetConfig()->Get('impact_analysis_lazy_loading'); + $sContextKey = "itop-config-mgmt/relation_context/$sClass/$sRelation/$sDirection"; + + // Check if the current object supports Attachments, similar to AttachmentPlugin::IsTargetObject + $sClassForAttachment = null; + $iIdForAttachment = null; + if (class_exists('Attachment')) { + $aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket')); + foreach ($aAllowedClasses as $sAllowedClass) { + if ($oObj instanceof $sAllowedClass) { + $iIdForAttachment = $id; + $sClassForAttachment = $sClass; + } + } + } + + // Display the tabs + if ($sFirstTab == 'list') + { + DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); + $oP->SetCurrentTab('UI:RelationshipGraph'); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); + DisplayNavigatorGroupTab($oP); + } + else + { + $oP->SetCurrentTab('UI:RelationshipGraph'); + $oDisplayGraph->Display($oP, $aResults, $sRelation, $oAppContext, array(), $sClassForAttachment, $iIdForAttachment, $sContextKey, array('this' => $oObj),$bLazyLoading); + DisplayNavigatorListTab($oP, $aResults, $sRelation, $sDirection, $oObj); + DisplayNavigatorGroupTab($oP); + } + + $oP->SetCurrentTab(''); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'kill_lock': + $oP->DisableBreadCrumb(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $id = utils::ReadParam('id', ''); + iTopOwnershipLock::KillLock($sClass, $id); + $oObj = MetaModel::GetObject($sClass, $id); + ReloadAndDisplay($oP, $oObj, 'concurrent_lock_killed', Dict::S('UI:ConcurrentLockKilled'), 'info'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + case 'cancel': // An action was cancelled + $oP->DisableBreadCrumb(); + $oP->set_title(Dict::S('UI:OperationCancelled')); + $oP->add('

'.Dict::S('UI:OperationCancelled').'

'); + break; + + /////////////////////////////////////////////////////////////////////////////////////////// + + default: // Menu node rendering (templates) + ApplicationMenu::LoadAdditionalMenus(); + $oMenuNode = ApplicationMenu::GetMenuNode(ApplicationMenu::GetMenuIndexById(ApplicationMenu::GetActiveNodeId())); + if (is_object($oMenuNode)) + { + $oMenuNode->RenderContent($oP, $oAppContext->GetAsHash()); + $oP->set_title($oMenuNode->GetLabel()); + } + + /////////////////////////////////////////////////////////////////////////////////////////// } - - /////////////////////////////////////////////////////////////////////////////////////////// - } + DisplayWelcomePopup($oP); $oKPI->ComputeAndReport('Compute page'); - $oP->output(); + $oP->output(); } catch (Exception $e) { $oErrorPage = new ErrorPage(Dict::S('UI:PageTitle:FatalError')); diff --git a/pages/ajax.render.php b/pages/ajax.render.php index 08008bb68..d2a46116c 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -15,6 +15,7 @@ use Combodo\iTop\Controller\Base\Layout\ObjectController; use Combodo\iTop\Controller\PreferencesController; use Combodo\iTop\Renderer\Console\ConsoleBlockRenderer; use Combodo\iTop\Renderer\Console\ConsoleFormRenderer; +use Combodo\iTop\Router\Router; require_once('../approot.inc.php'); @@ -35,6 +36,7 @@ try $oKPI = new ExecutionKPI(); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); + $sRoute = utils::ReadParam('route', '', false, utils::ENUM_SANITIZATION_FILTER_ROUTE); $operation = utils::ReadParam('operation', '', false, utils::ENUM_SANITIZATION_FILTER_OPERATION); // Only allow export functions to portal users @@ -55,945 +57,896 @@ try LoginWebPage::DoLoginEx($sRequestedPortalId, false); $oKPI->ComputeAndReport('User login'); - $oPage = new AjaxPage(""); - - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $sEncoding = utils::ReadParam('encoding', 'serialize'); - $sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class'); - $sStyle = utils::ReadParam('style', 'list'); - // N°2780 Fix ContextTag for console - // some operations are also used in the portal though + // Some operations are also used in the portal though switch ($operation) { case 'export_build_portal': case 'export_download': - // do nothing : used in portal (export.js in portal-base) + // Do nothing: used in portal (export.js in portal-base) break; default: ContextTag::AddContext(ContextTag::TAG_CONSOLE); } - $oAjaxRenderController = new AjaxRenderController(); + // First check if we can redirect the route to a dedicated controller + $sRoute = utils::ReadParam('route', '', false, utils::ENUM_SANITIZATION_FILTER_ROUTE); + $oRouter = Router::GetInstance(); + if ($oRouter->CanDispatchRoute($sRoute)) { + $mResponse = $oRouter->DispatchRoute($sRoute); - switch ($operation) { - case 'search_and_refresh': - $oPage = new JsonPage(); - // Feeds dataTables directly - $oPage->SetOutputDataOnly(true); - $aResult = AjaxRenderController::SearchAndRefresh($sFilter); - $oPage->SetData($aResult); - break; + // If response isn't a \WebPage, it is most likely that the output already occured, stop the script. + // Note that this is done here and not directly in the Router::DispatchRoute() so custom endpoint can handle null responses their own way. + if (false === ($mResponse instanceof WebPage)) { + die(); + } - case 'search': - $oPage = new JsonPage(); - // Feeds dataTables directly - $oPage->SetOutputDataOnly(true); - $aResult = AjaxRenderController::Search($sEncoding, $sFilter); - $oPage->SetData($aResult); - break; + // Response is a \WebPage, let's handle it like legacy operations + $oPage = $mResponse; + } + // Otherwise, use legacy operation + else { + // Default for most operations, but can be switch to a \JsonPage, \DownloadPage or else if the operation requires it + $oPage = new AjaxPage(""); - case 'refreshDashletCount': - $oPage->SetContentType('application/json'); - $aResult = AjaxRenderController::RefreshDashletCount($sFilter); - $oPage->add(json_encode($aResult)); - break; + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $sEncoding = utils::ReadParam('encoding', 'serialize'); + $sClass = utils::ReadParam('class', 'MissingAjaxParam', false, 'class'); + $sStyle = utils::ReadParam('style', 'list'); - case 'refreshDashletList': - $oPage->SetContentType('application/json'); - $aResult = AjaxRenderController::RefreshDashletList($sStyle, $sFilter); - $oPage->add(json_encode($aResult)); - break; - case 'refreshDashletSummary': - $oPage->SetContentType('text/html'); - $sExtraParams = utils::ReadParam('extra_params', '', false, 'raw_data'); - $aExtraParams = json_decode($sExtraParams, true); - $aQueryParams = []; - if (isset($aExtraParams['query_params'])) { - $aQueryParams = $aExtraParams['query_params']; - } - $oFilter = DBObjectSearch::FromOQL($sFilter, $aQueryParams); - $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); - $oSet = new CMDBObjectSet($oFilter, [], $aExtraParams); - $oBlock = new displayblock($oFilter, 'summary', false, [], $oSet); - $oBlock->RenderContent($oPage, $aExtraParams); - break; + $oAjaxRenderController = new AjaxRenderController(); - case 'datatable_save_settings': - $oPage->SetContentType('text/plain'); - $bRet = AjaxRenderController::DatatableSaveSettings(); - $oPage->add($bRet ? 'Ok' : 'KO'); - break; + switch ($operation) { + case 'search_and_refresh': + $oPage = new JsonPage(); + // Feeds dataTables directly + $oPage->SetOutputDataOnly(true); + $aResult = AjaxRenderController::SearchAndRefresh($sFilter); + $oPage->SetData($aResult); + break; - case 'datatable_reset_settings': - $oPage->SetContentType('text/plain'); - $bRet = AjaxRenderController::DatatableResetSettings(); - $oPage->add($bRet ? 'Ok' : 'KO'); - break; + case 'search': + $oPage = new JsonPage(); + // Feeds dataTables directly + $oPage->SetOutputDataOnly(true); + $aResult = AjaxRenderController::Search($sEncoding, $sFilter); + $oPage->SetData($aResult); + break; - // ui.searchformforeignkeys - case 'ShowModalSearchForeignKeys': - $oPage->SetContentType('text/html'); - $iInputId = utils::ReadParam('iInputId', ''); - $sTitle = utils::ReadParam('sTitle', '', false, 'raw_data'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId); - $oWidget->ShowModalSearchForeignKeys($oPage, $sTitle); - break; + case 'refreshDashletCount': + $oPage->SetContentType('application/json'); + $aResult = AjaxRenderController::RefreshDashletCount($sFilter); + $oPage->add(json_encode($aResult)); + break; - // ui.searchformforeignkeys - case 'GetFullListForeignKeysFromSelection': - $oPage->SetContentType('application/json'); - $oWidget = new UISearchFormForeignKeys($sClass); - $oFullSetFilter = new DBObjectSearch($sClass); - $oWidget->GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter); - break; + case 'refreshDashletList': + $oPage->SetContentType('application/json'); + $aResult = AjaxRenderController::RefreshDashletList($sStyle, $sFilter); + $oPage->add(json_encode($aResult)); + break; - // ui.searchformforeignkeys - case 'ListResultsSearchForeignKeys': - $oPage->SetContentType('text/html'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); - $oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId); - $oWidget->ListResultsSearchForeignKeys($oPage, $sRemoteClass); - break; - - // ui.linkswidget - case 'addObjects': - $oPage->SetContentType('text/html'); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - if (!empty($sJson)) - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - } - else - { - // Search form: no current object - $oObj = null; - } - $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); - $oAppContext = new ApplicationContext(); - $aPrefillFormParam = array( 'user' => Session::Get("auth_user"), - 'context' => $oAppContext->GetAsHash(), - 'att_code' => $sAttCode, - 'origin' => 'console', - 'source_obj' => $oObj - ); - $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); - /** @var \DBObject $oObj */ - $oWidget->GetObjectPickerDialog($oPage, $oObj, $sJson, $aAlreadyLinked, $aPrefillFormParam); - break; - - // ui.linkswidget - case 'searchObjectsToAdd': - $oPage->SetContentType('text/html'); - $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); - $sAttCode = utils::ReadParam('sAttCode', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $sSuffix = utils::ReadParam('sSuffix', ''); - $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; - $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); - $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); - $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); - break; - - //ui.linksdirectwidget - case 'createObject': - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sRealClass = utils::ReadParam('real_class', '', false, 'class'); - $sAttCode = utils::ReadParam('att_code', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $oPage->SetContentType('text/html'); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - if (!empty($sJson)) - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - } - $oObj = $oWizardHelper->GetTargetObject(); - $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oWidget->GetObjectCreationDlg($oPage, $sRealClass, $oObj); - break; - - // ui.linksdirectwidget - case 'getLinksetRow': - $oPage = new JsonPage(); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sRealClass = utils::ReadParam('real_class', '', false, 'class'); - $sAttCode = utils::ReadParam('att_code', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $iTempId = utils::ReadParam('tempId', ''); - $aValues = utils::ReadParam('values', array(), false, 'raw_data'); - $oPage->SetContentType('text/html'); - $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oPage->AddData($oWidget->GetFormRow($oPage, $sRealClass, $aValues, -$iTempId)); - break; - - // ui.linksdirectwidget - case 'selectObjectsToAdd': - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - /** @var \DBObject $oObj */ - $oObj = null; - if ($sJson != '') - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - } - $sRealClass = utils::ReadParam('real_class', '', false, 'class'); - $sAttCode = utils::ReadParam('att_code', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $iCurrObjectId = utils::ReadParam('iObjId', 0); - $oPage->SetContentType('text/html'); - $oAppContext = new ApplicationContext(); - $aPrefillFormParam = array( 'user' => Session::Get('auth_user'), - 'context' => $oAppContext->GetAsHash(), - 'att_code' => $sAttCode, - 'origin' => 'console', - 'source_obj' => $oObj, - ); - $aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass()); - $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oWidget->GetObjectsSelectionDlg($oPage, $oObj, $aAlreadyLinked, $aPrefillFormParam); - break; - - // ui.linksdirectwidget - case 'searchObjectsToAdd2': - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sRealClass = utils::ReadParam('real_class', '', false, 'class'); - $sAttCode = utils::ReadParam('att_code', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - $oObj = null; - if ($sJson != '') - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - } - $oAppContext = new ApplicationContext(); - $aPrefillFormParam = array( 'user' => Session::Get('auth_user'), - 'context' => $oAppContext->GetAsHash(), - 'att_code' => $sAttCode, - 'origin' => 'console', - 'source_obj' => $oObj, - ); - $aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass()); - $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oWidget->SearchObjectsToAdd($oPage, $sRealClass, $aAlreadyLinked, $oObj, $aPrefillFormParam); - break; - - // ui.linksdirectwidget - case 'doAddObjects2': - $oPage->SetContentType('text/html'); - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sRealClass = utils::ReadParam('real_class', '', false, 'class'); - $sAttCode = utils::ReadParam('att_code', ''); - $iInputId = utils::ReadParam('iInputId', ''); - $iCurrObjectId = utils::ReadParam('iObjId', 0); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - if ($sFilter != '') - { - $oFullSetFilter = DBObjectSearch::unserialize($sFilter); - } - else - { - $oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $valuesDef = $oLinksetDef->GetValuesDef(); - if ($valuesDef === null) - { - $oFullSetFilter = new DBObjectSearch($oLinksetDef->GetLinkedClass()); + case 'refreshDashletSummary': + $oPage->SetContentType('text/html'); + $sExtraParams = utils::ReadParam('extra_params', '', false, 'raw_data'); + $aExtraParams = json_decode($sExtraParams, true); + $aQueryParams = []; + if (isset($aExtraParams['query_params'])) { + $aQueryParams = $aExtraParams['query_params']; } - else - { - if (!$valuesDef instanceof ValueSetObjects) - { - throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').'); - } - $oFullSetFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression()); - } - } - $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); - $oWidget->DoAddObjects($oPage, $oFullSetFilter); - break; + $oFilter = DBObjectSearch::FromOQL($sFilter, $aQueryParams); + $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); + $oSet = new CMDBObjectSet($oFilter, [], $aExtraParams); + $oBlock = new displayblock($oFilter, 'summary', false, [], $oSet); + $oBlock->RenderContent($oPage, $aExtraParams); + break; - //////////////////////////////////////////////////////////// + case 'datatable_save_settings': + $oPage->SetContentType('text/plain'); + $bRet = AjaxRenderController::DatatableSaveSettings(); + $oPage->add($bRet ? 'Ok' : 'KO'); + break; - // ui.extkeywidget - case 'searchObjectsToSelect': - $oPage->SetContentType('text/html'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); - $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - $sAttCode = utils::ReadParam('sAttCode', ''); - $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); - if (!empty($sJson)) - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - } - else - { - // Search form: no current object - $oObj = null; - } - $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode); - $oWidget->SearchObjectsToSelect($oPage, $sFilter, $sRemoteClass, $oObj); - break; + case 'datatable_reset_settings': + $oPage->SetContentType('text/plain'); + $bRet = AjaxRenderController::DatatableResetSettings(); + $oPage->add($bRet ? 'Ok' : 'KO'); + break; - // ui.extkeywidget: autocomplete - case 'ac_extkey': - $oPage->SetContentType('text/plain'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - $sContains = utils::ReadParam('q', '', false, 'raw_data'); - $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); - $sOutputFormat = utils::ReadParam('sOutputFormat', UIExtKeyWidget::ENUM_OUTPUT_FORMAT_CSV, false, 'raw_data'); - if ($sContains != '') - { - if (!empty($sJson)) - { + // ui.searchformforeignkeys + case 'ShowModalSearchForeignKeys': + $oPage->SetContentType('text/html'); + $iInputId = utils::ReadParam('iInputId', ''); + $sTitle = utils::ReadParam('sTitle', '', false, 'raw_data'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId); + $oWidget->ShowModalSearchForeignKeys($oPage, $sTitle); + break; + + // ui.searchformforeignkeys + case 'GetFullListForeignKeysFromSelection': + $oPage->SetContentType('application/json'); + $oWidget = new UISearchFormForeignKeys($sClass); + $oFullSetFilter = new DBObjectSearch($sClass); + $oWidget->GetFullListForeignKeysFromSelection($oPage, $oFullSetFilter); + break; + + // ui.searchformforeignkeys + case 'ListResultsSearchForeignKeys': + $oPage->SetContentType('text/html'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); + $oWidget = new UISearchFormForeignKeys($sTargetClass, $iInputId); + $oWidget->ListResultsSearchForeignKeys($oPage, $sRemoteClass); + break; + + // ui.linkswidget + case 'addObjects': + $oPage->SetContentType('text/html'); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + if (!empty($sJson)) { $oWizardHelper = WizardHelper::FromJSON($sJson); $oObj = $oWizardHelper->GetTargetObject(); - } - else - { + } else { // Search form: no current object $oObj = null; } + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); + $oAppContext = new ApplicationContext(); + $aPrefillFormParam = array( + 'user' => Session::Get("auth_user"), + 'context' => $oAppContext->GetAsHash(), + 'att_code' => $sAttCode, + 'origin' => 'console', + 'source_obj' => $oObj + ); + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + /** @var \DBObject $oObj */ + $oWidget->GetObjectPickerDialog($oPage, $oObj, $sJson, $aAlreadyLinked, $aPrefillFormParam); + break; + + // ui.linkswidget + case 'searchObjectsToAdd': + $oPage->SetContentType('text/html'); + $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); + $sAttCode = utils::ReadParam('sAttCode', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $sSuffix = utils::ReadParam('sSuffix', ''); + $bDuplicates = (utils::ReadParam('bDuplicates', 'false') == 'false') ? false : true; + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId, $sSuffix, $bDuplicates); + $oWidget->SearchObjectsToAdd($oPage, $sRemoteClass, $aAlreadyLinked); + break; + + //ui.linksdirectwidget + case 'createObject': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $oPage->SetContentType('text/html'); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } + $oObj = $oWizardHelper->GetTargetObject(); + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->GetObjectCreationDlg($oPage, $sRealClass, $oObj); + break; + + // ui.linksdirectwidget + case 'getLinksetRow': + $oPage = new JsonPage(); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iTempId = utils::ReadParam('tempId', ''); + $aValues = utils::ReadParam('values', array(), false, 'raw_data'); + $oPage->SetContentType('text/html'); + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oPage->AddData($oWidget->GetFormRow($oPage, $sRealClass, $aValues, -$iTempId)); + break; + + // ui.linksdirectwidget + case 'selectObjectsToAdd': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + /** @var \DBObject $oObj */ + $oObj = null; + if ($sJson != '') { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iCurrObjectId = utils::ReadParam('iObjId', 0); + $oPage->SetContentType('text/html'); + $oAppContext = new ApplicationContext(); + $aPrefillFormParam = array( + 'user' => Session::Get('auth_user'), + 'context' => $oAppContext->GetAsHash(), + 'att_code' => $sAttCode, + 'origin' => 'console', + 'source_obj' => $oObj, + ); + $aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass()); + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->GetObjectsSelectionDlg($oPage, $oObj, $aAlreadyLinked, $aPrefillFormParam); + break; + + // ui.linksdirectwidget + case 'searchObjectsToAdd2': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $aAlreadyLinked = utils::ReadParam('aAlreadyLinked', array()); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $oObj = null; + if ($sJson != '') { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } + $oAppContext = new ApplicationContext(); + $aPrefillFormParam = array( + 'user' => Session::Get('auth_user'), + 'context' => $oAppContext->GetAsHash(), + 'att_code' => $sAttCode, + 'origin' => 'console', + 'source_obj' => $oObj, + ); + $aPrefillFormParam['dest_class'] = ($oObj === null ? '' : $oObj->Get($sAttCode)->GetClass()); + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->SearchObjectsToAdd($oPage, $sRealClass, $aAlreadyLinked, $oObj, $aPrefillFormParam); + break; + + // ui.linksdirectwidget + case 'doAddObjects2': + $oPage->SetContentType('text/html'); + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sRealClass = utils::ReadParam('real_class', '', false, 'class'); + $sAttCode = utils::ReadParam('att_code', ''); + $iInputId = utils::ReadParam('iInputId', ''); + $iCurrObjectId = utils::ReadParam('iObjId', 0); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + if ($sFilter != '') { + $oFullSetFilter = DBObjectSearch::unserialize($sFilter); + } else { + $oLinksetDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $valuesDef = $oLinksetDef->GetValuesDef(); + if ($valuesDef === null) { + $oFullSetFilter = new DBObjectSearch($oLinksetDef->GetLinkedClass()); + } else { + if (!$valuesDef instanceof ValueSetObjects) { + throw new Exception('Error: only ValueSetObjects are supported for "allowed_values" in AttributeLinkedSet ('.$this->sClass.'/'.$this->sAttCode.').'); + } + $oFullSetFilter = DBObjectSearch::FromOQL($valuesDef->GetFilterExpression()); + } + } + $oWidget = new UILinksWidgetDirect($sClass, $sAttCode, $iInputId); + $oWidget->DoAddObjects($oPage, $oFullSetFilter); + break; + + //////////////////////////////////////////////////////////// + + // ui.extkeywidget + case 'searchObjectsToSelect': + $oPage->SetContentType('text/html'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $sRemoteClass = utils::ReadParam('sRemoteClass', '', false, 'class'); + $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $sAttCode = utils::ReadParam('sAttCode', ''); + $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } else { + // Search form: no current object + $oObj = null; + } + $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode); + $oWidget->SearchObjectsToSelect($oPage, $sFilter, $sRemoteClass, $oObj); + break; + + // ui.extkeywidget: autocomplete + case 'ac_extkey': + $oPage->SetContentType('text/plain'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $sContains = utils::ReadParam('q', '', false, 'raw_data'); + $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); + $sOutputFormat = utils::ReadParam('sOutputFormat', UIExtKeyWidget::ENUM_OUTPUT_FORMAT_CSV, false, 'raw_data'); + if ($sContains != '') { + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } else { + // Search form: no current object + $oObj = null; + } + $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode); + $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat); + } + break; + + // ui.extkeywidget + case 'objectSearchForm': + $oPage->SetContentType('text/html'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $sFilter = utils::ReadParam('sFilter', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + $iInputId = utils::ReadParam('iInputId', ''); + $sTitle = utils::ReadParam('sTitle', '', false, 'raw_data'); + $sAttCode = utils::ReadParam('sAttCode', ''); + $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); + $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode, $sFilter); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } else { + // Search form: no current object + $oObj = null; + } + $oWidget->GetSearchDialog($oPage, $sTitle, $oObj); + break; + + // ui.extkeywidget + case 'objectCreationForm': + $oPage->SetContentType('text/html'); + // Retrieving parameters + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + // Building form, if target class is abstract we ask the user for the desired leaf class + $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false); + if (MetaModel::IsAbstract($sTargetClass)) { + $oWidget->GetClassSelectionForm($oPage); + } else { + $aPrefillFormParam = array(); + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + $oAppContext = new ApplicationContext(); + $aPrefillFormParam = array( + 'user' => Session::Get('auth_user'), + 'context' => $oAppContext->GetAsHash(), + 'att_code' => $sAttCode, + 'source_obj' => $oObj, + 'origin' => 'console' + ); + } else { + // Search form: no current object + $oObj = null; + } + $oWidget->GetObjectCreationForm($oPage, $oObj, $aPrefillFormParam); + } + break; + + // ui.extkeywidget + case 'doCreateObject': + $oPage->SetContentType('application/json'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $sFormPrefix = utils::ReadParam('sFormPrefix', ''); + $sAttCode = utils::ReadParam('sAttCode', ''); + $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false); + $aResult = $oWidget->DoCreateObject($oPage); + echo json_encode($aResult); + break; + + // ui.extkeywidget + case 'getObjectName': + $oPage->SetContentType('application/json'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $iInputId = utils::ReadParam('iInputId', ''); + $iObjectId = utils::ReadParam('iObjectId', ''); + $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); + $sFormAttCode = utils::ReadParam('sFormAttCode', null); $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode); - $oWidget->AutoComplete($oPage, $sFilter, $oObj, $sContains, $sOutputFormat); - } - break; + $sName = $oWidget->GetObjectName($iObjectId, $sFormAttCode); + echo json_encode(array('name' => $sName)); + break; - // ui.extkeywidget - case 'objectSearchForm': - $oPage->SetContentType('text/html'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $sFilter = utils::ReadParam('sFilter', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); - $iInputId = utils::ReadParam('iInputId', ''); - $sTitle = utils::ReadParam('sTitle', '', false, 'raw_data'); - $sAttCode = utils::ReadParam('sAttCode', ''); - $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); - $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, $bSearchMode, $sFilter); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - if (!empty($sJson)) { + // ui.extkeywidget + case 'displayHierarchy': + $oPage->SetContentType('text/html'); + $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); + $sInputId = utils::ReadParam('sInputId', ''); + $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); + $sJson = utils::ReadParam('json', '', false, 'raw_data'); + $currValue = utils::ReadParam('value', ''); + $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); + if (!empty($sJson)) { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); + } else { + // Search form: no current object + $oObj = null; + } + $oWidget = new UIExtKeyWidget($sTargetClass, $sInputId, '', $bSearchMode); + $oWidget->DisplayHierarchy($oPage, $sFilter, $currValue, $oObj); + break; + + //////////////////////////////////////////////////// + + // ui.linkswidget + case 'doAddObjects': + $oPage->SetContentType('text/html'); + AjaxRenderController::DoAddObjects($oPage, $sClass, $sFilter); + break; + + // ui.linkswidget + case 'doAddIndirectLinks': + $oPage = new JsonPage(); + AjaxRenderController::DoAddIndirectLinks($oPage, $sClass, $sFilter); + break; + //////////////////////////////////////////////////////////// + /// WizardHelper : see the corresponding PHP class, and JS class + + case 'wizard_helper_preview': + $oPage->SetContentType('text/html'); + $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); $oWizardHelper = WizardHelper::FromJSON($sJson); $oObj = $oWizardHelper->GetTargetObject(); - } else { - // Search form: no current object - $oObj = null; - } - $oWidget->GetSearchDialog($oPage, $sTitle, $oObj); - break; + $oObj->DisplayBareProperties($oPage); + break; - // ui.extkeywidget - case 'objectCreationForm': - $oPage->SetContentType('text/html'); - // Retrieving parameters - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - // Building form, if target class is abstract we ask the user for the desired leaf class - $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false); - if(MetaModel::IsAbstract($sTargetClass)) - { - $oWidget->GetClassSelectionForm($oPage); - } - else - { - $aPrefillFormParam = array(); - if (!empty($sJson)) - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $oAppContext = new ApplicationContext(); - $aPrefillFormParam = array( 'user' => Session::Get('auth_user'), - 'context' => $oAppContext->GetAsHash(), - 'att_code' => $sAttCode, - 'source_obj' => $oObj, - 'origin' => 'console' - ); - } - else - { - // Search form: no current object - $oObj = null; - } - $oWidget->GetObjectCreationForm($oPage, $oObj, $aPrefillFormParam); - } - break; - - // ui.extkeywidget - case 'doCreateObject': - $oPage->SetContentType('application/json'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $sFormPrefix = utils::ReadParam('sFormPrefix', ''); - $sAttCode = utils::ReadParam('sAttCode', ''); - $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false); - $aResult = $oWidget->DoCreateObject($oPage); - echo json_encode($aResult); - break; + case 'wizard_helper': + $oPage->SetContentType('text/html'); + $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); + $oWizardHelper = WizardHelper::FromJSON($sJson); + /** @var \DBObject $oObj */ + $oObj = $oWizardHelper->GetTargetObject(); + $sClass = $oWizardHelper->GetTargetClass(); + foreach ($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $defaultValue = $oAttDef->GetDefaultValue($oObj); + $oWizardHelper->SetDefaultValue($sAttCode, $defaultValue); + $oObj->Set($sAttCode, $defaultValue); + } + $sFormPrefix = $oWizardHelper->GetFormPrefix(); + $aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(), $oWizardHelper->GetInitialState()); + foreach ($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) { + $sId = $oWizardHelper->GetIdForField($sAttCode); + if ($sId != '') { + if (array_key_exists($sAttCode, $aExpectedAttributes)) { + $iFlags = $aExpectedAttributes[$sAttCode]; + } elseif ($oObj->IsNew()) { + $iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode); + } else { + $iFlags = $oObj->GetAttributeFlags($sAttCode); + } + if ($iFlags & OPT_ATT_READONLY) { + $sHTMLValue = "".$oObj->GetAsHTML($sAttCode); + $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + } else { + // It may happen that the field we'd like to update does not + // exist in the form. For example, if the field should be hidden/read-only + // in the current state of the object + $value = $oObj->Get($sAttCode); + $displayValue = $oObj->GetEditValue($sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if (!$oAttDef->IsWritable() || ($oWizardHelper->GetReturnNotEditableFields())) { + // Even non-writable fields (like AttributeExternal) can be refreshed + $sHTMLValue = "
".$oObj->GetAsHTML($sAttCode)."
"; + } else { + $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, + $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix), false); + // Make sure that we immediately validate the field when we reload it + $oPage->add_ready_script("$('#$sId').trigger('validate');"); + } + $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); + } + } + } + $oPage->add_script($oWizardHelper->GetJsForUpdateFields()); + break; - // ui.extkeywidget - case 'getObjectName': - $oPage->SetContentType('application/json'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $iInputId = utils::ReadParam('iInputId', ''); - $iObjectId = utils::ReadParam('iObjectId', ''); - $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); - $sFormAttCode = utils::ReadParam('sFormAttCode', null); - $oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, '', $bSearchMode); - $sName = $oWidget->GetObjectName($iObjectId, $sFormAttCode); - echo json_encode(array('name' => $sName)); - break; - - // ui.extkeywidget - case 'displayHierarchy': - $oPage->SetContentType('text/html'); - $sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class'); - $sInputId = utils::ReadParam('sInputId', ''); - $sFilter = utils::ReadParam('sFilter', '', false, 'raw_data'); - $sJson = utils::ReadParam('json', '', false, 'raw_data'); - $currValue = utils::ReadParam('value', ''); - $bSearchMode = (utils::ReadParam('bSearchMode', 'false') == 'true'); - if (!empty($sJson)) - { + case 'obj_creation_form': + $oPage->SetContentType('text/html'); + $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); $oWizardHelper = WizardHelper::FromJSON($sJson); $oObj = $oWizardHelper->GetTargetObject(); - } - else - { - // Search form: no current object - $oObj = null; - } - $oWidget = new UIExtKeyWidget($sTargetClass, $sInputId, '', $bSearchMode); - $oWidget->DisplayHierarchy($oPage, $sFilter, $currValue, $oObj); - break; + $sClass = $oWizardHelper->GetTargetClass(); + $sTargetState = utils::ReadParam('target_state', ''); + $iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); + $oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState); + cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId)); + break; - //////////////////////////////////////////////////// - - // ui.linkswidget - case 'doAddObjects': - $oPage->SetContentType('text/html'); - AjaxRenderController::DoAddObjects($oPage, $sClass, $sFilter); - break; - - // ui.linkswidget - case 'doAddIndirectLinks': - $oPage = new JsonPage(); - AjaxRenderController::DoAddIndirectLinks($oPage, $sClass, $sFilter); - break; - //////////////////////////////////////////////////////////// - /// WizardHelper : see the corresponding PHP class, and JS class - - case 'wizard_helper_preview': - $oPage->SetContentType('text/html'); - $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $oObj->DisplayBareProperties($oPage); - break; - - case 'wizard_helper': - $oPage->SetContentType('text/html'); - $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); - $oWizardHelper = WizardHelper::FromJSON($sJson); - /** @var \DBObject $oObj */ - $oObj = $oWizardHelper->GetTargetObject(); - $sClass = $oWizardHelper->GetTargetClass(); - foreach($oWizardHelper->GetFieldsForDefaultValue() as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $defaultValue = $oAttDef->GetDefaultValue($oObj); - $oWizardHelper->SetDefaultValue($sAttCode, $defaultValue); - $oObj->Set($sAttCode, $defaultValue); - } - $sFormPrefix = $oWizardHelper->GetFormPrefix(); - $aExpectedAttributes = ($oWizardHelper->GetStimulus() === null) ? array() : $oObj->GetTransitionAttributes($oWizardHelper->GetStimulus(), $oWizardHelper->GetInitialState()); - foreach($oWizardHelper->GetFieldsForAllowedValues() as $sAttCode) - { - $sId = $oWizardHelper->GetIdForField($sAttCode); - if ($sId != '') - { - if (array_key_exists($sAttCode, $aExpectedAttributes)) - { - $iFlags = $aExpectedAttributes[$sAttCode]; + // DisplayBlock + case 'ajax': + $oPage->SetContentType('text/html'); + if ($sFilter != "") { + $sExtraParams = stripslashes(utils::ReadParam('extra_params', '', false, 'raw_data')); + $aExtraParams = array(); + if (!empty($sExtraParams)) { + $aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); } - elseif ($oObj->IsNew()) - { - $iFlags = $oObj->GetInitialStateAttributeFlags($sAttCode); - } - else - { - $iFlags = $oObj->GetAttributeFlags($sAttCode); - } - if ($iFlags & OPT_ATT_READONLY) - { - $sHTMLValue = "".$oObj->GetAsHTML($sAttCode); - $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); - } - else - { - // It may happen that the field we'd like to update does not - // exist in the form. For example, if the field should be hidden/read-only - // in the current state of the object - $value = $oObj->Get($sAttCode); - $displayValue = $oObj->GetEditValue($sAttCode); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if (!$oAttDef->IsWritable() || ($oWizardHelper->GetReturnNotEditableFields())) - { - // Even non-writable fields (like AttributeExternal) can be refreshed - $sHTMLValue = "
".$oObj->GetAsHTML($sAttCode)."
"; + // Restore the app context from the ExtraParams + $oAppContext = new ApplicationContext(false); // false => don't read the context yet ! + $aContext = array(); + foreach ($oAppContext->GetNames() as $sName) { + $sParamName = 'c['.$sName.']'; + if (isset($aExtraParams[$sParamName])) { + $aContext[$sName] = $aExtraParams[$sParamName]; } - else - { - $sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value, - $displayValue, $sId, '', $iFlags, array('this' => $oObj, 'formPrefix' => $sFormPrefix), false); - // Make sure that we immediately validate the field when we reload it - $oPage->add_ready_script("$('#$sId').trigger('validate');"); + } + $_REQUEST['c'] = $aContext; + if ($sEncoding == 'oql') { + $oFilter = DBSearch::FromOQL($sFilter); + } else { + try { + $oFilter = DBSearch::unserialize($sFilter); + } + catch (CoreException $e) { + $sFilter = utils::HtmlEntities($sFilter); + $oPage->p("Invalid query (invalid filter) : $sFilter"); + IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter"); + break; } - $oWizardHelper->SetAllowedValuesHtml($sAttCode, $sHTMLValue); } + $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); + $aExtraParams['display_limit'] = true; + $oDisplayBlock->RenderContent($oPage, $aExtraParams); + } else { + $oPage->p("Invalid query (empty filter)."); } - } - $oPage->add_script($oWizardHelper->GetJsForUpdateFields()); - break; + break; - case 'obj_creation_form': - $oPage->SetContentType('text/html'); - $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); - $sClass = $oWizardHelper->GetTargetClass(); - $sTargetState = utils::ReadParam('target_state', ''); - $iTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); - $oObj->Set(MetaModel::GetStateAttributeCode($sClass), $sTargetState); - cmdbAbstractObject::DisplayCreationForm($oPage, $sClass, $oObj, array(), array('action' => utils::GetAbsoluteUrlAppRoot().'pages/UI.php', 'transaction_id' => $iTransactionId)); - break; + case 'displayCSVHistory': + $oPage->SetContentType('text/html'); + $bShowAll = (utils::ReadParam('showall', 'false') == 'true'); + BulkChange::DisplayImportHistory($oPage, true, $bShowAll); + break; - // DisplayBlock - case 'ajax': - $oPage->SetContentType('text/html'); - if ($sFilter != "") - { - $sExtraParams = stripslashes(utils::ReadParam('extra_params', '', false, 'raw_data')); - $aExtraParams = array(); - if (!empty($sExtraParams)) - { - $aExtraParams = json_decode(str_replace("'", '"', $sExtraParams), true /* associative array */); - } - // Restore the app context from the ExtraParams - $oAppContext = new ApplicationContext(false); // false => don't read the context yet ! - $aContext = array(); - foreach($oAppContext->GetNames() as $sName) - { - $sParamName = 'c['.$sName.']'; - if (isset($aExtraParams[$sParamName])) - { - $aContext[$sName] = $aExtraParams[$sParamName]; - } - } - $_REQUEST['c'] = $aContext; - if ($sEncoding == 'oql') - { - $oFilter = DBSearch::FromOQL($sFilter); - } - else - { - try - { + case 'details': + $oPage->SetContentType('text/html'); + $key = utils::ReadParam('id', 0); + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', $key, '='); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + break; + + case 'pie_chart': + $oPage->SetContentType('application/json'); + $sGroupBy = utils::ReadParam('group_by', ''); + if ($sFilter != '') { + if ($sEncoding == 'oql') { + $oFilter = DBSearch::FromOQL($sFilter); + } else { $oFilter = DBSearch::unserialize($sFilter); } - catch (CoreException $e) - { - $sFilter = utils::HtmlEntities($sFilter); - $oPage->p("Invalid query (invalid filter) : $sFilter"); - IssueLog::Error("ajax.render operation='ajax', invalid DBSearch filter param : $sFilter"); - break; - } - } - $oDisplayBlock = new DisplayBlock($oFilter, $sStyle, false); - $aExtraParams['display_limit'] = true; - $oDisplayBlock->RenderContent($oPage, $aExtraParams); - } - else - { - $oPage->p("Invalid query (empty filter)."); - } - break; - - case 'displayCSVHistory': - $oPage->SetContentType('text/html'); - $bShowAll = (utils::ReadParam('showall', 'false') == 'true'); - BulkChange::DisplayImportHistory($oPage, true, $bShowAll); - break; - - case 'details': - $oPage->SetContentType('text/html'); - $key = utils::ReadParam('id', 0); - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', $key, '='); - $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); - $oDisplayBlock->RenderContent($oPage); - break; - - case 'pie_chart': - $oPage->SetContentType('application/json'); - $sGroupBy = utils::ReadParam('group_by', ''); - if ($sFilter != '') - { - if ($sEncoding == 'oql') - { - $oFilter = DBSearch::FromOQL($sFilter); - } - else - { - $oFilter = DBSearch::unserialize($sFilter); - } - $oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false); - $oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy)); - } - else - { - - $oPage->add("\n3d pie\n."); - } - break; - - case 'chart': - $iRefresh = utils::ReadParam('refresh', '-1', false, 'int'); - if ($iRefresh != -1) { - $oPage->SetContentType('application/json'); - $aParams = utils::ReadParam('params', array(), false, 'raw_data'); - if ($sFilter != '') { - $oFilter = DBObjectSearch::FromOQL($sFilter); - $oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false); - $oBlock = $oDisplayBlock->GetRenderContent($oPage, $aParams, $aParams['currentId']); - $sChartType = isset($aParams['chart_type']) ? $aParams['chart_type'] : 'pie'; - switch ($sChartType) { - case 'bars': - $aResult['type'] = 'bars'; - //$aResult['JSNames'] = str_replace('"','\'',$oBlock->sJSNames); - $aResult['Json'] = str_replace('"', '\'', $oBlock->sJson); - $aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs); - $aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson). - ',keys: { x: \'label\', value: [\'value\']'. - '},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})'; - break; - - case 'pie': - $aResult['type'] = 'pie'; - $aResult['JSColumns'] = str_replace('"', '\'', $oBlock->sJSColumns); - $aResult['JSNames'] = str_replace('"', '\'', $oBlock->sJSNames); - //$aResult['JSNames'] = json_decode($oBlock->sJSNames); - $aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs); - $aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns). - ',names: '.str_replace('"', '\'', $oBlock->sJSNames). - ',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})'; - break; - } - } else { - $aResult = []; - } - - $oPage->add(json_encode($aResult)); - } else { - // Workaround for IE8 + IIS + HTTPS - // See TRAC #363, fix described here: http://forums.codecharge.com/posts.php?post_id=97771 - $oPage->add_header("Cache-Control: cache, must-revalidate"); - $oPage->add_header("Pragma: public"); - $oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); - - $aParams = utils::ReadParam('params', array(), false, 'raw_data'); - if ($sFilter != '') { - $oFilter = DBSearch::unserialize($sFilter); - $oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false); - $oDisplayBlock->RenderContent($oPage, $aParams); + $oDisplayBlock = new DisplayBlock($oFilter, 'pie_chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, array('group_by' => $sGroupBy)); } else { $oPage->add("\n3d pie\n."); } - } - break; + break; - case 'modal_details': - $oPage->SetContentType('text/html'); - $key = utils::ReadParam('id', 0); - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition('id', $key, '='); - $oPage->Add("

Object Details

\n"); - $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); - $oDisplayBlock->RenderContent($oPage); - $oPage->Add("\n"); - break; + case 'chart': + $iRefresh = utils::ReadParam('refresh', '-1', false, 'int'); + if ($iRefresh != -1) { + $oPage->SetContentType('application/json'); + $aParams = utils::ReadParam('params', array(), false, 'raw_data'); + if ($sFilter != '') { + $oFilter = DBObjectSearch::FromOQL($sFilter); + $oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false); + $oBlock = $oDisplayBlock->GetRenderContent($oPage, $aParams, $aParams['currentId']); + $sChartType = isset($aParams['chart_type']) ? $aParams['chart_type'] : 'pie'; + switch ($sChartType) { + case 'bars': + $aResult['type'] = 'bars'; + //$aResult['JSNames'] = str_replace('"','\'',$oBlock->sJSNames); + $aResult['Json'] = str_replace('"', '\'', $oBlock->sJson); + $aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs); + $aResult['js'] = 'charts['.$iRefresh.'].load({json: '.str_replace('"', '\'', $oBlock->sJson). + ',keys: { x: \'label\', value: [\'value\']'. + '},onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})'; + break; - case 'link': - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('sclass', 'logInfra', false, 'class'); - $sAttCode = utils::ReadParam('attCode', 'name'); - //$sOrg = utils::ReadParam('org_id', ''); - $sName = utils::ReadParam('q', ''); - $iMaxCount = utils::ReadParam('max', 30); - $iCount = 0; - $oFilter = new DBObjectSearch($sClass); - $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); - //$oFilter->AddCondition('org_id', $sOrg, '='); - $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); - while (($iCount < $iMaxCount) && ($oObj = $oSet->fetch())) - { - $oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n"); - $iCount++; - } - break; + case 'pie': + $aResult['type'] = 'pie'; + $aResult['JSColumns'] = str_replace('"', '\'', $oBlock->sJSColumns); + $aResult['JSNames'] = str_replace('"', '\'', $oBlock->sJSNames); + //$aResult['JSNames'] = json_decode($oBlock->sJSNames); + $aResult['JSURLs'] = str_replace('"', '\'', $oBlock->sJSURLs); + $aResult['js'] = 'charts['.$iRefresh.'].load({columns: '.str_replace('"', '\'', $oBlock->sJSColumns). + ',names: '.str_replace('"', '\'', $oBlock->sJSNames). + ',onclick: function (d) { var aURLs = $.parseJSON('.str_replace('"', '\'', $oBlock->sJSURLs).'); window.location.href= aURLs[d.index]; }})'; + break; + } + } else { + $aResult = []; + } - case 'combo_options': - $oPage->SetContentType('text/html'); - $oFilter = DBSearch::FromOQL($sFilter); - $oSet = new CMDBObjectSet($oFilter); - while ($oObj = $oSet->fetch()) - { - $oPage->add(''); - } - break; + $oPage->add(json_encode($aResult)); + } else { + // Workaround for IE8 + IIS + HTTPS + // See TRAC #363, fix described here: http://forums.codecharge.com/posts.php?post_id=97771 + $oPage->add_header("Cache-Control: cache, must-revalidate"); + $oPage->add_header("Pragma: public"); + $oPage->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); - case 'display_document': - $id = utils::ReadParam('id', ''); - $sField = utils::ReadParam('field', ''); - if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField)) - { + $aParams = utils::ReadParam('params', array(), false, 'raw_data'); + if ($sFilter != '') { + $oFilter = DBSearch::unserialize($sFilter); + $oDisplayBlock = new DisplayBlock($oFilter, 'chart_ajax', false); + $oDisplayBlock->RenderContent($oPage, $aParams); + } else { + + $oPage->add("\n3d pie\n."); + } + } + break; + + case 'modal_details': + $oPage->SetContentType('text/html'); + $key = utils::ReadParam('id', 0); + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition('id', $key, '='); + $oPage->Add("

Object Details

\n"); + $oDisplayBlock = new DisplayBlock($oFilter, 'details', false); + $oDisplayBlock->RenderContent($oPage); + $oPage->Add("\n"); + break; + + case 'link': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('sclass', 'logInfra', false, 'class'); + $sAttCode = utils::ReadParam('attCode', 'name'); + //$sOrg = utils::ReadParam('org_id', ''); + $sName = utils::ReadParam('q', ''); + $iMaxCount = utils::ReadParam('max', 30); + $iCount = 0; + $oFilter = new DBObjectSearch($sClass); + $oFilter->AddCondition($sAttCode, $sName, 'Begins with'); + //$oFilter->AddCondition('org_id', $sOrg, '='); + $oSet = new CMDBObjectSet($oFilter, array($sAttCode => true)); + while (($iCount < $iMaxCount) && ($oObj = $oSet->fetch())) { + $oPage->add($oObj->GetAsHTML($sAttCode)."|".$oObj->GetKey()."\n"); + $iCount++; + } + break; + + case 'combo_options': + $oPage->SetContentType('text/html'); + $oFilter = DBSearch::FromOQL($sFilter); + $oSet = new CMDBObjectSet($oFilter); + while ($oObj = $oSet->fetch()) { + $oPage->add(''); + } + break; + + case 'display_document': + $id = utils::ReadParam('id', ''); + $sField = utils::ReadParam('field', ''); + if (!empty($sClass) && ($sClass != 'InlineImage') && !empty($id) && !empty($sField)) { + $oPage = new DownloadPage(''); + // X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page + // so we're resetting its value ! (see N°3416) + $oPage->add_xframe_options(''); + $iCacheSec = (int)utils::ReadParam('cache', 0); + $oPage->set_cache($iCacheSec); + + // N°4129 - Prevent XSS attacks & other script executions + if (utils::GetConfig()->Get('security.disable_inline_documents_sandbox') === false) { + $oPage->add_header('Content-Security-Policy: sandbox;'); + } + + ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline'); + } + break; + + case 'search_form': + $oPage->SetContentType('text/html'); + $sClass = utils::ReadParam('className', '', false, 'class'); + $sRootClass = utils::ReadParam('baseClass', '', false, 'class'); + $currentId = utils::ReadParam('currentId', ''); + $sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data'); + $sAction = utils::ReadParam('action', ''); + $sSelectionMode = utils::ReadParam('selection_mode', null, false, 'raw_data'); + $sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null, false, 'raw_data'); + $scssCount = utils::ReadParam('css_count', null, false, 'raw_data'); + $sTableInnerId = utils::ReadParam('table_inner_id', $sTableId, false, 'raw_data'); + + $oFilter = new DBObjectSearch($sClass); + $oSet = new CMDBObjectSet($oFilter); + $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array( + 'currentId' => $currentId, + 'baseClass' => $sRootClass, + 'action' => $sAction, + 'table_id' => $sTableId, + 'selection_mode' => $sSelectionMode, + 'result_list_outer_selector' => $sResultListOuterSelector, + 'cssCount' => $scssCount, + 'table_inner_id' => $sTableInnerId + )); + $oPage->add($sHtml); + break; + + case 'set_pref': + $sCode = utils::ReadPostedParam('code', '', 'raw_data'); + $sValue = utils::ReadPostedParam('value', '', 'raw_data'); + appUserPreferences::SetPref($sCode, $sValue); + break; + + case 'erase_all_pref': + // Can be useful in case a user got some corrupted prefs... + appUserPreferences::ClearPreferences(); + break; + + case 'on_form_cancel': + // Called when a creation/modification form is cancelled by the end-user + // Let's take this opportunity to inform the plug-ins so that they can perform some cleanup + $iTransactionId = utils::ReadParam('transaction_id', 0, false, 'transaction_id'); + $sTempId = utils::GetUploadTempId($iTransactionId); + InlineImage::OnFormCancel($sTempId); + /** @var \iApplicationUIExtension $oExtensionInstance */ + foreach (MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) { + $oExtensionInstance->OnFormCancel($sTempId); + } + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); + $sToken = utils::ReadParam('token', 0, false, 'raw_data'); + if (($sObjClass != '') && ($iObjKey != 0) && ($sToken != '')) { + $bReleaseLock = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken); + } + + IssueLog::Trace('on_form_cancel', $sObjClass, array( + '$iObjKey' => $iObjKey, + '$sTransactionId' => $iTransactionId, + '$sTempId' => $sTempId, + '$sToken' => $sToken, + '$sUser' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + + break; + + case 'dashboard': + $oPage->SetContentType('text/html'); + $id = (int)utils::ReadParam('id', 0); + $sAttCode = utils::ReadParam('attcode', ''); + /** @var \cmdbAbstractObject $oObj */ + $oObj = MetaModel::GetObject($sClass, $id); + // - Add graphs dependencies + WebResourcesHelper::EnableC3JSToWebPage($oPage); + $oObj->DisplayDashboard($oPage, $sAttCode); + break; + + case 'export_dashboard': $oPage = new DownloadPage(''); - // X-Frame http header : set in page constructor, but we need to allow frame integration for this specific page - // so we're resetting its value ! (see N°3416) - $oPage->add_xframe_options(''); - $iCacheSec = (int)utils::ReadParam('cache', 0); - $oPage->set_cache($iCacheSec); - - // N°4129 - Prevent XSS attacks & other script executions - if (utils::GetConfig()->Get('security.disable_inline_documents_sandbox') === false) { - $oPage->add_header('Content-Security-Policy: sandbox;'); + $sDashboardId = utils::ReadParam('id', '', false, 'raw_data'); + $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); + $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); + if (!is_null($oDashboard)) { + $oPage->TrashUnexpectedOutput(); + $oPage->SetContentType('text/xml'); + $oPage->SetContentDisposition('attachment', 'dashboard_'.$oDashboard->GetTitle().'.xml'); + $oPage->add($oDashboard->ToXml()); } + break; - ormDocument::DownloadDocument($oPage, $sClass, $id, $sField, 'inline'); - } - break; + case 'import_dashboard': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); - case 'search_form': - $oPage->SetContentType('text/html'); - $sClass = utils::ReadParam('className', '', false, 'class'); - $sRootClass = utils::ReadParam('baseClass', '', false, 'class'); - $currentId = utils::ReadParam('currentId', ''); - $sTableId = utils::ReadParam('_table_id_', null, false, 'raw_data'); - $sAction = utils::ReadParam('action', ''); - $sSelectionMode = utils::ReadParam('selection_mode', null,false,'raw_data'); - $sResultListOuterSelector = utils::ReadParam('result_list_outer_selector', null,false,'raw_data'); - $scssCount = utils::ReadParam('css_count', null,false,'raw_data'); - $sTableInnerId = utils::ReadParam('table_inner_id', $sTableId,false,'raw_data'); - - $oFilter = new DBObjectSearch($sClass); - $oSet = new CMDBObjectSet($oFilter); - $sHtml = cmdbAbstractObject::GetSearchForm($oPage, $oSet, array('currentId' => $currentId, - 'baseClass' => $sRootClass, - 'action' => $sAction, - 'table_id' => $sTableId, - 'selection_mode' => $sSelectionMode, - 'result_list_outer_selector' => $sResultListOuterSelector, - 'cssCount' => $scssCount, - 'table_inner_id' => $sTableInnerId)); - $oPage->add($sHtml); - break; - - case 'set_pref': - $sCode = utils::ReadPostedParam('code', '', 'raw_data'); - $sValue = utils::ReadPostedParam('value', '', 'raw_data'); - appUserPreferences::SetPref($sCode, $sValue); - break; - - case 'erase_all_pref': - // Can be useful in case a user got some corrupted prefs... - appUserPreferences::ClearPreferences(); - break; - - case 'on_form_cancel': - // Called when a creation/modification form is cancelled by the end-user - // Let's take this opportunity to inform the plug-ins so that they can perform some cleanup - $iTransactionId = utils::ReadParam('transaction_id', 0, false, 'transaction_id'); - $sTempId = utils::GetUploadTempId($iTransactionId); - InlineImage::OnFormCancel($sTempId); - /** @var \iApplicationUIExtension $oExtensionInstance */ - foreach(MetaModel::EnumPlugins('iApplicationUIExtension') as $oExtensionInstance) - { - $oExtensionInstance->OnFormCancel($sTempId); - } - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - $sToken = utils::ReadParam('token', 0, false, 'raw_data'); - if (($sObjClass != '') && ($iObjKey != 0) && ($sToken != '')) - { - $bReleaseLock = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken); - } - - IssueLog::Trace('on_form_cancel', $sObjClass, array( - '$iObjKey' => $iObjKey, - '$sTransactionId' => $iTransactionId, - '$sTempId' => $sTempId, - '$sToken' => $sToken, - '$sUser' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - - break; - - case 'dashboard': - $oPage->SetContentType('text/html'); - $id = (int)utils::ReadParam('id', 0); - $sAttCode = utils::ReadParam('attcode', ''); - /** @var \cmdbAbstractObject $oObj */ - $oObj = MetaModel::GetObject($sClass, $id); - // - Add graphs dependencies - WebResourcesHelper::EnableC3JSToWebPage($oPage); - $oObj->DisplayDashboard($oPage, $sAttCode); - break; - - case 'export_dashboard': - $oPage = new DownloadPage(''); - $sDashboardId = utils::ReadParam('id', '', false, 'raw_data'); - $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); - if (!is_null($oDashboard)) { - $oPage->TrashUnexpectedOutput(); - $oPage->SetContentType('text/xml'); - $oPage->SetContentDisposition('attachment', 'dashboard_'.$oDashboard->GetTitle().'.xml'); - $oPage->add($oDashboard->ToXml()); - } - break; - - case 'import_dashboard': - $oPage = new JsonPage(); - $oPage->SetOutputDataOnly(true); - - $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId, true)) - { - throw new SecurityException('ajax.render.php import_dashboard : invalid transaction_id'); - } - $sDashboardId = utils::ReadParam('id', '', false, 'raw_data'); - $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); - $aResult = array('error' => ''); - if (!is_null($oDashboard)) - { - try - { - $oDoc = utils::ReadPostedDocument('dashboard_upload_file'); - $oDashboard->FromXml($oDoc->GetData()); - $oDashboard->Save(); - } catch (DOMException $e) - { - $aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile')); - } catch (Exception $e) - { - $aResult = array('error' => $e->getMessage()); + $sTransactionId = utils::ReadParam('transaction_id', '', false, 'transaction_id'); + if (!utils::IsTransactionValid($sTransactionId, true)) { + throw new SecurityException('ajax.render.php import_dashboard : invalid transaction_id'); } - } - else - { - $aResult['error'] = 'Dashboard id="'.$sDashboardId.'" not found.'; - } - $oPage->SetData($aResult); - break; - - case 'toggle_dashboard': - $oPage->SetContentType('text/html'); - $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); - - $bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sDashboardId, false); - appUserPreferences::UnsetPref('display_original_dashboard_'.$sDashboardId); - appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, !$bStandardSelected); - - $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); - $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); - $aResult = array('error' => ''); - if (!is_null($oDashboard)) - { - if (!empty($sReloadURL)) - { - $oDashboard->SetReloadURL($sReloadURL); + $sDashboardId = utils::ReadParam('id', '', false, 'raw_data'); + $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); + $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); + $aResult = array('error' => ''); + if (!is_null($oDashboard)) { + try { + $oDoc = utils::ReadPostedDocument('dashboard_upload_file'); + $oDashboard->FromXml($oDoc->GetData()); + $oDashboard->Save(); + } + catch (DOMException $e) { + $aResult = array('error' => Dict::S('UI:Error:InvalidDashboardFile')); + } + catch (Exception $e) { + $aResult = array('error' => $e->getMessage()); + } + } else { + $aResult['error'] = 'Dashboard id="'.$sDashboardId.'" not found.'; } - $oDashboard->Render($oPage, false, $aExtraParams); - } - break; + $oPage->SetData($aResult); + break; - case 'reload_dashboard': - $oPage->SetContentType('text/html'); - $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); - $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); - $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); - $aResult = array('error' => ''); - if (!is_null($oDashboard)) - { - if (!empty($sReloadURL)) - { - $oDashboard->SetReloadURL($sReloadURL); + case 'toggle_dashboard': + $oPage->SetContentType('text/html'); + $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); + + $bStandardSelected = appUserPreferences::GetPref('display_original_dashboard_'.$sDashboardId, false); + appUserPreferences::UnsetPref('display_original_dashboard_'.$sDashboardId); + appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, !$bStandardSelected); + + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); + $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); + $aResult = array('error' => ''); + if (!is_null($oDashboard)) { + if (!empty($sReloadURL)) { + $oDashboard->SetReloadURL($sReloadURL); + } + $oDashboard->Render($oPage, false, $aExtraParams); } - $oDashboard->Render($oPage, false, $aExtraParams); - } - break; + break; - case 'save_dashboard': - $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'context_param'); + case 'reload_dashboard': + $oPage->SetContentType('text/html'); + $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $sDashboardFile = utils::ReadParam('file', '', false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); + $oDashboard = RuntimeDashboard::GetDashboard($sDashboardFile, $sDashboardId); + $aResult = array('error' => ''); + if (!is_null($oDashboard)) { + if (!empty($sReloadURL)) { + $oDashboard->SetReloadURL($sReloadURL); + } + $oDashboard->Render($oPage, false, $aExtraParams); + } + break; - $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); - appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, false); - $sJSExtraParams = json_encode($aExtraParams); - $aParams = array(); - $aParams['layout_class'] = utils::ReadParam('layout_class', ''); - $aParams['title'] = utils::ReadParam('title', '', false, 'raw_data'); - $aParams['auto_reload'] = utils::ReadParam('auto_reload', false); - $aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300); - $aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data'); + case 'save_dashboard': + $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'context_param'); - $oDashboard = new RuntimeDashboard($sDashboardId); - $oDashboard->FromParams($aParams); - $bIsNew = $oDashboard->Save(); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); + appUserPreferences::SetPref('display_original_dashboard_'.$sDashboardId, false); + $sJSExtraParams = json_encode($aExtraParams); + $aParams = array(); + $aParams['layout_class'] = utils::ReadParam('layout_class', ''); + $aParams['title'] = utils::ReadParam('title', '', false, 'raw_data'); + $aParams['auto_reload'] = utils::ReadParam('auto_reload', false); + $aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300); + $aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data'); - $sDashboardFile = addslashes(utils::ReadParam('file', '', false, 'string')); - $sDashboardDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sDashboardId); - $sOperation = 'reload_dashboard'; - if ($bIsNew) { - // Trigger a reload of the current page since the dashboard just changed - $oPage->add_script( - <<FromParams($aParams); + $bIsNew = $oDashboard->Save(); + + $sDashboardFile = addslashes(utils::ReadParam('file', '', false, 'string')); + $sDashboardDivId = preg_replace('/[^a-zA-Z0-9_]/', '', $sDashboardId); + $sOperation = 'reload_dashboard'; + if ($bIsNew) { + // Trigger a reload of the current page since the dashboard just changed + $oPage->add_script( + <<add_script( - <<add_script( + <<Revert(); - $sFile = addslashes($oDashboard->GetDefinitionFile()); - $sDivId = utils::Sanitize($sDashboardId, '', 'element_identifier'); - // trigger a reload of the current page since the dashboard just changed - $oPage->add_script( -<<Revert(); + $sFile = addslashes($oDashboard->GetDefinitionFile()); + $sDivId = utils::Sanitize($sDashboardId, '', 'element_identifier'); + // trigger a reload of the current page since the dashboard just changed + $oPage->add_script( + <<FromParams($aParams); - $oDashboard->SetReloadURL($sReloadURL); - $oDashboard->Render($oPage, true /* bEditMode */, $aExtraParams); - break; + case 'render_dashboard': + $sDashboardId = utils::ReadParam('dashboard_id', '', false, 'raw_data'); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $aParams = array(); + $aParams['layout_class'] = utils::ReadParam('layout_class', ''); + $aParams['title'] = utils::ReadParam('title', '', false, 'raw_data'); + $aParams['cells'] = utils::ReadParam('cells', array(), false, 'raw_data'); + $aParams['auto_reload'] = utils::ReadParam('auto_reload', false); + $aParams['auto_reload_sec'] = utils::ReadParam('auto_reload_sec', 300); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); + $oDashboard = new RuntimeDashboard($sDashboardId); + $oDashboard->FromParams($aParams); + $oDashboard->SetReloadURL($sReloadURL); + $oDashboard->Render($oPage, true /* bEditMode */, $aExtraParams); + break; - case 'dashboard_editor': - $sId = utils::ReadParam('id', '', false, 'context_param'); - $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $aExtraParams['dashboard_div_id'] = utils::Sanitize($sId, '', 'element_identifier'); - $sDashboardFile = utils::ReadParam('file', '', false, 'string'); - $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); - $oDashboard = RuntimeDashboard::GetDashboardToEdit($sDashboardFile, $sId); - if (!is_null($oDashboard)) { - if (!empty($sReloadURL)) { - $oDashboard->SetReloadURL($sReloadURL); + case 'dashboard_editor': + $sId = utils::ReadParam('id', '', false, 'context_param'); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $aExtraParams['dashboard_div_id'] = utils::Sanitize($sId, '', 'element_identifier'); + $sDashboardFile = utils::ReadParam('file', '', false, 'string'); + $sReloadURL = utils::ReadParam('reload_url', '', false, utils::ENUM_SANITIZATION_FILTER_URL); + $oDashboard = RuntimeDashboard::GetDashboardToEdit($sDashboardFile, $sId); + if (!is_null($oDashboard)) { + if (!empty($sReloadURL)) { + $oDashboard->SetReloadURL($sReloadURL); + } + $oDashboard->RenderEditor($oPage, $aExtraParams); } - $oDashboard->RenderEditor($oPage, $aExtraParams); - } - break; + break; - case 'new_dashlet_id': - $sDashboardDivId = utils::ReadParam("dashboardid"); - $bIsCustomized = true; // Only called at runtime when customizing a dashboard - $iRow = utils::ReadParam("iRow"); - $iCol = utils::ReadParam("iCol"); - $sDashletIdOrig = utils::ReadParam("dashletid"); - $sFinalDashletId = Dashboard::GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletIdOrig); - $oPage = new AjaxPage(''); - $oPage->SetOutputDataOnly(true); - $oPage->add($sFinalDashletId); - break; + case 'new_dashlet_id': + $sDashboardDivId = utils::ReadParam("dashboardid"); + $bIsCustomized = true; // Only called at runtime when customizing a dashboard + $iRow = utils::ReadParam("iRow"); + $iCol = utils::ReadParam("iCol"); + $sDashletIdOrig = utils::ReadParam("dashletid"); + $sFinalDashletId = Dashboard::GetDashletUniqueId($bIsCustomized, $sDashboardDivId, $iRow, $iCol, $sDashletIdOrig); + $oPage = new AjaxPage(''); + $oPage->SetOutputDataOnly(true); + $oPage->add($sFinalDashletId); + break; - case 'new_dashlet': - require_once(APPROOT.'application/forms.class.inc.php'); - require_once(APPROOT.'application/dashlet.class.inc.php'); - $sDashletClass = utils::ReadParam('dashlet_class', ''); - $sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data'); - if (is_subclass_of($sDashletClass, 'Dashlet')) - { - $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId); - $offset = $oPage->start_capture(); - $oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */); - $sHtml = addslashes($oPage->end_capture($offset)); - $sHtml = str_replace("\n", '', $sHtml); - $sHtml = str_replace("\r", '', $sHtml); - $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script - // but is executed BEFORE all 'ready_scripts' - $oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed - $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property')); - $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard')); - $sHtml = str_replace("\n", '', $sHtml); - $sHtml = str_replace("\r", '', $sHtml); - $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); // in ajax web page add_script has the same effect as add_ready_script // but is executed BEFORE all 'ready_scripts' - } - break; + case 'new_dashlet': + require_once(APPROOT.'application/forms.class.inc.php'); + require_once(APPROOT.'application/dashlet.class.inc.php'); + $sDashletClass = utils::ReadParam('dashlet_class', ''); + $sDashletId = utils::ReadParam('dashlet_id', '', false, 'raw_data'); + if (is_subclass_of($sDashletClass, 'Dashlet')) { + $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId); + $offset = $oPage->start_capture(); + $oDashlet->DoRender($oPage, true /* bEditMode */, false /* bEnclosingDiv */); + $sHtml = addslashes($oPage->end_capture($offset)); + $sHtml = str_replace("\n", '', $sHtml); + $sHtml = str_replace("\r", '', $sHtml); + $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); // in ajax web page add_script has the same effect as add_ready_script + // but is executed BEFORE all 'ready_scripts' + $oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed + $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property')); + $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true /* bReturnHtml */, '.itop-dashboard')); + $sHtml = str_replace("\n", '', $sHtml); + $sHtml = str_replace("\r", '', $sHtml); + $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); // in ajax web page add_script has the same effect as add_ready_script // but is executed BEFORE all 'ready_scripts' + } + break; - case 'update_dashlet_property': - require_once(APPROOT.'application/forms.class.inc.php'); - require_once(APPROOT.'application/dashlet.class.inc.php'); - $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); - $aParams = utils::ReadParam('params', '', false, 'raw_data'); - $sDashletClass = $aParams['attr_dashlet_class']; - $sDashletType = $aParams['attr_dashlet_type']; - $sDashletId = $aParams['attr_dashlet_id']; - $aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc... - $aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value' - if (is_subclass_of($sDashletClass, 'Dashlet')) { - /** @var \Dashlet $oDashlet */ - $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId); - $oDashlet->SetDashletType($sDashletType); - $oForm = $oDashlet->GetForm(); - $aValues = $oForm->ReadParams(); // hash array: 'xxx' => 'new_value' + case 'update_dashlet_property': + require_once(APPROOT.'application/forms.class.inc.php'); + require_once(APPROOT.'application/dashlet.class.inc.php'); + $aExtraParams = utils::ReadParam('extra_params', array(), false, 'raw_data'); + $aParams = utils::ReadParam('params', '', false, 'raw_data'); + $sDashletClass = $aParams['attr_dashlet_class']; + $sDashletType = $aParams['attr_dashlet_type']; + $sDashletId = $aParams['attr_dashlet_id']; + $aUpdatedProperties = $aParams['updated']; // Code of the changed properties as an array: 'attr_xxx', 'attr_xxy', etc... + $aPreviousValues = $aParams['previous_values']; // hash array: 'attr_xxx' => 'old_value' + if (is_subclass_of($sDashletClass, 'Dashlet')) { + /** @var \Dashlet $oDashlet */ + $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), $sDashletId); + $oDashlet->SetDashletType($sDashletType); + $oForm = $oDashlet->GetForm(); + $aValues = $oForm->ReadParams(); // hash array: 'xxx' => 'new_value' - $aCurrentValues = $aValues; - $aUpdatedDecoded = array(); - foreach ($aUpdatedProperties as $sProp) { - $sDecodedProp = str_replace('attr_', '', $sProp); // Remove the attr_ prefix - // Set the previous value - if ( isset($aPreviousValues[$sProp]) && $aPreviousValues[$sProp] != '' ){ - $aCurrentValues[$sDecodedProp] = $aPreviousValues[$sProp]; - } else { - if(gettype($aCurrentValues[$sDecodedProp]) == "array") { - $aCurrentValues[$sDecodedProp] = []; + $aCurrentValues = $aValues; + $aUpdatedDecoded = array(); + foreach ($aUpdatedProperties as $sProp) { + $sDecodedProp = str_replace('attr_', '', $sProp); // Remove the attr_ prefix + // Set the previous value + if (isset($aPreviousValues[$sProp]) && $aPreviousValues[$sProp] != '') { + $aCurrentValues[$sDecodedProp] = $aPreviousValues[$sProp]; } else { - $aCurrentValues[$sDecodedProp] = ''; + if (gettype($aCurrentValues[$sDecodedProp]) == "array") { + $aCurrentValues[$sDecodedProp] = []; + } else { + $aCurrentValues[$sDecodedProp] = ''; + } + } + $aUpdatedDecoded[] = $sDecodedProp; + } + + $oDashlet->FromParams($aCurrentValues); + $sPrevClass = get_class($oDashlet); + $oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded); + $sNewClass = get_class($oDashlet); + if ($sNewClass != $sPrevClass) { + $oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});"); + } + if ($oDashlet->IsRedrawNeeded()) { + $oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams); + $sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock); + $sHtml = str_replace("\n", '', $sHtml); + $sHtml = str_replace("\r", '', $sHtml); + $sHtml = str_replace("'", "\'", $sHtml); + $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); + } + if ($oDashlet->IsFormRedrawNeeded()) { + $oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed + $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams)); + $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard')); + $sHtml = str_replace("\n", '', $sHtml); + $sHtml = str_replace("\r", '', $sHtml); + $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); + } + } + break; + + case 'dashlet_creation_dlg': + $sOQL = utils::ReadParam('oql', '', false, 'raw_data'); + RuntimeDashboard::GetDashletCreationDlgFromOQL($oPage, $sOQL); + break; + + case 'add_dashlet': + $oForm = RuntimeDashboard::GetDashletCreationForm(); + $aValues = $oForm->ReadParams(); + + $sDashletClass = $aValues['dashlet_class']; + $sMenuId = $aValues['menu_id']; + + if (is_subclass_of($sDashletClass, 'Dashlet')) { + $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), 0); + $oDashlet->FromParams($aValues); + + ApplicationMenu::LoadAdditionalMenus(); + $index = ApplicationMenu::GetMenuIndexById($sMenuId); + $oMenu = ApplicationMenu::GetMenuNode($index); + $oMenu->AddDashlet($oDashlet); + // navigate to the dashboard page + if ($aValues['open_editor']) { + $oPage->add_ready_script("window.location.href='".addslashes(utils::GetAbsoluteUrlAppRoot().'pages/UI.php?c[menu]='.urlencode($sMenuId))."&edit=1';"); // reloads the page, doing a GET even if we arrived via a POST + } + } + break; + + case 'shortcut_list_dlg': + $sOQL = utils::ReadParam('oql', '', false, 'raw_data'); + $sTableSettings = utils::ReadParam('table_settings', '', false, 'raw_data'); + ShortcutOQL::GetCreationDlgFromOQL($oPage, $sOQL, $sTableSettings); + break; + + case 'shortcut_list_create': + $oForm = ShortcutOQL::GetCreationForm(); + $aValues = $oForm->ReadParams(); + + $oAppContext = new ApplicationContext(); + $aContext = $oAppContext->GetAsHash(); + $sContext = serialize($aContext); + + // Create shortcut + /** @var ShortcutOQL $oShortcut */ + $oShortcut = MetaModel::NewObject("ShortcutOQL"); + $oShortcut->Set('user_id', UserRights::GetUserId()); + $oShortcut->Set("context", $sContext); + $oShortcut->Set("name", $aValues['name']); + $oShortcut->Set("oql", $aValues['oql']); + $iAutoReload = (int)$aValues['auto_reload_sec']; + if (($aValues['auto_reload']) && ($iAutoReload > 0)) { + $oShortcut->Set("auto_reload_sec", max(MetaModel::GetConfig()->Get('min_reload_interval'), $iAutoReload)); + $oShortcut->Set("auto_reload", 'custom'); + } + utils::PushArchiveMode(false); + $iId = $oShortcut->DBInsertNoReload(); + utils::PopArchiveMode(); + + $oShortcut->CloneTableSettings($aValues['table_settings']); + + // Add shortcut to current menu + // - Init. app. menu + ApplicationMenu::LoadAdditionalMenus(); + + // - Find newly created shortcut + $aNewShortcutNode = null; + $sMenuGroupId = 'MyShortcuts'; + $sMenuGroupIdx = ApplicationMenu::GetMenuIndexById($sMenuGroupId); + if (0 <= $sMenuGroupIdx) { + $sNewShortcutId = $sMenuGroupId.'_'.$oShortcut->GetKey(); + $aShortcutsNodes = ApplicationMenu::GetSubMenuNodes($sMenuGroupIdx); + foreach ($aShortcutsNodes as $aShortcutNode) { + if ($sNewShortcutId === $aShortcutNode['sId']) { + $aNewShortcutNode = $aShortcutNode; + break; } } - $aUpdatedDecoded[] = $sDecodedProp; } - $oDashlet->FromParams($aCurrentValues); - $sPrevClass = get_class($oDashlet); - $oDashlet = $oDashlet->Update($aValues, $aUpdatedDecoded); - $sNewClass = get_class($oDashlet); - if ($sNewClass != $sPrevClass) { - $oPage->add_ready_script("$('#dashlet_$sDashletId').dashlet('option', {dashlet_class: '$sNewClass'});"); - } - if ($oDashlet->IsRedrawNeeded()) { - $oBlock = $oDashlet->DoRender($oPage, true, false, $aExtraParams); - $sHtml = ConsoleBlockRenderer::RenderBlockTemplateInPage($oPage, $oBlock); - $sHtml = str_replace("\n", '', $sHtml); - $sHtml = str_replace("\r", '', $sHtml); - $sHtml = str_replace("'", "\'", $sHtml); - $oPage->add_script("$('#dashlet_$sDashletId').html('$sHtml');"); - } - if ($oDashlet->IsFormRedrawNeeded()) { - $oForm = $oDashlet->GetForm(); // Rebuild the form since the values/content changed - $oForm->SetSubmitParams(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php', array('operation' => 'update_dashlet_property', 'extra_params' => $aExtraParams)); - $sHtml = addslashes($oForm->RenderAsPropertySheet($oPage, true, '.itop-dashboard')); - $sHtml = str_replace("\n", '', $sHtml); - $sHtml = str_replace("\r", '', $sHtml); - $oPage->add_script("$('#dashlet_properties_$sDashletId').html('$sHtml')"); - } - } - break; + // - If shortcut found, insert it in the navigation menu + if (!empty($aNewShortcutNode)) { + $sHtml = TwigHelper::RenderTemplate( + TwigHelper::GetTwigEnvironment(TwigHelper::ENUM_TEMPLATES_BASE_PATH_BACKOFFICE), + ['aMenuNode' => $aNewShortcutNode], + 'base/layouts/navigation-menu/menu-node' + ); - case 'dashlet_creation_dlg': - $sOQL = utils::ReadParam('oql', '', false, 'raw_data'); - RuntimeDashboard::GetDashletCreationDlgFromOQL($oPage, $sOQL); - break; - - case 'add_dashlet': - $oForm = RuntimeDashboard::GetDashletCreationForm(); - $aValues = $oForm->ReadParams(); - - $sDashletClass = $aValues['dashlet_class']; - $sMenuId = $aValues['menu_id']; - - if (is_subclass_of($sDashletClass, 'Dashlet')) - { - $oDashlet = new $sDashletClass(new ModelReflectionRuntime(), 0); - $oDashlet->FromParams($aValues); - - ApplicationMenu::LoadAdditionalMenus(); - $index = ApplicationMenu::GetMenuIndexById($sMenuId); - $oMenu = ApplicationMenu::GetMenuNode($index); - $oMenu->AddDashlet($oDashlet); - // navigate to the dashboard page - if ($aValues['open_editor']) - { - $oPage->add_ready_script("window.location.href='".addslashes(utils::GetAbsoluteUrlAppRoot().'pages/UI.php?c[menu]='.urlencode($sMenuId))."&edit=1';"); // reloads the page, doing a GET even if we arrived via a POST - } - } - break; - - case 'shortcut_list_dlg': - $sOQL = utils::ReadParam('oql', '', false, 'raw_data'); - $sTableSettings = utils::ReadParam('table_settings', '', false, 'raw_data'); - ShortcutOQL::GetCreationDlgFromOQL($oPage, $sOQL, $sTableSettings); - break; - - case 'shortcut_list_create': - $oForm = ShortcutOQL::GetCreationForm(); - $aValues = $oForm->ReadParams(); - - $oAppContext = new ApplicationContext(); - $aContext = $oAppContext->GetAsHash(); - $sContext = serialize($aContext); - - // Create shortcut - /** @var ShortcutOQL $oShortcut */ - $oShortcut = MetaModel::NewObject("ShortcutOQL"); - $oShortcut->Set('user_id', UserRights::GetUserId()); - $oShortcut->Set("context", $sContext); - $oShortcut->Set("name", $aValues['name']); - $oShortcut->Set("oql", $aValues['oql']); - $iAutoReload = (int)$aValues['auto_reload_sec']; - if (($aValues['auto_reload']) && ($iAutoReload > 0)) { - $oShortcut->Set("auto_reload_sec", max(MetaModel::GetConfig()->Get('min_reload_interval'), $iAutoReload)); - $oShortcut->Set("auto_reload", 'custom'); - } - utils::PushArchiveMode(false); - $iId = $oShortcut->DBInsertNoReload(); - utils::PopArchiveMode(); - - $oShortcut->CloneTableSettings($aValues['table_settings']); - - // Add shortcut to current menu - // - Init. app. menu - ApplicationMenu::LoadAdditionalMenus(); - - // - Find newly created shortcut - $aNewShortcutNode = null; - $sMenuGroupId = 'MyShortcuts'; - $sMenuGroupIdx = ApplicationMenu::GetMenuIndexById($sMenuGroupId); - if (0 <= $sMenuGroupIdx) { - $sNewShortcutId = $sMenuGroupId.'_'.$oShortcut->GetKey(); - $aShortcutsNodes = ApplicationMenu::GetSubMenuNodes($sMenuGroupIdx); - foreach ($aShortcutsNodes as $aShortcutNode) { - if ($sNewShortcutId === $aShortcutNode['sId']) { - $aNewShortcutNode = $aShortcutNode; - break; - } - } - } - - // - If shortcut found, insert it in the navigation menu - if (!empty($aNewShortcutNode)) { - $sHtml = TwigHelper::RenderTemplate( - TwigHelper::GetTwigEnvironment(TwigHelper::ENUM_TEMPLATES_BASE_PATH_BACKOFFICE), - ['aMenuNode' => $aNewShortcutNode], - 'base/layouts/navigation-menu/menu-node' - ); - - // Important: Mind the back ticks to avoid line breaks to break the JS - $oPage->add_script(<<add_script(<<StartRenameDialog($oPage); - break; - - case 'shortcut_rename_go': - $iShortcut = utils::ReadParam('id', 0); - $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); - - $sName = utils::ReadParam('attr_name', '', false, 'raw_data'); - if (strlen($sName) > 0) - { - $oShortcut->Set('name', $sName); - utils::PushArchiveMode(false); - $oShortcut->DBUpdate(); - utils::PopArchiveMode(); - $oPage->add_ready_script('window.location.reload();'); - } - - break; - - case 'shortcut_delete_go': - $oSearch = new DBObjectSearch('Shortcut'); - $oSearch->AddCondition('user_id', UserRights::GetUserId(), '='); - $aShortcuts = utils::ReadMultipleSelection($oSearch); - foreach($aShortcuts as $iShortcut) - { - $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); - utils::PushArchiveMode(false); - $oShortcut->DBDelete(); - utils::PopArchiveMode(); - $oPage->add_ready_script('window.location.reload();'); - } - break; - - case 'about_box': - AjaxRenderController::DisplayAboutBox($oPage); - break; - - case 'full_text_search': - $aFullTextNeedles = utils::ReadParam('needles', array(), false, 'raw_data'); - $sFullText = trim(implode(' ', $aFullTextNeedles)); - $sClassName = utils::ReadParam('classname', ''); - $iCount = utils::ReadParam('count', 0); - $iCurrentPos = utils::ReadParam('position', 0); - $iTune = utils::ReadParam('tune', 0); - if (empty($sFullText)) { - $oPage->p(Dict::S('UI:Search:NoSearch')); + ); + } break; - } - // Search in full text mode in all the classes - $aMatches = array(); + case 'shortcut_rename_dlg': + $oSearch = new DBObjectSearch('Shortcut'); + $aShortcuts = utils::ReadMultipleSelection($oSearch); + $iShortcut = $aShortcuts[0]; + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); + $oShortcut->StartRenameDialog($oPage); + break; - // Build the ordered list of classes to search into - // - if (empty($sClassName)) - { - $aSearchClasses = MetaModel::GetClasses('searchable'); - } - else - { - // Search is limited to a given class and its subclasses - $aSearchClasses = MetaModel::EnumChildClasses($sClassName, ENUM_CHILD_CLASSES_ALL); - } - // Skip abstract classes, since we search in all the child classes anyway - foreach($aSearchClasses as $idx => $sClass) - { - if (MetaModel::IsAbstract($sClass)) - { - unset($aSearchClasses[$idx]); - } - } + case 'shortcut_rename_go': + $iShortcut = utils::ReadParam('id', 0); + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); - $sMaxChunkDuration = MetaModel::GetConfig()->Get('full_text_chunk_duration'); - $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); + $sName = utils::ReadParam('attr_name', '', false, 'raw_data'); + if (strlen($sName) > 0) { + $oShortcut->Set('name', $sName); + utils::PushArchiveMode(false); + $oShortcut->DBUpdate(); + utils::PopArchiveMode(); + $oPage->add_ready_script('window.location.reload();'); + } - foreach(array_reverse($aAccelerators) as $sClass => $aRestriction) - { - $bSkip = false; - $iPos = array_search($sClass, $aSearchClasses); - if ($iPos !== false) - { - unset($aSearchClasses[$iPos]); - } - else - { - $bSkip = true; - } - $bSkip |= array_key_exists('skip', $aRestriction) ? $aRestriction['skip'] : false; - if (!in_array($sClass, $aSearchClasses)) - { - if ($sClass == $sClassName) - { - // Class explicitely requested, do NOT skip it - // beware: there may not be a 'query' defined for a skipped class ! - $bSkip = false; - } - } - if (!$bSkip) - { - // NOT skipped, add the class to the list of classes to search into - if (array_key_exists('query', $aRestriction)) - { - array_unshift($aSearchClasses, $aRestriction['query']); - } - else - { - // No accelerator query - array_unshift($aSearchClasses, $sClassName); - } - } - } + break; - $aSearchClasses = array_values($aSearchClasses); // renumbers the array starting from zero, removing the missing indexes - $fStarted = microtime(true); - $iFoundInThisRound = 0; - for($iPos = $iCurrentPos; $iPos < count($aSearchClasses); $iPos++) - { - if ($iFoundInThisRound && (microtime(true) - $fStarted >= $sMaxChunkDuration)) - { + case 'shortcut_delete_go': + $oSearch = new DBObjectSearch('Shortcut'); + $oSearch->AddCondition('user_id', UserRights::GetUserId(), '='); + $aShortcuts = utils::ReadMultipleSelection($oSearch); + foreach ($aShortcuts as $iShortcut) { + $oShortcut = MetaModel::GetObject('Shortcut', $iShortcut); + utils::PushArchiveMode(false); + $oShortcut->DBDelete(); + utils::PopArchiveMode(); + $oPage->add_ready_script('window.location.reload();'); + } + break; + + case 'about_box': + AjaxRenderController::DisplayAboutBox($oPage); + break; + + case 'full_text_search': + $aFullTextNeedles = utils::ReadParam('needles', array(), false, 'raw_data'); + $sFullText = trim(implode(' ', $aFullTextNeedles)); + $sClassName = utils::ReadParam('classname', ''); + $iCount = utils::ReadParam('count', 0); + $iCurrentPos = utils::ReadParam('position', 0); + $iTune = utils::ReadParam('tune', 0); + if (empty($sFullText)) { + $oPage->p(Dict::S('UI:Search:NoSearch')); break; } - $sClassSpec = $aSearchClasses[$iPos]; - if (substr($sClassSpec, 0, 7) == 'SELECT ') - { - $oFilter = DBObjectSearch::FromOQL($sClassSpec); - $sClassName = $oFilter->GetClass(); - $sNeedleFormat = isset($aAccelerators[$sClassName]['needle']) ? $aAccelerators[$sClassName]['needle'] : '%$needle$%'; - $sNeedle = str_replace('$needle$', $sFullText, $sNeedleFormat); - $aParams = array('needle' => $sNeedle); - } - else - { - $sClassName = $sClassSpec; - $oFilter = new DBObjectSearch($sClassName); - $aParams = array(); + // Search in full text mode in all the classes + $aMatches = array(); - foreach($aFullTextNeedles as $sSearchText) - { - $oFilter->AddCondition_FullText($sSearchText); + // Build the ordered list of classes to search into + // + if (empty($sClassName)) { + $aSearchClasses = MetaModel::GetClasses('searchable'); + } else { + // Search is limited to a given class and its subclasses + $aSearchClasses = MetaModel::EnumChildClasses($sClassName, ENUM_CHILD_CLASSES_ALL); + } + // Skip abstract classes, since we search in all the child classes anyway + foreach ($aSearchClasses as $idx => $sClass) { + if (MetaModel::IsAbstract($sClass)) { + unset($aSearchClasses[$idx]); } } - $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); - // Skip abstract classes - if (MetaModel::IsAbstract($sClassName)) continue; - if ($iTune > 0) - { - $fStartedClass = microtime(true); - } - $oSet = new DBObjectSet($oFilter, array(), $aParams); - if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('attributes', $aAccelerators[$sClassName])) - { - $oSet->OptimizeColumnLoad(array($oFilter->GetClassAlias() => $aAccelerators[$sClassName]['attributes'])); + $sMaxChunkDuration = MetaModel::GetConfig()->Get('full_text_chunk_duration'); + $aAccelerators = MetaModel::GetConfig()->Get('full_text_accelerators'); + + foreach (array_reverse($aAccelerators) as $sClass => $aRestriction) { + $bSkip = false; + $iPos = array_search($sClass, $aSearchClasses); + if ($iPos !== false) { + unset($aSearchClasses[$iPos]); + } else { + $bSkip = true; + } + $bSkip |= array_key_exists('skip', $aRestriction) ? $aRestriction['skip'] : false; + if (!in_array($sClass, $aSearchClasses)) { + if ($sClass == $sClassName) { + // Class explicitely requested, do NOT skip it + // beware: there may not be a 'query' defined for a skipped class ! + $bSkip = false; + } + } + if (!$bSkip) { + // NOT skipped, add the class to the list of classes to search into + if (array_key_exists('query', $aRestriction)) { + array_unshift($aSearchClasses, $aRestriction['query']); + } else { + // No accelerator query + array_unshift($aSearchClasses, $sClassName); + } + } } - $sFullTextJS = addslashes($sFullText); - $bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]); - if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) - { - $bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge']; - } - $sEnlargeTheSearch = - <<= $sMaxChunkDuration)) { + break; + } + + $sClassSpec = $aSearchClasses[$iPos]; + if (substr($sClassSpec, 0, 7) == 'SELECT ') { + $oFilter = DBObjectSearch::FromOQL($sClassSpec); + $sClassName = $oFilter->GetClass(); + $sNeedleFormat = isset($aAccelerators[$sClassName]['needle']) ? $aAccelerators[$sClassName]['needle'] : '%$needle$%'; + $sNeedle = str_replace('$needle$', $sFullText, $sNeedleFormat); + $aParams = array('needle' => $sNeedle); + } else { + $sClassName = $sClassSpec; + $oFilter = new DBObjectSearch($sClassName); + $aParams = array(); + + foreach ($aFullTextNeedles as $sSearchText) { + $oFilter->AddCondition_FullText($sSearchText); + } + } + $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); + // Skip abstract classes + if (MetaModel::IsAbstract($sClassName)) { + continue; + } + + if ($iTune > 0) { + $fStartedClass = microtime(true); + } + $oSet = new DBObjectSet($oFilter, array(), $aParams); + if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('attributes', $aAccelerators[$sClassName])) { + $oSet->OptimizeColumnLoad(array($oFilter->GetClassAlias() => $aAccelerators[$sClassName]['attributes'])); + } + + $sFullTextJS = addslashes($sFullText); + $bEnableEnlarge = array_key_exists($sClassName, $aAccelerators) && array_key_exists('query', $aAccelerators[$sClassName]); + if (array_key_exists($sClassName, $aAccelerators) && array_key_exists('enable_enlarge', $aAccelerators[$sClassName])) { + $bEnableEnlarge &= $aAccelerators[$sClassName]['enable_enlarge']; + } + $sEnlargeTheSearch = + <<'); @@ -1436,91 +1362,75 @@ JS EOF; - $sEnlargeButton = ''; - if ($bEnableEnlarge) - { - $sEnlargeButton = " "; - } - if ($oSet->Count() > 0) - { - $aLeafs = array(); - while ($oObj = $oSet->Fetch()) - { - if (get_class($oObj) == $sClassName) - { - $aLeafs[] = $oObj->GetKey(); - $iFoundInThisRound++; + $sEnlargeButton = ''; + if ($bEnableEnlarge) { + $sEnlargeButton = " "; + } + if ($oSet->Count() > 0) { + $aLeafs = array(); + while ($oObj = $oSet->Fetch()) { + if (get_class($oObj) == $sClassName) { + $aLeafs[] = $oObj->GetKey(); + $iFoundInThisRound++; + } + } + $oLeafsFilter = new DBObjectSearch($sClassName); + if (count($aLeafs) > 0) { + $iCount += count($aLeafs); + $oPage->add("
\n"); + $oPage->add("
\n"); + if (array_key_exists($sClassName, $aAccelerators)) { + $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."

\n"); + } else { + $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."

\n"); + } + $oPage->add("
\n"); + $oLeafsFilter->AddCondition('id', $aLeafs, 'IN'); + $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); + $sBlockId = 'global_search_'.$sClassName; + $oPage->add('
'); + $oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId)); + $oPage->add("
\n"); + $oPage->add("
\n"); + $oPage->p(' '); // Some space ? + } + } else { + if (array_key_exists($sClassName, $aAccelerators)) { + $oPage->add("
\n"); + $oPage->add("
\n"); + $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', 0, Metamodel::GetName($sClassName)).$sEnlargeButton."

\n"); + $oPage->add("
\n"); + $oPage->add("
\n"); + $oPage->p(' '); // Some space ? } } - $oLeafsFilter = new DBObjectSearch($sClassName); - if (count($aLeafs) > 0) - { - $iCount += count($aLeafs); - $oPage->add("
\n"); - $oPage->add("
\n"); - if (array_key_exists($sClassName, $aAccelerators)) - { - $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName)).$sEnlargeButton."

\n"); - } - else - { - $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aLeafs), Metamodel::GetName($sClassName))."

\n"); - } - $oPage->add("
\n"); - $oLeafsFilter->AddCondition('id', $aLeafs, 'IN'); - $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); - $sBlockId = 'global_search_'.$sClassName; - $oPage->add('
'); - $oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId)); - $oPage->add("
\n"); - $oPage->add("
\n"); - $oPage->p(' '); // Some space ? + if ($iTune > 0) { + $fDurationClass = microtime(true) - $fStartedClass; + $oPage->add_script("oTimeStatistics.$sClassName = $fDurationClass;"); } } - else - { - if (array_key_exists($sClassName, $aAccelerators)) - { - $oPage->add("
\n"); - $oPage->add("
\n"); - $oPage->add('

'.MetaModel::GetClassIcon($sClassName).Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', 0, Metamodel::GetName($sClassName)).$sEnlargeButton."

\n"); - $oPage->add("
\n"); - $oPage->add("
\n"); - $oPage->p(' '); // Some space ? - } - } - if ($iTune > 0) - { - $fDurationClass = microtime(true) - $fStartedClass; - $oPage->add_script("oTimeStatistics.$sClassName = $fDurationClass;"); - } - } - if ($iPos < count($aSearchClasses)) - { - $sJSNeedle = json_encode($aFullTextNeedles); - $oPage->add_ready_script( - <<add_ready_script( + <<add_ready_script( - <<add_ready_script( + << 0) - { - $oPage->add_ready_script( - << 0) { + $oPage->add_ready_script( + <<add_ready_script("$('#full_text_results').append('
$sFullTextSummary
');"); - } - } - break; - - case 'full_text_search_enlarge': - $sFullText = trim(utils::ReadParam('text', '', false, 'raw_data')); - $sClass = trim(utils::ReadParam('class', '')); - $iTune = utils::ReadParam('tune', 0); - - if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) - { - // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression - $aFullTextNeedles = array($aMatches[1]); - } - else - { - // Split the text on the blanks and treat this as a search for AND AND - $aFullTextNeedles = explode(' ', $sFullText); - } - - $oFilter = new DBObjectSearch($sClass); - foreach($aFullTextNeedles as $sSearchText) - { - $oFilter->AddCondition_FullText($sSearchText); - } - $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); - $oSet = new DBObjectSet($oFilter); - $oPage->add("
\n"); - $oPage->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."

\n"); - $oPage->add("
\n"); - if ($oSet->Count() > 0) - { - $aLeafs = array(); - while ($oObj = $oSet->Fetch()) - { - if (get_class($oObj) == $sClass) - { - $aLeafs[] = $oObj->GetKey(); + if ($iCount == 0) { + $sFullTextSummary = addslashes(Dict::S('UI:Search:NoObjectFound')); + $oPage->add_ready_script("$('#full_text_results').append('
$sFullTextSummary
');"); } } - $oLeafsFilter = new DBObjectSearch($sClass); - if (count($aLeafs) > 0) - { - $oLeafsFilter->AddCondition('id', $aLeafs, 'IN'); - $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); - $sBlockId = 'global_search_'.$sClass; - $oPage->add('
'); - $oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId)); - $oPage->add('
'); - $oPage->P(' '); // Some space ? - // Hide "no object found" - $oPage->add_ready_script('$("#no_object_found").hide();'); + break; + + case 'full_text_search_enlarge': + $sFullText = trim(utils::ReadParam('text', '', false, 'raw_data')); + $sClass = trim(utils::ReadParam('class', '')); + $iTune = utils::ReadParam('tune', 0); + + if (preg_match('/^"(.*)"$/', $sFullText, $aMatches)) { + // The text is surrounded by double-quotes, remove the quotes and treat it as one single expression + $aFullTextNeedles = array($aMatches[1]); + } else { + // Split the text on the blanks and treat this as a search for AND AND + $aFullTextNeedles = explode(' ', $sFullText); } - } - $oPage->add_ready_script( - <<AddCondition_FullText($sSearchText); + } + $oFilter->SetShowObsoleteData(utils::ShowObsoleteData()); + $oSet = new DBObjectSet($oFilter); + $oPage->add("
\n"); + $oPage->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sClass))."

\n"); + $oPage->add("
\n"); + if ($oSet->Count() > 0) { + $aLeafs = array(); + while ($oObj = $oSet->Fetch()) { + if (get_class($oObj) == $sClass) { + $aLeafs[] = $oObj->GetKey(); + } + } + $oLeafsFilter = new DBObjectSearch($sClass); + if (count($aLeafs) > 0) { + $oLeafsFilter->AddCondition('id', $aLeafs, 'IN'); + $oBlock = new DisplayBlock($oLeafsFilter, 'list', false); + $sBlockId = 'global_search_'.$sClass; + $oPage->add('
'); + $oBlock->RenderContent($oPage, array('table_id' => $sBlockId, 'currentId' => $sBlockId)); + $oPage->add('
'); + $oPage->P(' '); // Some space ? + // Hide "no object found" + $oPage->add_ready_script('$("#no_object_found").hide();'); + } + } + $oPage->add_ready_script( + <<SetContentType('text/html'); - $oPage->add( - <<SetContentType('text/html'); + $oPage->add( + << .ui-progressbar { position: relative; @@ -1654,568 +1555,599 @@ EOF } EOF - ); - $oPage->add('
'); - $oPage->add('
'); - $oPage->add('

 

'); - $oPage->add('

'.Dict::S('UI:CSVImport:AdvancedMode+').'

'); - $oPage->add('

 

'); - $oPage->add('
'); - $oPage->add('

'.Dict::S('ExcelExport:PreparingExport').'

'); - $oPage->add('
'.Dict::S('ExcelExport:Statistics').'
'); - $oPage->add('
'); - $aLabels = array( - 'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'), - 'cancel_button' => Dict::S('UI:Button:Cancel'), - 'export_button' => Dict::S('ExcelExporter:ExportButton'), - 'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??) - ); - $sJSLabels = json_encode($aLabels); - $sFilter = addslashes($sFilter); - $sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'); - $oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});"); - break; + ); + $oPage->add('
'); + $oPage->add('
'); + $oPage->add('

 

'); + $oPage->add('

'.Dict::S('UI:CSVImport:AdvancedMode+').'

'); + $oPage->add('

 

'); + $oPage->add('
'); + $oPage->add('

'.Dict::S('ExcelExport:PreparingExport').'

'); + $oPage->add('
'.Dict::S('ExcelExport:Statistics').'
'); + $oPage->add('
'); + $aLabels = array( + 'dialog_title' => Dict::S('ExcelExporter:ExportDialogTitle'), + 'cancel_button' => Dict::S('UI:Button:Cancel'), + 'export_button' => Dict::S('ExcelExporter:ExportButton'), + 'download_button' => Dict::Format('ExcelExporter:DownloadButton', 'export.xlsx'), //TODO: better name for the file (based on the class of the filter??) + ); + $sJSLabels = json_encode($aLabels); + $sFilter = addslashes($sFilter); + $sJSPageUrl = addslashes(utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php'); + $oPage->add_ready_script("$('#XlsxExportDlg').xlsxexporter({filter: '$sFilter', labels: $sJSLabels, ajax_page_url: '$sJSPageUrl'});"); + break; - case 'xlsx_start': - DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); - $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); - $bAdvanced = (utils::ReadParam('advanced', 'false') == 'true'); - $oSearch = DBObjectSearch::unserialize($sFilter); - $oExcelExporter = new ExcelExporter(); - $oExcelExporter->SetObjectList($oSearch); - //$oExcelExporter->SetChunkSize(10); //Only for testing - $oExcelExporter->SetAdvancedMode($bAdvanced); - $sToken = $oExcelExporter->SaveState(); - $oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken))); - break; + case 'xlsx_start': + DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); + $sFilter = utils::ReadParam('filter', '', false, 'raw_data'); + $bAdvanced = (utils::ReadParam('advanced', 'false') == 'true'); + $oSearch = DBObjectSearch::unserialize($sFilter); + $oExcelExporter = new ExcelExporter(); + $oExcelExporter->SetObjectList($oSearch); + //$oExcelExporter->SetChunkSize(10); //Only for testing + $oExcelExporter->SetAdvancedMode($bAdvanced); + $sToken = $oExcelExporter->SaveState(); + $oPage->add(json_encode(array('status' => 'ok', 'token' => $sToken))); + break; - case 'xlsx_run': - DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); - $sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit'); - if (utils::SetMinMemoryLimit($sMemoryLimit) === false) { - IssueLog::Warning("XSLX export : cannot set memory_limit to {$sMemoryLimit}"); - } - ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes - - $sToken = utils::ReadParam('token', '', false, 'raw_data'); - $oExcelExporter = new ExcelExporter($sToken); - $aStatus = $oExcelExporter->Run(); - $aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']); - if ($aStatus['code'] == 'done') { - $aResults['statistics'] = $oExcelExporter->GetStatistics('html'); - } - $oPage->add(json_encode($aResults)); - break; - - case 'xlsx_download': - DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); - $oPage = new DownloadPage(''); - $sToken = utils::ReadParam('token', '', false, 'raw_data'); - $oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); - $oPage->SetContentDisposition('attachment', 'export.xlsx'); - $sFileContent = ExcelExporter::GetExcelFileFromToken($sToken); - $oPage->add($sFileContent); - ExcelExporter::CleanupFromToken($sToken); - break; - - case 'xlsx_abort': - DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); - // Stop & cleanup an export... - $sToken = utils::ReadParam('token', '', false, 'raw_data'); - ExcelExporter::CleanupFromToken($sToken); - break; - - case 'relation_pdf': - case 'relation_attachment': - require_once(APPROOT.'core/simplegraph.class.inc.php'); - require_once(APPROOT.'core/relationgraph.class.inc.php'); - require_once(APPROOT.'core/displayablegraph.class.inc.php'); - $sRelation = utils::ReadParam('relation', 'impacts'); - $sDirection = utils::ReadParam('direction', 'down'); - - $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer'); - $sPageFormat = utils::ReadParam('p', 'A4'); - $sPageOrientation = utils::ReadParam('o', 'L'); - $sTitle = utils::ReadParam('title', '', false, 'raw_data'); - $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); - $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); - $bIncludeList = (bool)utils::ReadParam('include_list', false); - $sComments = utils::ReadParam('comments', '', false, 'raw_data'); - $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); - $sContextKey = utils::ReadParam('context_key', '', false, 'raw_data'); - $aPositions = null; - if ($sPositions != null) { - $aPositions = json_decode($sPositions, true); - } - - // Get the list of source objects - $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); - $aSourceObjects = array(); - foreach ($aSources as $sClass => $aIDs) { - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition('id', $aIDs, 'IN'); - $oSet = new DBObjectSet($oSearch); - while ($oObj = $oSet->Fetch()) { - $aSourceObjects[] = $oObj; + case 'xlsx_run': + DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); + $sMemoryLimit = MetaModel::GetConfig()->Get('xlsx_exporter_memory_limit'); + if (utils::SetMinMemoryLimit($sMemoryLimit) === false) { + IssueLog::Warning("XSLX export : cannot set memory_limit to {$sMemoryLimit}"); } - } - $sSourceClass = '*'; - if (count($aSourceObjects) == 1) { - $sSourceClass = get_class($aSourceObjects[0]); - } + ini_set('max_execution_time', max(300, ini_get('max_execution_time'))); // At least 5 minutes - // Get the list of excluded objects - $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); - $aExcludedObjects = array(); - foreach ($aExcluded as $sClass => $aIDs) { - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition('id', $aIDs, 'IN'); - $oSet = new DBObjectSet($oSearch); - while ($oObj = $oSet->Fetch()) { - $aExcludedObjects[] = $oObj; + $sToken = utils::ReadParam('token', '', false, 'raw_data'); + $oExcelExporter = new ExcelExporter($sToken); + $aStatus = $oExcelExporter->Run(); + $aResults = array('status' => $aStatus['code'], 'percentage' => $aStatus['percentage'], 'message' => $aStatus['message']); + if ($aStatus['code'] == 'done') { + $aResults['statistics'] = $oExcelExporter->GetStatistics('html'); } - } + $oPage->add(json_encode($aResults)); + break; - $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); - if ($sDirection == 'up') { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); - } else { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); - } + case 'xlsx_download': + DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); + $oPage = new DownloadPage(''); + $sToken = utils::ReadParam('token', '', false, 'raw_data'); + $oPage->SetContentType('application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); + $oPage->SetContentDisposition('attachment', 'export.xlsx'); + $sFileContent = ExcelExporter::GetExcelFileFromToken($sToken); + $oPage->add($sFileContent); + ExcelExporter::CleanupFromToken($sToken); + break; - // Remove excluded classes from the graph - if (count($aExcludedClasses) > 0) { - $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); - foreach ($oIterator as $oNode) { - $oObj = $oNode->GetProperty('object'); - if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { - $oRelGraph->FilterNode($oNode); + case 'xlsx_abort': + DeprecatedCallsLog::NotifyDeprecatedPhpEndpoint('Use "export_*" operations instead of "'.$operation.'"'); + // Stop & cleanup an export... + $sToken = utils::ReadParam('token', '', false, 'raw_data'); + ExcelExporter::CleanupFromToken($sToken); + break; + + case 'relation_pdf': + case 'relation_attachment': + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + $sRelation = utils::ReadParam('relation', 'impacts'); + $sDirection = utils::ReadParam('direction', 'down'); + + $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer'); + $sPageFormat = utils::ReadParam('p', 'A4'); + $sPageOrientation = utils::ReadParam('o', 'L'); + $sTitle = utils::ReadParam('title', '', false, 'raw_data'); + $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); + $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); + $bIncludeList = (bool)utils::ReadParam('include_list', false); + $sComments = utils::ReadParam('comments', '', false, 'raw_data'); + $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); + $sContextKey = utils::ReadParam('context_key', '', false, 'raw_data'); + $aPositions = null; + if ($sPositions != null) { + $aPositions = json_decode($sPositions, true); + } + + // Get the list of source objects + $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); + $aSourceObjects = array(); + foreach ($aSources as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aSourceObjects[] = $oObj; } } - } - - $oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation); - $oPage->SetContentDisposition('attachment', $sTitle.'.pdf'); - - $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'), true); - $oGraph->InitFromGraphviz(); - if ($aPositions != null) { - $oGraph->UpdatePositions($aPositions); - } - - $aGroups = array(); - $oIterator = new RelationTypeIterator($oGraph, 'Node'); - foreach ($oIterator as $oNode) { - if ($oNode instanceof DisplayableGroupNode) { - $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects(); + $sSourceClass = '*'; + if (count($aSourceObjects) == 1) { + $sSourceClass = get_class($aSourceObjects[0]); } - } - // First page is the graph - $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey); - if ($bIncludeList) { - // Then the lists of objects (one table per finalclass) - $aResults = array(); - $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); - foreach ($oIterator as $oNode) { - $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object - if ($oObj) { - $sObjClass = get_class($oObj); - if (!array_key_exists($sObjClass, $aResults)) { - $aResults[$sObjClass] = array(); + // Get the list of excluded objects + $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedObjects = array(); + foreach ($aExcluded as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aExcludedObjects[] = $oObj; + } + } + + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); + if ($sDirection == 'up') { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); + } else { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); + } + + // Remove excluded classes from the graph + if (count($aExcludedClasses) > 0) { + $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); + foreach ($oIterator as $oNode) { + $oObj = $oNode->GetProperty('object'); + if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { + $oRelGraph->FilterNode($oNode); } - $aResults[$sObjClass][] = $oObj; } } - $oPage->get_tcpdf()->AddPage(); - $oPage->get_tcpdf()->SetFontSize(10); // Reset the font size to its default - $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationshipList'))); - $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); - foreach ($aResults as $sListClass => $aObjects) { - set_time_limit($iLoopTimeLimit * count($aObjects)); - $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); - $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); - /* cf N°3928 - Polishing: Impact analysis - remove icons in pdf list - $sIconUrl = MetaModel::GetClassIcon($sListClass, false); - $sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); - $oTitle = new Html(" ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));*/ - $oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass))); - $oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2)); - $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass))); + $oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation); + $oPage->SetContentDisposition('attachment', $sTitle.'.pdf'); + + $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'), true); + $oGraph->InitFromGraphviz(); + if ($aPositions != null) { + $oGraph->UpdatePositions($aPositions); } - // Then the content of the groups (one table per group) - if (count($aGroups) > 0) { + $aGroups = array(); + $oIterator = new RelationTypeIterator($oGraph, 'Node'); + foreach ($oIterator as $oNode) { + if ($oNode instanceof DisplayableGroupNode) { + $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects(); + } + } + // First page is the graph + $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey); + + if ($bIncludeList) { + // Then the lists of objects (one table per finalclass) + $aResults = array(); + $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); + foreach ($oIterator as $oNode) { + $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object + if ($oObj) { + $sObjClass = get_class($oObj); + if (!array_key_exists($sObjClass, $aResults)) { + $aResults[$sObjClass] = array(); + } + $aResults[$sObjClass][] = $oObj; + } + } + $oPage->get_tcpdf()->AddPage(); - $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationGroups'))); - foreach ($aGroups as $idx => $aObjects) { + $oPage->get_tcpdf()->SetFontSize(10); // Reset the font size to its default + $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationshipList'))); + $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); + foreach ($aResults as $sListClass => $aObjects) { set_time_limit($iLoopTimeLimit * count($aObjects)); - $sListClass = get_class(current($aObjects)); $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); - $sIconUrl = MetaModel::GetClassIcon($sListClass, false); + $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); + /* cf N°3928 - Polishing: Impact analysis - remove icons in pdf list + $sIconUrl = MetaModel::GetClassIcon($sListClass, false); $sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); - $oTitle = new Html(" ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass)); + $oTitle = new Html(" ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass)));*/ + $oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass))); $oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2)); - $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet)); + $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass))); + } + // Then the content of the groups (one table per group) + if (count($aGroups) > 0) { + $oPage->get_tcpdf()->AddPage(); + $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationGroups'))); + foreach ($aGroups as $idx => $aObjects) { + set_time_limit($iLoopTimeLimit * count($aObjects)); + $sListClass = get_class(current($aObjects)); + $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); + $sIconUrl = MetaModel::GetClassIcon($sListClass, false); + $sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); + $oTitle = new Html(" ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass)); + $oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2)); + $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet)); + + } } } - } - if ($operation == 'relation_attachment') { - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); + if ($operation == 'relation_attachment') { + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - // Save the generated PDF as an attachment - $sPDF = $oPage->get_pdf(); + // Save the generated PDF as an attachment + $sPDF = $oPage->get_pdf(); + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + $oAttachment = MetaModel::NewObject('Attachment'); + $oAttachment->Set('item_class', $sObjClass); + $oAttachment->Set('item_id', $iObjKey); + $oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf'); + $oAttachment->Set('contents', $oDoc); + $iAttachmentId = $oAttachment->DBInsert(); + $aRet = array( + 'status' => 'ok', + 'att_id' => $iAttachmentId, + ); + $oPage->SetData($aRet); + } + break; + + case 'relation_json': + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + $sRelation = utils::ReadParam('relation', 'impacts'); + $sDirection = utils::ReadParam('direction', 'down'); + $iGroupingThreshold = utils::ReadParam('g', 5); + $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); + $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); + $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); + $sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data'); + $aPositions = null; + if ($sPositions != null) { + $aPositions = json_decode($sPositions, true); + } + + // Get the list of source objects + $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); + $aSourceObjects = array(); + foreach ($aSources as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aSourceObjects[] = $oObj; + } + } + + // Get the list of excluded objects + $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedObjects = array(); + foreach ($aExcluded as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aExcludedObjects[] = $oObj; + } + } + + // Compute the graph + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); + if ($sDirection == 'up') { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); + } else { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); + } + + // Remove excluded classes from the graph + if (count($aExcludedClasses) > 0) { + $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); + foreach ($oIterator as $oNode) { + $oObj = $oNode->GetProperty('object'); + if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { + $oRelGraph->FilterNode($oNode); + } + } + } + + $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + $oGraph->InitFromGraphviz(); + if ($aPositions != null) { + $oGraph->UpdatePositions($aPositions); + } + $oPage->add($oGraph->GetAsJSON($sContextKey)); + $oPage->SetContentType('application/json'); + break; + + case 'relation_groups': + $aGroups = utils::ReadParam('groups'); + $iBlock = 1; // Zero is not a valid blockid + foreach ($aGroups as $idx => $aDefinition) { + $sListClass = $aDefinition['class']; + $oSearch = new DBObjectSearch($sListClass); + $oSearch->AddCondition('id', $aDefinition['keys'], 'IN'); + $oSearch->SetShowObsoleteData(utils::ShowObsoleteData()); + $oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), 1, "relation_group_$idx")); + $oBlock = new DisplayBlock($oSearch, 'list'); + $oBlock->Display($oPage, 'group_'.$iBlock++, array( + 'surround_with_panel' => true, + 'panel_class' => $sListClass, + 'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass)), + 'panel_icon' => MetaModel::GetClassIcon($sListClass, false), + )); + } + break; + + case 'relation_lists': + $aLists = utils::ReadParam('lists'); + $iBlock = 1; // Zero is not a valid blockid + foreach ($aLists as $sListClass => $aKeys) { + $oSearch = new DBObjectSearch($sListClass); + $oSearch->AddCondition('id', $aKeys, 'IN'); + $oSearch->SetShowObsoleteData(utils::ShowObsoleteData()); + $oBlock = new DisplayBlock($oSearch, 'list'); + $oBlock->Display($oPage, 'list_'.$iBlock++, array( + 'table_id' => 'ImpactAnalysis_'.$sListClass, + 'surround_with_panel' => true, + 'panel_class' => $sListClass, + 'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass)), + 'panel_icon' => MetaModel::GetClassIcon($sListClass, false), + )); + } + break; + + case 'ticket_impact': + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + + $sRelation = utils::ReadParam('relation', 'impacts'); + $sDirection = utils::ReadParam('direction', 'down'); + $iGroupingThreshold = utils::ReadParam('g', 5); + $sClass = utils::ReadParam('class', '', false, 'class'); + $sAttCode = utils::ReadParam('attcode', 'functionalcis_list'); + $sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code'); + $sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual'); + $iId = (int)utils::ReadParam('id', 0, false, 'integer'); + + WebResourcesHelper::EnableSimpleGraphInWebPage($oPage); + + // Get the list of source objects + $oTicket = MetaModel::GetObject($sClass, $iId); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); + $oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote); + $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); + $oSet = $oTicket->Get($sAttCode); + $aSourceObjects = array(); + $aExcludedObjects = array(); + while ($oLnk = $oSet->Fetch()) { + if ($oLnk->Get($sImpactAttCode) == 'manual') { + $aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); + } + if ($oLnk->Get($sImpactAttCode) == 'not_impacted') { + $aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); + } + } + + // Compute the graph + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); + if ($sDirection == 'up') { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); + } else { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects); + } + + $aResults = $oRelGraph->GetObjectsByClass(); + $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); + + $sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection; + $oAppContext = new ApplicationContext(); + $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket)); + break; + + case 'export_build': $oPage = new JsonPage(); $oPage->SetOutputDataOnly(true); - $oAttachment = MetaModel::NewObject('Attachment'); - $oAttachment->Set('item_class', $sObjClass); - $oAttachment->Set('item_id', $iObjKey); - $oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf'); - $oAttachment->Set('contents', $oDoc); - $iAttachmentId = $oAttachment->DBInsert(); - $aRet = array( - 'status' => 'ok', - 'att_id' => $iAttachmentId, - ); - $oPage->SetData($aRet); - } - break; + $oAjaxRenderController->ExportBuild($oPage, false); + break; - case 'relation_json': - require_once(APPROOT.'core/simplegraph.class.inc.php'); - require_once(APPROOT.'core/relationgraph.class.inc.php'); - require_once(APPROOT.'core/displayablegraph.class.inc.php'); - $sRelation = utils::ReadParam('relation', 'impacts'); - $sDirection = utils::ReadParam('direction', 'down'); - $iGroupingThreshold = utils::ReadParam('g', 5); - $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); - $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); - $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); - $sContextKey = utils::ReadParam('context_key', array(), false, 'raw_data'); - $aPositions = null; - if ($sPositions != null) - { - $aPositions = json_decode($sPositions, true); - } + case 'export_build_portal': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + $oAjaxRenderController->ExportBuild($oPage, true); + break; - // Get the list of source objects - $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); - $aSourceObjects = array(); - foreach($aSources as $sClass => $aIDs) - { - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition('id', $aIDs, 'IN'); - $oSet = new DBObjectSet($oSearch); - while ($oObj = $oSet->Fetch()) - { - $aSourceObjects[] = $oObj; - } - } - - // Get the list of excluded objects - $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); - $aExcludedObjects = array(); - foreach($aExcluded as $sClass => $aIDs) - { - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition('id', $aIDs, 'IN'); - $oSet = new DBObjectSet($oSearch); - while ($oObj = $oSet->Fetch()) - { - $aExcludedObjects[] = $oObj; - } - } - - // Compute the graph - $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); - if ($sDirection == 'up') - { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); - } - else - { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); - } - - // Remove excluded classes from the graph - if (count($aExcludedClasses) > 0) - { - $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); - foreach($oIterator as $oNode) - { - $oObj = $oNode->GetProperty('object'); - if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) - { - $oRelGraph->FilterNode($oNode); + case 'export_download': + $token = utils::ReadParam('token', null); + if ($token !== null) { + $oExporter = BulkExport::FindExporterFromToken($token); + if ($oExporter) { + $sMimeType = $oExporter->GetMimeType(); + if (substr($sMimeType, 0, 5) == 'text/') { + $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); + } + $oPage = new DownloadPage(''); + $oPage->SetContentType($sMimeType); + $oPage->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); + $oPage->add(file_get_contents($oExporter->GetTmpFilePath())); } } - } + break; - $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); - $oGraph->InitFromGraphviz(); - if ($aPositions != null) - { - $oGraph->UpdatePositions($aPositions); - } - $oPage->add($oGraph->GetAsJSON($sContextKey)); - $oPage->SetContentType('application/json'); - break; - - case 'relation_groups': - $aGroups = utils::ReadParam('groups'); - $iBlock = 1; // Zero is not a valid blockid - foreach($aGroups as $idx => $aDefinition) - { - $sListClass = $aDefinition['class']; - $oSearch = new DBObjectSearch($sListClass); - $oSearch->AddCondition('id', $aDefinition['keys'], 'IN'); - $oSearch->SetShowObsoleteData(utils::ShowObsoleteData()); - $oPage->AddUiBlock(TitleUIBlockFactory::MakeNeutral(Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)),1,"relation_group_$idx")); - $oBlock = new DisplayBlock($oSearch, 'list'); - $oBlock->Display($oPage, 'group_'.$iBlock++, array('surround_with_panel' => true, - 'panel_class' => $sListClass, - 'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aDefinition['keys']), Metamodel::GetName($sListClass)), - 'panel_icon' => MetaModel::GetClassIcon($sListClass, false), - )); - } - break; - - case 'relation_lists': - $aLists = utils::ReadParam('lists'); - $iBlock = 1; // Zero is not a valid blockid - foreach($aLists as $sListClass => $aKeys) - { - $oSearch = new DBObjectSearch($sListClass); - $oSearch->AddCondition('id', $aKeys, 'IN'); - $oSearch->SetShowObsoleteData(utils::ShowObsoleteData()); - $oBlock = new DisplayBlock($oSearch, 'list'); - $oBlock->Display($oPage, 'list_'.$iBlock++, array('table_id' => 'ImpactAnalysis_'.$sListClass, - 'surround_with_panel' => true, - 'panel_class' => $sListClass, - 'panel_title' => Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', count($aKeys), Metamodel::GetName($sListClass)), - 'panel_icon' => MetaModel::GetClassIcon($sListClass, false), - )); - } - break; - - case 'ticket_impact': - require_once(APPROOT.'core/simplegraph.class.inc.php'); - require_once(APPROOT.'core/relationgraph.class.inc.php'); - require_once(APPROOT.'core/displayablegraph.class.inc.php'); - - $sRelation = utils::ReadParam('relation', 'impacts'); - $sDirection = utils::ReadParam('direction', 'down'); - $iGroupingThreshold = utils::ReadParam('g', 5); - $sClass = utils::ReadParam('class', '', false, 'class'); - $sAttCode = utils::ReadParam('attcode', 'functionalcis_list'); - $sImpactAttCode = utils::ReadParam('impact_attcode', 'impact_code'); - $sImpactAttCodeValue = utils::ReadParam('impact_attcode_value', 'manual'); - $iId = (int)utils::ReadParam('id', 0, false, 'integer'); - - WebResourcesHelper::EnableSimpleGraphInWebPage($oPage); - - // Get the list of source objects - $oTicket = MetaModel::GetObject($sClass, $iId); - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $sExtKeyToRemote = $oAttDef->GetExtKeyToRemote(); - $oExtKeyToRemote = MetaModel::GetAttributeDef($oAttDef->GetLinkedClass(), $sExtKeyToRemote); - $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); - $oSet = $oTicket->Get($sAttCode); - $aSourceObjects = array(); - $aExcludedObjects = array(); - while ($oLnk = $oSet->Fetch()) - { - if ($oLnk->Get($sImpactAttCode) == 'manual') - { - $aSourceObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); - } - if ($oLnk->Get($sImpactAttCode) == 'not_impacted') - { - $aExcludedObjects[] = MetaModel::GetObject($sRemoteClass, $oLnk->Get($sExtKeyToRemote)); - } - } - - // Compute the graph - $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); - if ($sDirection == 'up') - { - $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth); - } - else - { - $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, $aExcludedObjects); - } - - $aResults = $oRelGraph->GetObjectsByClass(); - $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); - - $sContextKey = 'itop-tickets/relation_context/'.$sClass.'/'.$sRelation.'/'.$sDirection; - $oAppContext = new ApplicationContext(); - $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId, $sContextKey, array('this' => $oTicket)); - break; - - case 'export_build': - $oPage = new JsonPage(); - $oPage->SetOutputDataOnly(true); - $oAjaxRenderController->ExportBuild($oPage, false); - break; - - case 'export_build_portal': - $oPage = new JsonPage(); - $oPage->SetOutputDataOnly(true); - $oAjaxRenderController->ExportBuild($oPage, true); - break; - - case 'export_download': - $token = utils::ReadParam('token', null); - if ($token !== null) { - $oExporter = BulkExport::FindExporterFromToken($token); - if ($oExporter) { - $sMimeType = $oExporter->GetMimeType(); - if (substr($sMimeType, 0, 5) == 'text/') { - $sMimeType .= ';charset='.strtolower($oExporter->GetCharacterSet()); + case 'export_cancel': + $token = utils::ReadParam('token', null); + if ($token !== null) { + $oExporter = BulkExport::FindExporterFromToken($token); + if ($oExporter) { + $oExporter->Cleanup(); } - $oPage = new DownloadPage(''); - $oPage->SetContentType($sMimeType); - $oPage->SetContentDisposition('attachment', $oExporter->GetDownloadFileName()); - $oPage->add(file_get_contents($oExporter->GetTmpFilePath())); } - } - break; + $aResult = array('code' => 'error', 'percentage' => 100, 'message' => Dict::S('Core:BulkExport:ExportCancelledByUser')); + $oPage->add(json_encode($aResult)); + break; - case 'export_cancel': - $token = utils::ReadParam('token', null); - if ($token !== null) - { - $oExporter = BulkExport::FindExporterFromToken($token); - if ($oExporter) { - $oExporter->Cleanup(); - } - } - $aResult = array('code' => 'error', 'percentage' => 100, 'message' => Dict::S('Core:BulkExport:ExportCancelledByUser')); - $oPage->add(json_encode($aResult)); - break; - - case 'check_lock_state': - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - $aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey); - - $aResult = [ - 'locked' => $aLockData['locked'], - 'message' => '', - ]; - - // If lock taken by someone else, tell by who - if (true === $aLockData['locked']) { - // Either the contact friendlyname if the user has a contact, otherwise its login - $sOwner = ($aLockData['owner']->Get('contactid') > 0) ? $aLockData['owner']->Get('contactid_friendlyname') : $aLockData['owner']->GetRawName(); - $aResult['message'] = Dict::Format('UI:CurrentObjectIsSoftLockedBy_User', $sOwner); - } - - $oPage->SetContentType('application/json'); - $oPage->add(json_encode($aResult)); - break; - - // Important: Only from the backoffice AND logged in - case 'acquire_lock': - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - - $aResult = iTopOwnershipLock::AcquireLock($sObjClass, $iObjKey); - if (false === $aResult['success']) { + case 'check_lock_state': + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); $aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey); + + $aResult = [ + 'locked' => $aLockData['locked'], + 'message' => '', + ]; + // If lock taken by someone else, tell by who if (true === $aLockData['locked']) { // Either the contact friendlyname if the user has a contact, otherwise its login $sOwner = ($aLockData['owner']->Get('contactid') > 0) ? $aLockData['owner']->Get('contactid_friendlyname') : $aLockData['owner']->GetRawName(); $aResult['message'] = Dict::Format('UI:CurrentObjectIsSoftLockedBy_User', $sOwner); } - } - $oPage->SetContentType('application/json'); - $oPage->add(json_encode($aResult)); - break; + $oPage->SetContentType('application/json'); + $oPage->add(json_encode($aResult)); + break; - case 'extend_lock': - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - $sToken = utils::ReadParam('token', 0, false, 'raw_data'); + // Important: Only from the backoffice AND logged in + case 'acquire_lock': + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - $aResult = iTopOwnershipLock::ExtendLock($sObjClass, $iObjKey, $sToken); - if (!$aResult['status']) { - if ($aResult['operation'] == 'lost') { - $sName = $aResult['owner']->GetName(); - if ($aResult['owner']->Get('contactid') != 0) { - $sName .= ' ('.$aResult['owner']->Get('contactid_friendlyname').')'; - } - $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); - $aResult['popup_message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User_Explanation', $sName); - } else { - if ($aResult['operation'] == 'expired') { - $aResult['message'] = Dict::S('UI:CurrentObjectLockExpired'); - $aResult['popup_message'] = Dict::S('UI:CurrentObjectLockExpired_Explanation'); + $aResult = iTopOwnershipLock::AcquireLock($sObjClass, $iObjKey); + if (false === $aResult['success']) { + $aLockData = iTopOwnershipLock::IsLocked($sObjClass, $iObjKey); + // If lock taken by someone else, tell by who + if (true === $aLockData['locked']) { + // Either the contact friendlyname if the user has a contact, otherwise its login + $sOwner = ($aLockData['owner']->Get('contactid') > 0) ? $aLockData['owner']->Get('contactid_friendlyname') : $aLockData['owner']->GetRawName(); + $aResult['message'] = Dict::Format('UI:CurrentObjectIsSoftLockedBy_User', $sOwner); } } - } - $oPage->SetContentType('application/json'); - $oPage->add(json_encode($aResult)); - break; + $oPage->SetContentType('application/json'); + $oPage->add(json_encode($aResult)); + break; - case 'release_lock': - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); - $sToken = utils::ReadParam('token', 0, false, 'raw_data'); + case 'extend_lock': + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); + $sToken = utils::ReadParam('token', 0, false, 'raw_data'); - $bReleased = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken); - $aResult = [ - 'success' => $bReleased, - ]; + $aResult = iTopOwnershipLock::ExtendLock($sObjClass, $iObjKey, $sToken); + if (!$aResult['status']) { + if ($aResult['operation'] == 'lost') { + $sName = $aResult['owner']->GetName(); + if ($aResult['owner']->Get('contactid') != 0) { + $sName .= ' ('.$aResult['owner']->Get('contactid_friendlyname').')'; + } + $aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName); + $aResult['popup_message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User_Explanation', $sName); + } else { + if ($aResult['operation'] == 'expired') { + $aResult['message'] = Dict::S('UI:CurrentObjectLockExpired'); + $aResult['popup_message'] = Dict::S('UI:CurrentObjectLockExpired_Explanation'); + } + } + } - $oPage->SetContentType('application/json'); - $oPage->add(json_encode($aResult)); - break; + $oPage->SetContentType('application/json'); + $oPage->add(json_encode($aResult)); + break; - case 'watchdog': - $oPage->add('ok'); // Better for debugging... - break; + case 'release_lock': + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); + $sToken = utils::ReadParam('token', 0, false, 'raw_data'); - case 'cke_img_upload': - $oPage = new JsonPage(); - $oPage->SetOutputDataOnly(true); + $bReleased = iTopOwnershipLock::ReleaseLock($sObjClass, $iObjKey, $sToken); + $aResult = [ + 'success' => $bReleased, + ]; - // Image uploaded via CKEditor - $aResult = array( - 'uploaded' => 0, - 'fileName' => '', - 'url' => '', - 'icon' => '', - 'msg' => '', - 'att_id' => 0, - 'preview' => 'false', - ); + $oPage->SetContentType('application/json'); + $oPage->add(json_encode($aResult)); + break; - $sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class')); - $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); - if (empty($sObjClass)) - { - $aResult['error'] = "Missing argument 'obj_class'"; - } - elseif (empty($sTempId)) - { - $aResult['error'] = "Missing argument 'temp_id'"; - } - else - { - try - { + case 'watchdog': + $oPage->add('ok'); // Better for debugging... + break; + + case 'cke_img_upload': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + + // Image uploaded via CKEditor + $aResult = array( + 'uploaded' => 0, + 'fileName' => '', + 'url' => '', + 'icon' => '', + 'msg' => '', + 'att_id' => 0, + 'preview' => 'false', + ); + + $sObjClass = stripslashes(utils::ReadParam('obj_class', '', false, 'class')); + $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); + if (empty($sObjClass)) { + $aResult['error'] = "Missing argument 'obj_class'"; + } elseif (empty($sTempId)) { + $aResult['error'] = "Missing argument 'temp_id'"; + } else { + try { + $oDoc = utils::ReadPostedDocument('upload'); + if (InlineImage::IsImage($oDoc->GetMimeType())) { + $aDimensions = null; + $oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions); + /** @var InlineImage $oAttachment */ + $oAttachment = MetaModel::NewObject('InlineImage'); + $oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); + $oAttachment->Set('temp_id', $sTempId); + $oAttachment->Set('item_class', $sObjClass); + $oAttachment->SetDefaultOrgId(); + $oAttachment->Set('contents', $oDoc); + $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess + $iAttId = $oAttachment->DBInsert(); + + $aResult['uploaded'] = 1; + $aResult['msg'] = utils::EscapeHtml($oDoc->GetFileName()); + $aResult['fileName'] = $oDoc->GetFileName(); + $aResult['url'] = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.$iAttId.'&s='.$oAttachment->Get('secret'); + if (is_array($aDimensions)) { + $aResult['width'] = $aDimensions['width']; + $aResult['height'] = $aDimensions['height']; + } + + IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( + '$operation' => $operation, + '$aResult' => $aResult, + 'secret' => $oAttachment->Get('secret'), + 'temp_id' => $sTempId, + 'item_class' => $sObjClass, + 'user' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], + )); + } else { + $aResult['error'] = $oDoc->GetFileName().' is not a valid image format.'; + } + } + catch (FileUploadException $e) { + $aResult['error'] = $e->GetMessage(); + } + } + $oPage->SetData($aResult); + break; + + /** @noinspection PhpMissingBreakStatementInspection cke_upload_and_browse and cke_browse are chained */ + case 'cke_upload_and_browse': + $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + try { $oDoc = utils::ReadPostedDocument('upload'); - if (InlineImage::IsImage($oDoc->GetMimeType())) - { + $sDocMimeType = $oDoc->GetMimeType(); + if (!InlineImage::IsImage($sDocMimeType)) { + LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image', + array( + 'operation' => 'cke_upload_and_browse', + 'class' => $sObjClass, + 'ImgMimeType' => $sDocMimeType, + )); + } else { $aDimensions = null; $oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions); /** @var InlineImage $oAttachment */ @@ -2228,138 +2160,77 @@ EOF $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess $iAttId = $oAttachment->DBInsert(); - $aResult['uploaded'] = 1; - $aResult['msg'] = utils::EscapeHtml($oDoc->GetFileName()); - $aResult['fileName'] = $oDoc->GetFileName(); - $aResult['url'] = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL.$iAttId.'&s='.$oAttachment->Get('secret'); - if (is_array($aDimensions)) { - $aResult['width'] = $aDimensions['width']; - $aResult['height'] = $aDimensions['height']; - } - IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( '$operation' => $operation, - '$aResult' => $aResult, - 'secret' => $oAttachment->Get('secret'), - 'temp_id' => $sTempId, - 'item_class' => $sObjClass, - 'user' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); - } - else - { - $aResult['error'] = $oDoc->GetFileName().' is not a valid image format.'; - } - } catch (FileUploadException $e) - { - $aResult['error'] = $e->GetMessage(); - } - } - $oPage->SetData($aResult); - break; - - /** @noinspection PhpMissingBreakStatementInspection cke_upload_and_browse and cke_browse are chained */ - case 'cke_upload_and_browse': - $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); - $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); - try - { - $oDoc = utils::ReadPostedDocument('upload'); - $sDocMimeType = $oDoc->GetMimeType(); - if (!InlineImage::IsImage($sDocMimeType)) - { - LogErrorMessage('CKE : error when uploading image in ajax.render.php, not an image', - array( - 'operation' => 'cke_upload_and_browse', - 'class' => $sObjClass, - 'ImgMimeType' => $sDocMimeType, + 'secret' => $oAttachment->Get('secret'), + 'temp_id' => $sTempId, + 'item_class' => $sObjClass, + 'user' => UserRights::GetUser(), + 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], + 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], )); - } else { - $aDimensions = null; - $oDoc = InlineImage::ResizeImageToFit($oDoc, $aDimensions); - /** @var InlineImage $oAttachment */ - $oAttachment = MetaModel::NewObject('InlineImage'); - $oAttachment->Set('expire', time() + MetaModel::GetConfig()->Get('draft_attachments_lifetime')); - $oAttachment->Set('temp_id', $sTempId); - $oAttachment->Set('item_class', $sObjClass); - $oAttachment->SetDefaultOrgId(); - $oAttachment->Set('contents', $oDoc); - $oAttachment->Set('secret', sprintf('%06x', mt_rand(0, 0xFFFFFF))); // something not easy to guess - $iAttId = $oAttachment->DBInsert(); + } - IssueLog::Trace('InlineImage created', LogChannels::INLINE_IMAGE, array( - '$operation' => $operation, - 'secret' => $oAttachment->Get('secret'), - 'temp_id' => $sTempId, - 'item_class' => $sObjClass, - 'user' => UserRights::GetUser(), - 'HTTP_REFERER' => @$_SERVER['HTTP_REFERER'], - 'REQUEST_URI' => @$_SERVER['REQUEST_URI'], - )); + } + catch (FileUploadException $e) { + LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured', + array( + 'operation' => 'cke_upload_and_browse', + 'class' => $sObjClass, + 'exceptionMsg' => $e, + )); + } + // Fall though !! => browse + + case 'cke_browse': + $oPage = new NiceWebPage(Dict::S('UI:BrowseInlineImages')); + $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/magnific-popup.css'); + $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.magnific-popup.min.js'); + $sImgUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL; + + /** @noinspection SuspiciousAssignmentsInspection cke_upload_and_browse and cke_browse are chained */ + $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); + $sClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjectId = utils::ReadParam('obj_key', 0, false, 'integer'); + $sCKEditorFuncNum = utils::ReadParam('CKEditorFuncNum', ''); + + if (empty($sTempId)) { + throw new SecurityException('Cannot access endpoint with empty temp_id parameter'); + } + if (false === privUITransaction::IsTransactionValid($sTempId, false)) { + throw new SecurityException('Access rejected'); + } + if (false === MetaModel::IsValidClass($sClass)) { + throw new CoreUnexpectedValue('Invalid object'); + } + if ($iObjectId > 0) { + // searching for object in the DB with a count query + // using DBSearch so that user rights are applied ! + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition(MetaModel::DBGetKey($sClass), $iObjectId, '='); + $oSet = new CMDBObjectSet($oSearch); + if (false === $oSet->CountExceeds(0)) { + throw new SecurityException(Dict::S('UI:ObjectDoesNotExist')); + } } - } catch (FileUploadException $e) - { - LogErrorMessage('CKE : error when uploading image in ajax.render.php, exception occured', - array( - 'operation' => 'cke_upload_and_browse', - 'class' => $sObjClass, - 'exceptionMsg' => $e, - )); - } - // Fall though !! => browse + $sPostUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum; - case 'cke_browse': - $oPage = new NiceWebPage(Dict::S('UI:BrowseInlineImages')); - $oPage->add_linked_stylesheet(utils::GetAbsoluteUrlAppRoot().'css/magnific-popup.css'); - $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/jquery.magnific-popup.min.js'); - $sImgUrl = utils::GetAbsoluteUrlAppRoot().INLINEIMAGE_DOWNLOAD_URL; - - /** @noinspection SuspiciousAssignmentsInspection cke_upload_and_browse and cke_browse are chained */ - $sTempId = utils::ReadParam('temp_id', '', false, 'transaction_id'); - $sClass = utils::ReadParam('obj_class', '', false, 'class'); - $iObjectId = utils::ReadParam('obj_key', 0, false, 'integer'); - $sCKEditorFuncNum = utils::ReadParam('CKEditorFuncNum', ''); - - if (empty($sTempId)) { - throw new SecurityException('Cannot access endpoint with empty temp_id parameter'); - } - if (false === privUITransaction::IsTransactionValid($sTempId, false)) { - throw new SecurityException('Access rejected'); - } - if (false === MetaModel::IsValidClass($sClass)) { - throw new CoreUnexpectedValue('Invalid object'); - } - if ($iObjectId > 0) { - // searching for object in the DB with a count query - // using DBSearch so that user rights are applied ! - $oSearch = new DBObjectSearch($sClass); - $oSearch->AddCondition(MetaModel::DBGetKey($sClass), $iObjectId, '='); - $oSet = new CMDBObjectSet($oSearch); - if (false === $oSet->CountExceeds(0)) { - throw new SecurityException(Dict::S('UI:ObjectDoesNotExist')); - } - } - - $sPostUrl = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?CKEditorFuncNum='.$sCKEditorFuncNum; - - $oPage->add_style( - <<add_style( + <<add( - <<add( + <<
$sUploadLegend @@ -2373,10 +2244,10 @@ EOF
EOF - ); + ); - $oPage->add_script( - <<add_script( + <<add_ready_script( - <<add_ready_script( + <<'); $('#upload_form').submit(); @@ -2417,270 +2288,266 @@ $('#upload_button').on('change', function() { }); $('.img-picker').magnificPopup({type: 'image', closeOnContentClick: true }); EOF - ); - $sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))"; - $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId)); - $oPage->add("
$sAvailableImagesLegend"); - - if ($oSet->Count() == 0) - { - $oPage->add("

$sNoInlineImage

"); - } - else - { - while ($oAttachment = $oSet->Fetch()) - { - $oDoc = $oAttachment->Get('contents'); - if ($oDoc->GetMainMimeType() == 'image') { - $sDocName = addslashes(utils::EscapeHtml($oDoc->GetFileName())); - $iAttId = $oAttachment->GetKey(); - $sSecret = $oAttachment->Get('secret'); - $oPage->add("
\"$sDocName\"
"); - } - } - } - $oPage->add("
"); - break; - - // TODO 3.0.0: Move this to new ajax render controller? - case 'cke_mentions': - $oPage->SetContentType('application/json'); - $sMarker = utils::ReadParam('marker', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); - $sNeedle = utils::ReadParam('needle', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); - $sHostClass = utils::ReadParam('host_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); - $iHostId = (int) utils::ReadParam('host_id', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER); - - // Check parameters - if($sMarker === '') { - throw new Exception('Invalid parameters, marker must be specified.'); - } - - $aMentionsAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes'); - if (isset($aMentionsAllowedClasses[$sMarker]) === false) { - throw new Exception('Invalid marker "'.$sMarker.'"'); - } - - $aMatches = array(); - if ($sNeedle !== '') { - // Retrieve mentioned class from marker - $sMentionedClass = $aMentionsAllowedClasses[$sMarker]; - if (MetaModel::IsValidClass($sMentionedClass) === false) { - throw new Exception('Invalid class "'.$sMentionedClass.'" for marker "'.$sMarker.'"'); - } - - // Base search used when no trigger configured - $oSearch = DBSearch::FromOQL("SELECT $sMentionedClass"); - $aSearchParams = ['needle' => "%$sNeedle%"]; - - // Retrieve restricting scopes from triggers if any - if ((strlen($sHostClass) > 0) && ($iHostId > 0)) { - $oHostObj = MetaModel::GetObject($sHostClass, $iHostId); - $aSearchParams['this'] = $oHostObj; - - $aTriggerMentionedSearches = []; - - $aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL)); - $oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aTriggerSetParams); - /** @var \TriggerOnObjectMention $oTrigger */ - while ($oTrigger = $oTriggerSet->Fetch()) { - $sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter'); - - // No filter on mentioned objects, don't restrict the scope at all, it can be any object of $sMentionedClass - if (strlen($sTriggerMentionedOQL) === 0) { - $aTriggerMentionedSearches = [$oSearch]; - break; - } - - $oTriggerMentionedSearch = DBSearch::FromOQL($sTriggerMentionedOQL); - $sTriggerMentionedClass = $oTriggerMentionedSearch->GetClass(); - - // Filter is not about the mentioned class, don't mind it - if (is_a($sMentionedClass, $sTriggerMentionedClass, true) === false) { - continue; - } - - $aTriggerMentionedSearches[] = $oTriggerMentionedSearch; - } - - if (count($aTriggerMentionedSearches) > 0) { - $oSearch = new DBUnionSearch($aTriggerMentionedSearches); - } - } - - $sSearchMainClassName = $oSearch->GetClass(); - $sSearchMainClassAlias = $oSearch->GetClassAlias(); - - $sObjectImageAttCode = MetaModel::GetImageAttributeCode($sSearchMainClassName); - - // Add condition to filter on the friendlyname - $oSearch->AddConditionExpression( - new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE', new VariableExpression('needle')) ); + $sOQL = "SELECT InlineImage WHERE ((temp_id = :temp_id) OR (item_class = :obj_class AND item_id = :obj_id))"; + $oSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL), array(), array('temp_id' => $sTempId, 'obj_class' => $sClass, 'obj_id' => $iObjectId)); + $oPage->add("
$sAvailableImagesLegend"); - $oSet = new DBObjectSet($oSearch, [], $aSearchParams); - // Optimize fields to load - $aObjectAttCodesToLoad = []; - if (MetaModel::IsValidAttCode($sSearchMainClassName, $sObjectImageAttCode)) { - $aObjectAttCodesToLoad[] = $sObjectImageAttCode; + if ($oSet->Count() == 0) { + $oPage->add("

$sNoInlineImage

"); + } else { + while ($oAttachment = $oSet->Fetch()) { + $oDoc = $oAttachment->Get('contents'); + if ($oDoc->GetMainMimeType() == 'image') { + $sDocName = addslashes(utils::EscapeHtml($oDoc->GetFileName())); + $iAttId = $oAttachment->GetKey(); + $sSecret = $oAttachment->Get('secret'); + $oPage->add("
\"$sDocName\"
"); + } + } } - $oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => $aObjectAttCodesToLoad]); - $oSet->SetLimit(MetaModel::GetConfig()->Get('max_autocomplete_results')); - // Note: We have to this manually because of a bug in DBSearch not checking the user prefs. by default. - $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); + $oPage->add("
"); + break; - while ($oObject = $oSet->Fetch()) { - // Note $oObject finalclass might be different than $sMentionedClass - $sObjectClass = get_class($oObject); - $iObjectId = $oObject->GetKey(); - $aMatch = [ - 'class' => $sObjectClass, - 'id' => $iObjectId, - 'friendlyname' => $oObject->Get('friendlyname'), - ]; + // TODO 3.0.0: Move this to new ajax render controller? + case 'cke_mentions': + $oPage->SetContentType('application/json'); + $sMarker = utils::ReadParam('marker', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + $sNeedle = utils::ReadParam('needle', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + $sHostClass = utils::ReadParam('host_class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); + $iHostId = (int)utils::ReadParam('host_id', 0, false, utils::ENUM_SANITIZATION_FILTER_INTEGER); - // Try to retrieve image for contact - if (!empty($sObjectImageAttCode)) { - /** @var \ormDocument $oImage */ - $oImage = $oObject->Get($sObjectImageAttCode); - if (!$oImage->IsEmpty()) { - $aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId, $sObjectImageAttCode)."')"; - $aMatch['initials'] = ''; - } else { - // If no image found, fallback on initials - $aMatch['picture_style'] = ''; - $aMatch['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObject->Get('friendlyname'))); + // Check parameters + if ($sMarker === '') { + throw new Exception('Invalid parameters, marker must be specified.'); + } + + $aMentionsAllowedClasses = MetaModel::GetConfig()->Get('mentions.allowed_classes'); + if (isset($aMentionsAllowedClasses[$sMarker]) === false) { + throw new Exception('Invalid marker "'.$sMarker.'"'); + } + + $aMatches = array(); + if ($sNeedle !== '') { + // Retrieve mentioned class from marker + $sMentionedClass = $aMentionsAllowedClasses[$sMarker]; + if (MetaModel::IsValidClass($sMentionedClass) === false) { + throw new Exception('Invalid class "'.$sMentionedClass.'" for marker "'.$sMarker.'"'); + } + + // Base search used when no trigger configured + $oSearch = DBSearch::FromOQL("SELECT $sMentionedClass"); + $aSearchParams = ['needle' => "%$sNeedle%"]; + + // Retrieve restricting scopes from triggers if any + if ((strlen($sHostClass) > 0) && ($iHostId > 0)) { + $oHostObj = MetaModel::GetObject($sHostClass, $iHostId); + $aSearchParams['this'] = $oHostObj; + + $aTriggerMentionedSearches = []; + + $aTriggerSetParams = array('class_list' => MetaModel::EnumParentClasses($sHostClass, ENUM_PARENT_CLASSES_ALL)); + $oTriggerSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnObjectMention AS t WHERE t.target_class IN (:class_list)"), array(), $aTriggerSetParams); + /** @var \TriggerOnObjectMention $oTrigger */ + while ($oTrigger = $oTriggerSet->Fetch()) { + $sTriggerMentionedOQL = $oTrigger->Get('mentioned_filter'); + + // No filter on mentioned objects, don't restrict the scope at all, it can be any object of $sMentionedClass + if (strlen($sTriggerMentionedOQL) === 0) { + $aTriggerMentionedSearches = [$oSearch]; + break; + } + + $oTriggerMentionedSearch = DBSearch::FromOQL($sTriggerMentionedOQL); + $sTriggerMentionedClass = $oTriggerMentionedSearch->GetClass(); + + // Filter is not about the mentioned class, don't mind it + if (is_a($sMentionedClass, $sTriggerMentionedClass, true) === false) { + continue; + } + + $aTriggerMentionedSearches[] = $oTriggerMentionedSearch; + } + + if (count($aTriggerMentionedSearches) > 0) { + $oSearch = new DBUnionSearch($aTriggerMentionedSearches); } } - $aMatches[] = $aMatch; + $sSearchMainClassName = $oSearch->GetClass(); + $sSearchMainClassAlias = $oSearch->GetClassAlias(); + + $sObjectImageAttCode = MetaModel::GetImageAttributeCode($sSearchMainClassName); + + // Add condition to filter on the friendlyname + $oSearch->AddConditionExpression( + new BinaryExpression(new FieldExpression('friendlyname', $sSearchMainClassAlias), 'LIKE', new VariableExpression('needle')) + ); + + $oSet = new DBObjectSet($oSearch, [], $aSearchParams); + // Optimize fields to load + $aObjectAttCodesToLoad = []; + if (MetaModel::IsValidAttCode($sSearchMainClassName, $sObjectImageAttCode)) { + $aObjectAttCodesToLoad[] = $sObjectImageAttCode; + } + $oSet->OptimizeColumnLoad([$oSearch->GetClassAlias() => $aObjectAttCodesToLoad]); + $oSet->SetLimit(MetaModel::GetConfig()->Get('max_autocomplete_results')); + // Note: We have to this manually because of a bug in DBSearch not checking the user prefs. by default. + $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); + + while ($oObject = $oSet->Fetch()) { + // Note $oObject finalclass might be different than $sMentionedClass + $sObjectClass = get_class($oObject); + $iObjectId = $oObject->GetKey(); + $aMatch = [ + 'class' => $sObjectClass, + 'id' => $iObjectId, + 'friendlyname' => $oObject->Get('friendlyname'), + ]; + + // Try to retrieve image for contact + if (!empty($sObjectImageAttCode)) { + /** @var \ormDocument $oImage */ + $oImage = $oObject->Get($sObjectImageAttCode); + if (!$oImage->IsEmpty()) { + $aMatch['picture_style'] = "background-image: url('".$oImage->GetDisplayURL($sObjectClass, $iObjectId, $sObjectImageAttCode)."')"; + $aMatch['initials'] = ''; + } else { + // If no image found, fallback on initials + $aMatch['picture_style'] = ''; + $aMatch['initials'] = utils::FormatInitialsForMedallion(utils::ToAcronym($oObject->Get('friendlyname'))); + } + } + + $aMatches[] = $aMatch; + } } - } - $oPage->add(json_encode($aMatches)); - break; + $oPage->add(json_encode($aMatches)); + break; - case 'custom_fields_update': - $oPage->SetContentType('application/json'); - $sAttCode = utils::ReadParam('attcode', ''); - $aRequestedFields = utils::ReadParam('requested_fields', array()); - $sRequestedFieldsFormPath = utils::ReadParam('form_path', ''); - $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); + case 'custom_fields_update': + $oPage->SetContentType('application/json'); + $sAttCode = utils::ReadParam('attcode', ''); + $aRequestedFields = utils::ReadParam('requested_fields', array()); + $sRequestedFieldsFormPath = utils::ReadParam('form_path', ''); + $sJson = utils::ReadParam('json_obj', '', false, 'raw_data'); - $aResult = array(); + $aResult = array(); - try - { - $oWizardHelper = WizardHelper::FromJSON($sJson); - $oObj = $oWizardHelper->GetTargetObject(); + try { + $oWizardHelper = WizardHelper::FromJSON($sJson); + $oObj = $oWizardHelper->GetTargetObject(); - $oOrmCustomFieldValue = $oObj->Get($sAttCode); - $oForm = $oOrmCustomFieldValue->GetForm(); - $oSubForm = $oForm->FindSubForm($sRequestedFieldsFormPath); - $oRenderer = new ConsoleFormRenderer($oSubForm); - $aRenderRes = $oRenderer->Render($aRequestedFields); + $oOrmCustomFieldValue = $oObj->Get($sAttCode); + $oForm = $oOrmCustomFieldValue->GetForm(); + $oSubForm = $oForm->FindSubForm($sRequestedFieldsFormPath); + $oRenderer = new ConsoleFormRenderer($oSubForm); + $aRenderRes = $oRenderer->Render($aRequestedFields); - $aResult['form']['updated_fields'] = $aRenderRes; - } - catch (Exception $e) { - $aResult['error'] = $e->getMessage(); - } - $oPage->add(json_encode($aResult)); - break; + $aResult['form']['updated_fields'] = $aRenderRes; + } + catch (Exception $e) { + $aResult['error'] = $e->getMessage(); + } + $oPage->add(json_encode($aResult)); + break; - //-------------------------------- - // Preferences - //-------------------------------- - /** @internal */ - case 'preferences.set_user_picture': - $oPage = new JsonPage(); - try { - $oController = new PreferencesController(); - $aResult = $oController->SetUserPicture(); - $aResult['success'] = true; - } - catch (Exception $oException) { - $aResult = [ - 'success' => false, - 'error_message' => $oException->getMessage(), - ]; - } - $oPage->SetData($aResult); - break; + //-------------------------------- + // Preferences + //-------------------------------- + /** @internal */ + case 'preferences.set_user_picture': + $oPage = new JsonPage(); + try { + $oController = new PreferencesController(); + $aResult = $oController->SetUserPicture(); + $aResult['success'] = true; + } + catch (Exception $oException) { + $aResult = [ + 'success' => false, + 'error_message' => $oException->getMessage(), + ]; + } + $oPage->SetData($aResult); + break; - //-------------------------------- - // Activity panel - //-------------------------------- - /** @internal */ - case 'activity_panel.save_state': - $oPage = new JsonPage(); - try { - $oController = new ActivityPanelController(); - $oController->SaveState(); - $aResult = [ - 'success' => true, - ]; - } - catch (Exception $oException) { - $aResult = [ - 'success' => false, - 'error_message' => $oException->getMessage(), - ]; - } - $oPage->SetData($aResult); - break; + //-------------------------------- + // Activity panel + //-------------------------------- + /** @internal */ + case 'activity_panel.save_state': + $oPage = new JsonPage(); + try { + $oController = new ActivityPanelController(); + $oController->SaveState(); + $aResult = [ + 'success' => true, + ]; + } + catch (Exception $oException) { + $aResult = [ + 'success' => false, + 'error_message' => $oException->getMessage(), + ]; + } + $oPage->SetData($aResult); + break; - /** @internal */ - case 'activity_panel.add_caselog_entries': - $oPage = new JsonPage(); - try { - $oController = new ActivityPanelController(); - $aResult = $oController->AddCaseLogsEntries(); - } - catch (Exception $oException) { - $aResult = [ - 'success' => false, - 'error_message' => $oException->getMessage(), - ]; - } - $oPage->SetData($aResult); - break; + /** @internal */ + case 'activity_panel.add_caselog_entries': + $oPage = new JsonPage(); + try { + $oController = new ActivityPanelController(); + $aResult = $oController->AddCaseLogsEntries(); + } + catch (Exception $oException) { + $aResult = [ + 'success' => false, + 'error_message' => $oException->getMessage(), + ]; + } + $oPage->SetData($aResult); + break; - /** @internal */ - case 'activity_panel.load_more_entries': - $oPage = new JsonPage(); - try { - $oController = new ActivityPanelController(); - $aResult = $oController->LoadMoreEntries(); - } - catch (Exception $oException) { - $aResult = [ - 'success' => false, - 'error_message' => $oException->getMessage(), - ]; - } - $oPage->SetData($aResult); - break; + /** @internal */ + case 'activity_panel.load_more_entries': + $oPage = new JsonPage(); + try { + $oController = new ActivityPanelController(); + $aResult = $oController->LoadMoreEntries(); + } + catch (Exception $oException) { + $aResult = [ + 'success' => false, + 'error_message' => $oException->getMessage(), + ]; + } + $oPage->SetData($aResult); + break; - //-------------------------------- - // Navigation menu - //-------------------------------- - case 'get_menus_count': - $oPage = new JsonPage(); - $oPage->SetOutputDataOnly(true); - $oAjaxRenderController->GetMenusCount($oPage); - break; + //-------------------------------- + // Navigation menu + //-------------------------------- + case 'get_menus_count': + $oPage = new JsonPage(); + $oPage->SetOutputDataOnly(true); + $oAjaxRenderController->GetMenusCount($oPage); + break; - //-------------------------------- - // Object - //-------------------------------- - /** @internal */ - case 'object.modify': - $oController = new ObjectController(); - $oPage = $oController->Modify(); - break; + //-------------------------------- + // Object + //-------------------------------- + /** @internal */ + case 'object.modify': + $oController = new ObjectController(); + $oPage = $oController->Modify(); + break; - default: - $oPage->p("Invalid query."); + default: + $oPage->p("Invalid query."); + } } $oKPI->ComputeAndReport('Data fetch and format'); $oPage->output(); diff --git a/sources/Controller/AbstractController.php b/sources/Controller/AbstractController.php index e2f4b6939..40f2b1f16 100644 --- a/sources/Controller/AbstractController.php +++ b/sources/Controller/AbstractController.php @@ -10,20 +10,16 @@ namespace Combodo\iTop\Controller; * Class AbstractController * * Abstract controller to centralize common features of business controllers which are still to be defined. + * Note that this can be extended by "TwigBase" controllers or standalone controllers. * * @author Guillaume Lajarige * @package Combodo\iTop\Controller * @since 3.1.0 */ -class AbstractController +abstract class AbstractController implements iController { /** - * It works if your JavaScript library sets an X-Requested-With HTTP header. - * It is known to work with common JavaScript frameworks: {@link https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript} - * - * @see \Symfony\Component\HttpFoundation\Request::isXmlHttpRequest() Inspired by - * - * @return bool True if the current request is an XmlHttpRequest (eg. an AJAX request) + * @inheritDoc */ public function IsHandlingXmlHttpRequest(): bool { diff --git a/sources/Controller/Base/Layout/ObjectController.php b/sources/Controller/Base/Layout/ObjectController.php index b52f855cf..a341abc21 100644 --- a/sources/Controller/Base/Layout/ObjectController.php +++ b/sources/Controller/Base/Layout/ObjectController.php @@ -11,6 +11,7 @@ use ApplicationException; use cmdbAbstractObject; use CMDBObjectSet; use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory; +use Combodo\iTop\Controller\AbstractController; use Dict; use iTopWebPage; use MetaModel; @@ -27,12 +28,9 @@ use WebPage; * @since 3.1.0 * @package Combodo\iTop\Controller\Base\Layout */ -class ObjectController extends \Combodo\iTop\Controller\AbstractController +class ObjectController extends AbstractController { - public function View() - { - - } + public const ROUTE_NAMESPACE = 'object'; /** * @return \iTopWebPage|\AjaxPage Object edit form in its webpage @@ -41,15 +39,12 @@ class ObjectController extends \Combodo\iTop\Controller\AbstractController * @throws \CoreException * @throws \SecurityException */ - public function Modify() + public function OperationModify() { $bPrintable = utils::ReadParam('printable', '0') === '1'; $sClass = utils::ReadParam('class', '', false, 'class'); $sId = utils::ReadParam('id', ''); - $sClass = 'Person'; - $sId = 6; - // Check parameters if (utils::IsNullOrEmptyString($sClass) || utils::IsNullOrEmptyString($sId)) { diff --git a/sources/Controller/iController.php b/sources/Controller/iController.php new file mode 100644 index 000000000..2a1b1638e --- /dev/null +++ b/sources/Controller/iController.php @@ -0,0 +1,30 @@ + + * @since 3.1.0 + * @package Combodo\iTop\Controller + */ +interface iController +{ + /** + * @var string|null Meant for overlaoding. Route namespace, what will prefix the "route" parameter to define in which namespoce the operation is to be executed. If left to `null`, the controller will be ignored. + */ + public const ROUTE_NAMESPACE = null; + + /** + * It works if your JavaScript library sets an X-Requested-With HTTP header. + * It is known to work with common JavaScript frameworks: {@link https://wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript} + * + * @see \Symfony\Component\HttpFoundation\Request::isXmlHttpRequest() Inspired by + * + * @return bool True if the current request is an XmlHttpRequest (eg. an AJAX request) + */ + public function IsHandlingXmlHttpRequest(): bool; +} diff --git a/sources/Router/Router.php b/sources/Router/Router.php new file mode 100644 index 000000000..4fcebb63e --- /dev/null +++ b/sources/Router/Router.php @@ -0,0 +1,183 @@ + + * @package Combodo\iTop\Router + * @since 3.1.0 + * @internal + */ +class Router +{ + /** @var \Combodo\iTop\Router\Router|null Singleton instance */ + protected static ?Router $oSingleton = null; + + /** + * @return $this The singleton instance of the router + */ + public static function GetInstance() + { + if (null === static::$oSingleton) { + static::$oSingleton = new static(); + } + + return static::$oSingleton; + } + + /**********************/ + /* Non-static methods */ + /**********************/ + + /** + * Singleton pattern, can't use the constructor. Use {@see \Combodo\iTop\Router\Router::GetInstance()} instead. + * + * @return void + */ + private function __construct() + { + // Don't do anything, we don't want to be initialized + } + + /** + * @param string $sRoute + * + * @return bool True if there is a matching handler for $sRoute + */ + public function CanDispatchRoute(string $sRoute): bool + { + return $this->GetDispatchSpecsForRoute($sRoute) !== null; + } + + /** + * Dispatch the current request to the matching handler for $sRoute + * + * @param string $sRoute + * + * @return mixed Response from the route's handler, can be anything. + * Even though it can be anything, in most cases, response will either be: + * - A \WebPage for usual backoffice operations + * - null for TwigBase backoffice operations + */ + public function DispatchRoute(string $sRoute) + { + $aMethodSpecs = $this->GetDispatchSpecsForRoute($sRoute); + $mResponse = call_user_func_array([new $aMethodSpecs[0](), $aMethodSpecs[1]], []); + + return $mResponse; + } + + /** + * @param string $sRoute + * + * @return array{sControllerFQCN, sOperationMethodName}|null The FQCN controller and operation method matching $sRoute, null if no matching handler + */ + public function GetDispatchSpecsForRoute(string $sRoute) + { + $aRouteParts = $this->GetRouteParts($sRoute); + if (is_null($aRouteParts)) { + return null; + } + + $sRouteNamespace = $aRouteParts['namespace']; + $sRouteOperation = $aRouteParts['operation']; + $sControllerFQCN = $this->FindControllerFromRouteNamespace($sRouteNamespace); + if (utils::IsNullOrEmptyString($sControllerFQCN)) { + return null; + } + + $sOperationMethodName = $this->MakeOperationMethodNameFromOperation($sRouteOperation); + $aMethodSpecs = [$sControllerFQCN, $sOperationMethodName]; + if (false === is_callable($aMethodSpecs)) { + return null; + } + + return $aMethodSpecs; + } + + /** + * @param string $sRoute + * + * @return array{namespace: string, operation: string}|null Route parts (namespace and operation) if route can be parsed, null otherwise + */ + public function GetRouteParts(string $sRoute) + { + if (utils::IsNullOrEmptyString($sRoute)) { + return null; + } + + $sRouteNamespace = $this->GetRouteNamespace($sRoute); + $sRouteOperation = $this->GetRouteOperation($sRoute); + if (utils::IsNullOrEmptyString($sRouteNamespace) || utils::IsNullOrEmptyString($sRouteOperation)) { + return null; + } + + return ['namespace' => $sRouteNamespace, 'operation' => $sRouteOperation]; + } + + /** + * @param string $sRoute + * + * @return string|null Namespace of the route (eg. "object" for "object.modify") if route can be parsed null otherwise + */ + public function GetRouteNamespace(string $sRoute): ?string + { + $mSeparatorPos = strripos($sRoute, '.', -1); + if (false === $mSeparatorPos) { + return null; + } + + return substr($sRoute, 0, $mSeparatorPos); + } + + /** + * @param string $sRoute + * + * @return string|null Operation of the route (eg. "modify" for "object.modify") if route can be parsed null otherwise + */ + public function GetRouteOperation(string $sRoute): ?string + { + $mSeparatorPos = strripos($sRoute, '.', -1); + if (false === $mSeparatorPos) { + return null; + } + + return substr($sRoute, $mSeparatorPos + 1); + } + + /** + * @param string $sRouteNamespace {@see static::$sRouteNamespace} + * + * @return string|null The FQCN of the controller matching the $sRouteNamespace, null if none matching. + */ + protected function FindControllerFromRouteNamespace(string $sRouteNamespace): ?string + { + foreach (utils::GetClassesForInterface('Combodo\iTop\Controller\iController', '', ['[\\\\/]lib[\\\\/]', '[\\\\/]node_modules[\\\\/]', '[\\\\/]test[\\\\/]']) as $sControllerFQCN) { + if ($sControllerFQCN::ROUTE_NAMESPACE === $sRouteNamespace) { + return $sControllerFQCN; + } + } + + return null; + } + + /** + * @param string $sOperation + * + * @return string The method name for the $sOperation regarding the convention + */ + protected function MakeOperationMethodNameFromOperation(string $sOperation): string + { + return 'Operation'.utils::ToCamelCase($sOperation); + } +} \ No newline at end of file diff --git a/test/sources/Router/RouterTest.php b/test/sources/Router/RouterTest.php new file mode 100644 index 000000000..82f7c6115 --- /dev/null +++ b/test/sources/Router/RouterTest.php @@ -0,0 +1,205 @@ + + * @since 3.1.0 + * @covers \Combodo\iTop\Router\Router + */ +class RouterTest extends ItopTestCase +{ + /** + * @dataProvider CanDispatchRouteProvider + * @covers \Combodo\iTop\Router\Router::CanDispatchRoute + * + * @param string $sRoute + * @param $bExpectedResult + * + * @return void + */ + public function testCanDispatchRoute(string $sRoute, $bExpectedResult): void + { + $oRouter = Router::GetInstance(); + $bTestedResult = $oRouter->CanDispatchRoute($sRoute); + + $sRouteNamespace = $oRouter->GetRouteNamespace($sRoute); + $sRouteOperation = $oRouter->GetRouteOperation($sRoute); + $aRouteParts = $oRouter->GetRouteParts($sRoute); + $sControllerFQCN = $this->InvokeNonPublicMethod(get_class($oRouter), 'FindControllerFromRouteNamespace', $oRouter, ['object']); + $sMethodName = $this->InvokeNonPublicMethod(get_class($oRouter), 'MakeOperationMethodNameFromOperation', $oRouter, ['modify']); + $aDispatchSpecs = $oRouter->GetDispatchSpecsForRoute($sRoute); + +$this->debug($sRoute); +$this->debug($sRouteNamespace); +$this->debug($sRouteOperation); +$this->debug($aRouteParts); +$this->debug($sControllerFQCN); +$this->debug($sMethodName); +$this->debug(is_callable([$sControllerFQCN, $sMethodName]) ? 'true' : 'false'); +$this->debug($aDispatchSpecs); +$this->debug($bTestedResult); + $this->assertEquals($bExpectedResult, $bTestedResult, "Dispatch capability for '$sRoute' was not the expected one. Got ".var_export($bTestedResult, true).", expected ".var_export($bExpectedResult, true)); + } + + public function CanDispatchRouteProvider(): array + { + return [ + 'Existing handler' => [ + 'object.modify', + true, + ], + 'Existing controller but unknown operation' => [ + 'object.modify_me_please', + false, + ], + 'Unknown controller' => [ + 'foo.bar', + false, + ], + ]; + } + + /** + * @dataProvider GetRouteNamespaceProvider + * @covers \Combodo\iTop\Router\Router::GetRouteNamespace + * + * @param string $sRoute + * @param string|null $sExpectedNamespace + * + * @return void + */ + public function testGetRouteNamespace(string $sRoute, ?string $sExpectedNamespace): void + { + $oRouter = Router::GetInstance(); + $sTestedNamespace = $oRouter->GetRouteNamespace($sRoute); + + $this->assertEquals($sExpectedNamespace, $sTestedNamespace, "Namespace found for '$sRoute' was not the expected one. Got '$sTestedNamespace', expected '$sExpectedNamespace'."); + } + + public function GetRouteNamespaceProvider(): array + { + return [ + 'Operation without namespace' => [ + 'some_operation', + null, + ], + 'Operation with namespace' => [ + 'some_namespace.some_operation', + 'some_namespace', + ], + 'Operation with multi-levels namespace' => [ + 'some.deep.namespace.some_operation', + 'some.deep.namespace', + ], + ]; + } + + /** + * @dataProvider GetRouteOperationProvider + * @covers \Combodo\iTop\Router\Router::GetRouteOperation + * + * @param string $sRoute + * @param string|null $sExpectedOperation + * + * @return void + */ + public function testGetRouteOperation(string $sRoute, ?string $sExpectedOperation): void + { + $oRouter = Router::GetInstance(); + $sTestedOperation = $oRouter->GetRouteOperation($sRoute); + + $this->assertEquals($sExpectedOperation, $sTestedOperation, "Operation found for '$sRoute' was not the expected one. Got '$sTestedOperation', expected '$sExpectedOperation'."); + } + + public function GetRouteOperationProvider(): array + { + return [ + 'Operation without namespace' => [ + 'some_operation', + null, + ], + 'Operation with namespace' => [ + 'some_namespace.some_operation', + 'some_operation', + ], + 'Operation with multi-levels namespace' => [ + 'some.deep.namespace.some_operation', + 'some_operation', + ], + ]; + } + + /** + * @dataProvider FindControllerFromRouteNamespaceProvider + * @covers \Combodo\iTop\Router\Router::FindControllerFromRouteNamespace + * + * @param string $sRouteNamespace + * @param string $sExpectedControllerFQCN + * + * @return void + */ + public function testFindControllerFromRouteNamespace(string $sRoute, ?string $sExpectedControllerFQCN): void + { + $oRouter = Router::GetInstance(); + $sRouteNamespace = $oRouter->GetRouteNamespace($sRoute); + + $sTestedControllerFQCN = $this->InvokeNonPublicMethod(get_class($oRouter), 'FindControllerFromRouteNamespace', $oRouter, [$sRouteNamespace]); + + $this->assertEquals($sExpectedControllerFQCN, $sTestedControllerFQCN, "Controller found for '$sRouteNamespace' was not the expected one. Got '$sTestedControllerFQCN', expected '$sExpectedControllerFQCN'."); + } + + public function FindControllerFromRouteNamespaceProvider(): array + { + return [ + 'Object controller' => [ + 'object.modify', + 'Combodo\iTop\Controller\Base\Layout\ObjectController', + ], + 'Unknown controller' => [ + 'something_that_should_not_exist_in_the_default_package.foo', + null, + ], + ]; + } + + /** + * @dataProvider GetOperationMethodNameFromRouteOperationProvider + * @covers \Combodo\iTop\Router\Router::MakeOperationMethodNameFromOperation + * + * @param string $sRoute + * @param string $sExpectedMethodName + * + * @return void + */ + public function testGetOperationMethodNameFromRouteOperation(string $sRoute, string $sExpectedMethodName): void + { + $oRouter = Router::GetInstance(); + $aRouteParts = $oRouter->GetRouteParts($sRoute); + + $sTestedMethodName = $this->InvokeNonPublicMethod(get_class($oRouter), 'MakeOperationMethodNameFromOperation', $oRouter, [$aRouteParts[1]]); + + $this->assertEquals($sExpectedMethodName, $sTestedMethodName, "Operation method name '$aRouteParts[1]' was not matching the expected one. Got '$sTestedMethodName', expected '$sExpectedMethodName'."); + } + + public function GetOperationMethodNameFromRouteOperationProvider(): array + { + return [ + 'Simple operation' => [ + 'object.modify', + 'OperationModify', + ], + 'Operation with an underscore' => [ + 'object.apply_modify', + 'OperationApplyModify', + ], + ]; + } +} \ No newline at end of file