From a333bcb0842dfa4d96b4cd7bb55464fea4a11fd7 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Mon, 28 Oct 2013 16:50:13 +0000 Subject: [PATCH] User portal: enable the creation of Incident tickets (ITIL + requires a change in the configuration file -see the readme file) SVN:trunk[2959] --- core/config.class.inc.php | 9 + .../datamodel.itop-request-mgmt.xml | 15 +- .../main.itop-welcome-itil.php | 12 +- .../datamodel.itop-request-mgmt-itil.xml | 26 +- .../datamodel.itop-request-mgmt.xml | 15 +- .../main.itop-welcome-itil.php | 12 +- portal/index.php | 371 +++++++++++++----- portal/readme.txt | 69 ++++ 8 files changed, 386 insertions(+), 143 deletions(-) create mode 100644 portal/readme.txt diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 9cb81f20b..3ea40ff00 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -667,6 +667,15 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'portal_tickets' => array( + 'type' => 'string', + 'description' => 'CSV list of classes supported in the portal', + // examples... not used + 'default' => 'UserRequest', + 'value' => 'UserRequest', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), ); public function IsProperty($sPropCode) diff --git a/datamodels/1.x/itop-request-mgmt-1.0.0/datamodel.itop-request-mgmt.xml b/datamodels/1.x/itop-request-mgmt-1.0.0/datamodel.itop-request-mgmt.xml index bc65906f9..ced2f4292 100644 --- a/datamodels/1.x/itop-request-mgmt-1.0.0/datamodel.itop-request-mgmt.xml +++ b/datamodels/1.x/itop-request-mgmt-1.0.0/datamodel.itop-request-mgmt.xml @@ -7,15 +7,16 @@ - - - - - + + + + + + - - + + diff --git a/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php b/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php index a9d1d964b..945b19e4b 100644 --- a/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php +++ b/datamodels/1.x/itop-welcome-itil/main.itop-welcome-itil.php @@ -71,16 +71,16 @@ class MyPortalURLMaker implements iDBObjectURLMaker { public static function MakeObjectURL($sClass, $iId) { - switch($sClass) + if (strpos(MetaModel::GetConfig()->Get('portal_tickets'), $sClass) !== false) { - case 'UserRequest': $sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot(); $sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId"; - return $sUrl; - - default: - return ''; } + else + { + $sUrl = ''; + } + return $sUrl; } } diff --git a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml index 823898159..2ecdb5268 100755 --- a/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml +++ b/datamodels/2.x/itop-request-mgmt-itil/datamodel.itop-request-mgmt-itil.xml @@ -3,19 +3,27 @@ - + - - - - - - + + - - + + + + + + + + + + + + + + diff --git a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml index d7a3115ac..c1ee0bfb4 100755 --- a/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml +++ b/datamodels/2.x/itop-request-mgmt/datamodel.itop-request-mgmt.xml @@ -7,15 +7,16 @@ - - - - - + + + + + + - - + + diff --git a/datamodels/2.x/itop-welcome-itil/main.itop-welcome-itil.php b/datamodels/2.x/itop-welcome-itil/main.itop-welcome-itil.php index 34de8240c..1879d420a 100755 --- a/datamodels/2.x/itop-welcome-itil/main.itop-welcome-itil.php +++ b/datamodels/2.x/itop-welcome-itil/main.itop-welcome-itil.php @@ -74,16 +74,16 @@ class MyPortalURLMaker implements iDBObjectURLMaker { public static function MakeObjectURL($sClass, $iId) { - switch($sClass) + if (strpos(MetaModel::GetConfig()->Get('portal_tickets'), $sClass) !== false) { - case 'UserRequest': $sAbsoluteUrl = utils::GetAbsoluteUrlAppRoot(); $sUrl = "{$sAbsoluteUrl}portal/index.php?operation=details&class=$sClass&id=$iId"; - return $sUrl; - - default: - return ''; } + else + { + $sUrl = ''; + } + return $sUrl; } } diff --git a/portal/index.php b/portal/index.php index c64c4b017..e1bc8f353 100644 --- a/portal/index.php +++ b/portal/index.php @@ -27,6 +27,91 @@ require_once(APPROOT.'/application/application.inc.php'); require_once(APPROOT.'/application/nicewebpage.class.inc.php'); require_once(APPROOT.'/application/wizardhelper.class.inc.php'); + +/** + * Helper to determine the supported types of tickets + */ +function GetTicketClasses() +{ + $aClasses = array(); + foreach (explode(',', MetaModel::GetConfig()->Get('portal_tickets')) as $sRawClass) + { + $sRawClass = trim($sRawClass); + if (!MetaModel::IsValidClass($sRawClass)) + { + throw new Exception("Class '$sRawClass' is not a valid class, please review your configuration (portal_tickets)"); + } + if (!MetaModel::IsParentClass('Ticket', $sRawClass)) + { + throw new Exception("Class '$sRawClass' does not inherit from Ticket, please review your configuration (portal_tickets)"); + } + $aClasses[] = $sRawClass; + } + return $aClasses; +} + +/** + * Helper to get the relevant constant + */ +function GetConstant($sClass, $sName) +{ + $sConstName = 'PORTAL_'.strtoupper($sClass).'_'.$sName; + if (defined($sConstName)) + { + return constant($sConstName); + } + else + { + throw new Exception("Missing portal constant '$sConstName'"); + } +} + +/** + * Helper to determine the ticket class given the service subcategory + */ +function ComputeClass($iSubSvcId) +{ + $aClasses = GetTicketClasses(); + if ((PORTAL_SET_TYPE_FROM == '') || (PORTAL_TYPE_TO_CLASS == '')) + { + // return the first enabled class + $sClass = reset($aClasses); + } + else + { + $oServiceSubcat = MetaModel::GetObject('ServiceSubcategory', $iSubSvcId, true, true /* allow all data*/); + $sTicketType = $oServiceSubcat->Get(PORTAL_SET_TYPE_FROM); + $aMapping = json_decode(PORTAL_TYPE_TO_CLASS, true); + if (!array_key_exists($sTicketType, $aMapping)) + { + throw new Exception("Ticket type '$sTicketType' not found in the mapping (".implode(', ', array_keys($aMapping))."). Please contact your administrator."); + } + $sClass = $aMapping[$sTicketType]; + if (!in_array($sClass, $aClasses)) + { + throw new Exception("Service subcategory #$iSubSvcId has a ticket type ($sClass) that is not known by the portal, please contact your administrator."); + } + } + return $sClass; +} + +/** + * Helper to limit the service categories depending on the current settings + */ +function RestrictSubcategories(&$oSearch) +{ + $aMapping = json_decode(PORTAL_TYPE_TO_CLASS, true); + foreach($aMapping as $sTicketType => $sClass) + { + if (!in_array($sClass, GetTicketClasses())) + { + // Exclude this value for the result set + $oSearch->AddCondition(PORTAL_SET_TYPE_FROM, $sTicketType, '!='); + } + } +} + + /** * Displays the portal main menu * @param WebPage $oP The current web page @@ -147,6 +232,7 @@ function SelectServiceSubCategory($oP, $oUserOrg, $iSvcId = null) $iDefaultWizNext = 2; $oSearch = DBObjectSearch::FromOQL(PORTAL_SERVICE_SUBCATEGORY_QUERY); + RestrictSubcategories($oSearch); $oSearch->AllowAllData(); // In case the user has the rights on his org only $oSet = new CMDBObjectSet($oSearch, array(), array('svc_id' => $iSvcId, 'org_id' => $oUserOrg->GetKey())); if ($oSet->Count() == 1) @@ -225,7 +311,17 @@ function SelectRequestTemplate($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null $iDefaultTemplate = isset($aParameters['template_id']) ? $aParameters['template_id'] : 0; if (MetaModel::IsValidClass('Template')) { - $oSearch = DBObjectSearch::FromOQL(REQUEST_TEMPLATE_QUERY); + $sClass = ComputeClass($aParameters['servicesubcategory_id']); + try + { + $sOql = GetConstant($sClass, 'TEMPLATE_QUERY'); + } + catch(Exception $e) + { + // Backward compatibility + $sOql = REQUEST_TEMPLATE_QUERY; + } + $oSearch = DBObjectSearch::FromOQL($sOql); $oSearch->AllowAllData(); $oSet = new CMDBObjectSet($oSearch, array(), array( 'service_id' => $aParameters['service_id'], @@ -293,7 +389,7 @@ function SelectRequestTemplate($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null } /** - * Displays the form for the final step of the UserRequest creation + * Displays the form for the final step of the ticket creation * @param WebPage $oP The current web page for the form output * @param Organization $oUserOrg The organization of the current user * @param integer $iSvcId The identifier of the service (fall through when there is only one service) @@ -303,12 +399,6 @@ function SelectRequestTemplate($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null */ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null, $iTemplateId = null) { - $oP->add_script( -<<ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); if ($iSvcId != null) { @@ -323,9 +413,6 @@ EOF $aParameters['template_id'] = $iTemplateId; } - // Example: $aList = array('title', 'description', 'impact', 'emergency'); - $aList = explode(',', PORTAL_REQUEST_FORM_ATTRIBUTES); - $sDescription = ''; if (isset($aParameters['template_id']) && ($aParameters['template_id'] != 0)) { @@ -352,17 +439,21 @@ EOF $oServiceSubCategory = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false, true /* allow all data*/); if (is_object($oServiceCategory) && is_object($oServiceSubCategory)) { - $oRequest = new UserRequest(); + $sClass = ComputeClass($oServiceSubCategory->GetKey()); + $oRequest = MetaModel::NewObject($sClass); $oRequest->Set('org_id', $oUserOrg->GetKey()); $oRequest->Set('caller_id', UserRights::GetContactId()); $oRequest->Set('service_id', $aParameters['service_id']); $oRequest->Set('servicesubcategory_id', $aParameters['servicesubcategory_id']); - - $oAttDef = MetaModel::GetAttributeDef('UserRequest', 'service_id'); + + $oAttDef = MetaModel::GetAttributeDef($sClass, 'service_id'); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceCategory->GetName()); - $oAttDef = MetaModel::GetAttributeDef('UserRequest', 'servicesubcategory_id'); + + $oAttDef = MetaModel::GetAttributeDef($sClass, 'servicesubcategory_id'); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceSubCategory->GetName()); + $aList = explode(',', GetConstant($sClass, 'FORM_ATTRIBUTES')); + $iFlags = 0; foreach($aList as $sAttCode) { @@ -377,7 +468,7 @@ EOF foreach($aList as $sAttCode) { $value = ''; - $oAttDef = MetaModel::GetAttributeDef(get_class($oRequest), $sAttCode); + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $iFlags = $oRequest->GetAttributeFlags($sAttCode); if (isset($aParameters[$sAttCode])) { @@ -387,11 +478,9 @@ EOF $sInputId = 'attr_'.$sAttCode; $aFieldsMap[$sAttCode] = $sInputId; - $sValue = "".$oRequest->GetFormElementForField($oP, get_class($oRequest), $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).''; + $sValue = "".$oRequest->GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).''; $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); } -// The log must be requested in the constant PORTAL_REQUEST_FORM_ATTRIBUTES -// $aDetails[] = array('label' => MetaModel::GetLabel('UserRequest', PORTAL_ATTCODE_LOG), 'value' => ''); if (!empty($aTemplateFields)) { @@ -399,7 +488,7 @@ EOF { if (!in_array($sAttCode, $aList)) { - $sValue = $oField->GetFormElement($oP, get_class($oRequest)); + $sValue = $oField->GetFormElement($oP, $sClass); if ($oField->Get('input_type') == 'hidden') { $aHidden[] = $sValue; @@ -412,6 +501,13 @@ EOF } } + $oP->add_script( +<<add_linked_script("../js/json.js"); $oP->add_linked_script("../js/forms-json-utils.js"); $oP->add_linked_script("../js/wizardhelper.js"); @@ -422,13 +518,28 @@ EOF $oP->add("
\n"); $oP->add("

".Dict::S('Portal:DescriptionOfTheRequest')."

\n"); $oP->WizardFormStart('request_form', 4); - //$oP->add("\n"); + $oP->details($aDetails); + // Add hidden fields for known values, enabling dependant attributes to be computed correctly + // + foreach($oRequest->ListChanges() as $sAttCode => $value) + { + if (!in_array($sAttCode, $aList)) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + if ($oAttDef->IsScalar() && $oAttDef->IsWritable()) + { + $sValue = htmlentities($oRequest->Get($sAttCode), ENT_QUOTES, 'UTF-8'); + $oP->add(""); + $aFieldsMap[$sAttCode] = 'attr_'.$sAttCode; + } + } + } + $oAttPlugin = new AttachmentPlugIn(); $oAttPlugin->OnDisplayRelations($oRequest, $oP, true /* edit */); - $oP->DumpHiddenParams($aParameters, $aList); $oP->add(""); $oP->WizardFormButtons(BUTTON_BACK | BUTTON_FINISH | BUTTON_CANCEL); //Back button automatically discarded if on the first page $oP->WizardFormEnd(); @@ -457,7 +568,7 @@ EOF } /** - * Validate the parameters and create the UserRequest object (based on the page's POSTed parameters) + * Validate the parameters and create the ticket object (based on the page's POSTed parameters) * @param WebPage $oP The current web page for the output * @param Organization $oUserOrg The organization of the current user * @return void @@ -487,6 +598,7 @@ function DoCreateRequest($oP, $oUserOrg) // 2) Service Subcategory $oSearch = DBObjectSearch::FromOQL(PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY); + RestrictSubcategories($oSearch); $oSearch->AllowAllData(); // In case the user has the rights on his org only $oSet = new CMDBObjectSet($oSearch, array(), array('service_id' => $aParameters['service_id'], 'id' =>$aParameters['servicesubcategory_id'],'org_id' => $oUserOrg->GetKey() )); if ($oSet->Count() != 1) @@ -496,27 +608,28 @@ function DoCreateRequest($oP, $oUserOrg) } $oServiceSubCategory = $oSet->Fetch(); - $oRequest = new UserRequest(); + $sClass = ComputeClass($oServiceSubCategory->GetKey()); + $oRequest = MetaModel::NewObject($sClass); $oRequest->Set('org_id', $oUserOrg->GetKey()); $oRequest->Set('caller_id', UserRights::GetContactId()); - $aList = array('service_id', 'servicesubcategory_id', 'title', 'description', 'impact'); $oRequest->UpdateObjectFromPostedForm(); if (isset($aParameters['moreinfo'])) { // There is a template, insert it into the description - $oRequest->Set(PORTAL_ATTCODE_LOG, $aParameters['moreinfo']); + $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); + $oRequest->Set($sLogAttCode, $aParameters['moreinfo']); } - if ((PORTAL_ATTCODE_TYPE != '') && (PORTAL_SET_TYPE_FROM != '')) + $sTypeAttCode = GetConstant($sClass, 'TYPE'); + if (($sTypeAttCode != '') && (PORTAL_SET_TYPE_FROM != '')) { - $oRequest->Set(PORTAL_ATTCODE_TYPE, $oServiceSubCategory->Get(PORTAL_SET_TYPE_FROM)); + $oRequest->Set($sTypeAttCode, $oServiceSubCategory->Get(PORTAL_SET_TYPE_FROM)); } - if (MetaModel::IsValidAttCode('UserRequest', 'origin')) + if (MetaModel::IsValidAttCode($sClass, 'origin')) { $oRequest->Set('origin', 'portal'); } - /////$oP->DoUpdateObjectFromPostedForm($oObj); $oAttPlugin = new AttachmentPlugIn(); $oAttPlugin->OnFormSubmit($oRequest); @@ -526,7 +639,8 @@ function DoCreateRequest($oP, $oUserOrg) if (isset($aParameters['template_id'])) { $oTemplate = MetaModel::GetObject('Template', $aParameters['template_id']); - $oRequest->Set('public_log', $oTemplate->GetPostedValuesAsText($oRequest)."\n"); + $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); + $oRequest->Set($sLogAttCode, $oTemplate->GetPostedValuesAsText($oRequest)."\n"); $oRequest->DBInsertNoReload(); $oTemplate->RecordExtraDataFromPostedForm($oRequest); } @@ -534,7 +648,7 @@ function DoCreateRequest($oP, $oUserOrg) { $oRequest->DBInsertNoReload(); } - $oP->add("

".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName(get_class($oRequest)))."

\n"); + $oP->add("

".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName($sClass))."

\n"); //DisplayObject($oP, $oRequest, $oUserOrg); ShowOngoingTickets($oP); @@ -579,6 +693,47 @@ function CreateRequest(WebPage $oP, Organization $oUserOrg) } } +/** + * Helper to display lists (UserRequest, Incident, etc.) + * Adjust the presentation depending on the following cases: + * - no item at all + * - items of one class only + * - items of several classes + */ +function DisplayRequestLists(WebPage $oP, $aClassToSet) +{ + $iNotEmpty = 0; // Count of types for which there are some items to display + foreach ($aClassToSet as $sClass => $oSet) + { + if ($oSet->Count() > 0) + { + $iNotEmpty++; + } + } + if ($iNotEmpty == 0) + { + $oP->p(Dict::S('Portal:NoOpenRequest')); + } + else + { + foreach ($aClassToSet as $sClass => $oSet) + { + if ($iNotEmpty > 1) + { + // Differentiate the sublists + $oP->add("

".MetaModel::GetName($sClass)."

\n"); + } + if ($oSet->Count() > 0) + { + $sZList = GetConstant($sClass, 'LIST_ZLIST'); + $aZList = explode(',', $sZList); + $oP->DisplaySet($oSet, $aZList, Dict::S('Portal:NoOpenRequest')); + } + } + } +} + + /** * Lists all the currently opened User Requests for the current user * @param WebPage $oP The current web page @@ -588,16 +743,19 @@ function ListOpenRequests(WebPage $oP) { $oUserOrg = GetUserOrg(); - $sOQL = 'SELECT UserRequest WHERE org_id = :org_id AND status NOT IN ("closed", "resolved")'; - $oSearch = DBObjectSearch::FromOQL($sOQL); - $iUser = UserRights::GetContactId(); - if ($iUser > 0 && !IsPowerUser()) + $aClassToSet = array(); + foreach (GetTicketClasses() as $sClass) { - $oSearch->AddCondition('caller_id', $iUser); + $sOQL = "SELECT $sClass WHERE org_id = :org_id AND status NOT IN ('closed', 'resolved')"; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $iUser = UserRights::GetContactId(); + if ($iUser > 0 && !IsPowerUser()) + { + $oSearch->AddCondition('caller_id', $iUser); + } + $aClassToSet[$sClass] = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); } - $oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); - $aZList = explode(',', PORTAL_TICKETS_LIST_ZLIST); - $oP->DisplaySet($oSet, $aZList, Dict::S('Portal:NoOpenRequest')); + DisplayRequestLists($oP, $aClassToSet); } /** @@ -609,16 +767,19 @@ function ListResolvedRequests(WebPage $oP) { $oUserOrg = GetUserOrg(); - $sOQL = 'SELECT UserRequest WHERE org_id = :org_id AND status = "resolved"'; - $oSearch = DBObjectSearch::FromOQL($sOQL); - $iUser = UserRights::GetContactId(); - if ($iUser > 0 && !IsPowerUser()) + $aClassToSet = array(); + foreach (GetTicketClasses() as $sClass) { - $oSearch->AddCondition('caller_id', $iUser); + $sOQL = "SELECT $sClass WHERE org_id = :org_id AND status = 'resolved'"; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $iUser = UserRights::GetContactId(); + if ($iUser > 0 && !IsPowerUser()) + { + $oSearch->AddCondition('caller_id', $iUser); + } + $aClassToSet[$sClass] = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); } - $oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); - $aZList = explode(',', PORTAL_TICKETS_LIST_ZLIST); - $oP->DisplaySet($oSet, $aZList, Dict::S('Portal:NoOpenRequest')); + DisplayRequestLists($oP, $aClassToSet); } /** @@ -629,28 +790,32 @@ function ListResolvedRequests(WebPage $oP) function ListClosedTickets(WebPage $oP) { $aAttSpecs = explode(',', PORTAL_TICKETS_SEARCH_CRITERIA); - $aZList = explode(',', PORTAL_TICKETS_CLOSED_ZLIST); - - $oP->DisplaySearchForm('UserRequest', $aAttSpecs, array('operation' => 'show_closed'), 'search_', false /* => not closed */); + $aClasses = GetTicketClasses(); + $sMainClass = reset($aClasses); + $oP->DisplaySearchForm($sMainClass, $aAttSpecs, array('operation' => 'show_closed'), 'search_', false /* => not closed */); $oUserOrg = GetUserOrg(); - // UserRequest - $oSearch = $oP->PostedParamsToFilter('UserRequest', $aAttSpecs, 'search_'); - if(is_null($oSearch)) - { - $oSearch = new DBObjectSearch('UserRequest'); - } - $oSearch->AddCondition('org_id', $oUserOrg->GetKey()); - $oSearch->AddCondition('status', 'closed'); - $iUser = UserRights::GetContactId(); - if ($iUser > 0 && !IsPowerUser()) - { - $oSearch->AddCondition('caller_id', $iUser); - } - $oSet1 = new CMDBObjectSet($oSearch); $oP->add("

".Dict::S('Portal:ClosedRequests')."

\n"); - $oP->DisplaySet($oSet1, $aZList, Dict::S('Portal:NoClosedRequest')); + + $aClassToSet = array(); + foreach (GetTicketClasses() as $sClass) + { + $oSearch = $oP->PostedParamsToFilter($sClass, $aAttSpecs, 'search_'); + if(is_null($oSearch)) + { + $oSearch = new DBObjectSearch($sClass); + } + $oSearch->AddCondition('org_id', $oUserOrg->GetKey()); + $oSearch->AddCondition('status', 'closed'); + $iUser = UserRights::GetContactId(); + if ($iUser > 0 && !IsPowerUser()) + { + $oSearch->AddCondition('caller_id', $iUser); + } + $aClassToSet[$sClass] = new CMDBObjectSet($oSearch); + } + DisplayRequestLists($oP, $aClassToSet); } @@ -663,13 +828,12 @@ function ListClosedTickets(WebPage $oP) */ function DisplayObject($oP, $oObj, $oUserOrg) { - switch(get_class($oObj)) + if (in_array(get_class($oObj), GetTicketClasses())) { - case 'UserRequest': ShowDetailsRequest($oP, $oObj); - break; - - default: + } + else + { throw new Exception("The class ".get_class($oObj)." is not handled through the portal"); } } @@ -683,6 +847,8 @@ function DisplayObject($oP, $oObj, $oUserOrg) function ShowDetailsRequest(WebPage $oP, $oObj) { $sClass = get_class($oObj); + $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); + $sUserCommentAttCode = GetConstant($sClass, 'USER_COMMENT'); $bIsEscalateButton = false; $bIsReopenButton = false; @@ -698,7 +864,7 @@ function ShowDetailsRequest(WebPage $oP, $oObj) case 'frozen': case 'pending': $aEditAtt = array( - PORTAL_ATTCODE_LOG => '????' + $sLogAttCode => '????' ); $bEditAttachments = true; // disabled - $bIsEscalateButton = true; @@ -707,7 +873,7 @@ function ShowDetailsRequest(WebPage $oP, $oObj) case 'escalated_tto': case 'escalated_ttr': $aEditAtt = array( - PORTAL_ATTCODE_LOG => '????' + $sLogAttCode => '????' ); $bEditAttachments = true; break; @@ -717,10 +883,10 @@ function ShowDetailsRequest(WebPage $oP, $oObj) if (array_key_exists('ev_reopen', MetaModel::EnumStimuli($sClass))) { $bIsReopenButton = true; - MakeStimulusForm($oP, $oObj, 'ev_reopen', array(PORTAL_ATTCODE_LOG)); + MakeStimulusForm($oP, $oObj, 'ev_reopen', array($sLogAttCode)); } $bIsCloseButton = true; - MakeStimulusForm($oP, $oObj, 'ev_close', array('user_satisfaction', PORTAL_ATTCODE_COMMENT)); + MakeStimulusForm($oP, $oObj, 'ev_close', array('user_satisfaction', $sUserCommentAttCode)); break; case 'closed': @@ -733,22 +899,13 @@ function ShowDetailsRequest(WebPage $oP, $oObj) // REFACTORISER LA MISE EN FORME $oP->add("

".$oObj->GetIcon()." ".Dict::Format('Portal:TitleRequestDetailsFor_Request', $oObj->GetName())."

\n"); - switch($sClass) + $aAttList = json_decode(GetConstant($sClass, 'DETAILS_ZLIST'), true); + + switch($oObj->GetState()) { - case 'UserRequest': - $aAttList = json_decode(PORTAL_TICKET_DETAILS_ZLIST, true); - - switch($oObj->GetState()) - { - case 'closed': - $aAttList['centered'][] = 'user_satisfaction'; - $aAttList['centered'][] = PORTAL_ATTCODE_COMMENT; - } - break; - - default: - array('col:left'=> array('ref','service_id','servicesubcategory_id','title','description'),'col:right'=> array('status','start_date')); - break; + case 'closed': + $aAttList['centered'][] = 'user_satisfaction'; + $aAttList['centered'][] = $sUserCommentAttCode; } // Remove the edited attribute from the shown attributes @@ -841,7 +998,7 @@ EOF } foreach($aEditFields as $sAttCode => $aFieldSpec) { - if ($sAttCode == PORTAL_ATTCODE_LOG) + if ($sAttCode == $sLogAttCode) { // Skip, the public log will be displayed below the buttons continue; @@ -881,17 +1038,17 @@ EOF $oP->add(''); $oP->add(''); $oP->add(''); @@ -1031,7 +1188,9 @@ try ApplicationContext::SetUrlMakerClass('MyPortalURLMaker'); - if (!class_exists('UserRequest')) + $aClasses = explode(',', MetaModel::GetConfig()->Get('portal_tickets')); + $sMainClass = trim(reset($aClasses)); + if (!class_exists($sMainClass)) { $oP = new WebPage(Dict::S('Portal:Title')); $oP->p(dict::Format('Portal:NoRequestMgmt', UserRights::GetUserFriendlyName())); @@ -1074,7 +1233,7 @@ try case 'details': $oP->set_title(Dict::S('Portal:TitleDetailsFor_Request')); DisplayMainMenu($oP); - $oObj = $oP->FindObjectFromArgs(array('UserRequest')); + $oObj = $oP->FindObjectFromArgs(GetTicketClasses()); DisplayObject($oP, $oObj, $oUserOrg); break; @@ -1083,16 +1242,12 @@ try DisplayMainMenu($oP); if (!MetaModel::DBIsReadOnly()) { - $oObj = $oP->FindObjectFromArgs(array('UserRequest')); - switch(get_class($oObj)) - { - case 'UserRequest': - $aAttList = array(PORTAL_ATTCODE_LOG, 'user_satisfaction', PORTAL_ATTCODE_COMMENT); - break; - - default: - throw new Exception("Implementation issue: unexpected class '".get_class($oObj)."'"); - } + $oObj = $oP->FindObjectFromArgs(GetTicketClasses()); + $aAttList = array( + GetConstant(get_class($oObj), 'PUBLIC_LOG'), + 'user_satisfaction', + GetConstant(get_class($oObj), 'USER_COMMENT') + ); try { $oP->DoUpdateObjectFromPostedForm($oObj, $aAttList); diff --git a/portal/readme.txt b/portal/readme.txt new file mode 100644 index 000000000..08108fe6b --- /dev/null +++ b/portal/readme.txt @@ -0,0 +1,69 @@ + +--- Customization of the portal + +This is the way it is working now and is highly subject to change... + + +Configuration (itop-config.php) +=============================== +portal_tickets: CSV value to specify which ticket classes are enabled (default to 'UserRequest') + + +Common constants (XML) +====================== +PORTAL_POWER_USER_PROFILE: Name of the profile that determines who can see the ticket of her organization (not only the tickets she is caller for) +PORTAL_SERVICECATEGORY_QUERY: OQL to list the services (parameters available: org_id) +PORTAL_SERVICE_SUBCATEGORY_QUERY: OQL to list the service subcategories (parameters available: org_id, svc_id) +PORTAL_VALIDATE_SERVICECATEGORY_QUERY: OQL to check the service again (security against malicious HTTP POSTs) +PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY: OQL to check the service again (security against malicious HTTP POSTs) +PORTAL_ALL_PARAMS: parameters that the wizard will kindly propagate through its pages (mixing should not be a problem, default value could be cleaned a little...) +PORTAL_SET_TYPE_FROM: attribute of the class ServiceSubcategory determining the request type +PORTAL_TYPE_TO_CLASS: optional mapping from the request types to ticket classes +PORTAL_TICKETS_SEARCH_CRITERIA: list of search criteria for closed tickets + + +Caution: Hardcoded stuff +======================== +Classes Service and ServiceSubcategory +A user can update a ticket (new/assigned) +A user can close a ticket (resolved) (user_satisfaction is hardcoded though user_comment is not) + + +Constants depending on the class of ticket +========================================== +For each ticket class enabled, you will have to define these constants: + +PORTAL__PUBLIC_LOG: name of the public log attribute +PORTAL__USER_COMMENT: name of the user comment attribute (legacy, used to be user_commmmment) +PORTAL__FORM_ATTRIBUTES: attributes proposed to the end-user in the edition form +PORTAL__TYPE: optional attribute to be set with the value of "request type" +PORTAL__LIST_ZLIST: list of attribute displayed in the lists (opened and resolved) +PORTAL__CLOSED_ZLIST: list of attribute displayed in the list of closed tickets +PORTAL__DETAILS_ZLIST: selection and presentation of attributes in the page that shows their details + + +How to add a type of ticket (example: Incident) +=============================================== +1) Add it to the list of supported tickets classes: itop-config.php/portal_tickets +2) Define PORTAL_SET_TYPE_FROM (if not already done) as the attribute of ServiceSubcategory, that will define the request type, depending on the user selection +3) Map the different values of this request type (in class ServiceSubcategory) to the supported ticket classes +YOU MUST MAKE SURE THAT ANY OF THE VALUE HAS A MAPPING SO AS TO EXCLUDE SUBCATEGORIES IF THE CORRESPONDING CLASS ARE NOT ENABLED IN THE CONFIG. +4) Make sure that the queries PORTAL_SERVICE_SUBCATEGORY_QUERY and PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY will not exclude the expected type +5) Define the various constants for this class (PORTAL__XXXX). +6) Adjust PORTAL_TICKETS_SEARCH_CRITERIA. Those criteria are common to all types of tickets. Giving too many criteria can lead to confusion. +7) Test, test and re-test!!! + + +How to copy the request type to the ticket +========================================== +1) Define PORTAL_SET_TYPE_FROM (if not already done) as the attribute of ServiceSubcategory, that will define the request type, depending on the user selection +2) Define PORTAL__TYPE as the tiket attribute code to which the request type will be copied as is. There is no mapping. + + +Behavior of the lists when handling several types of tickets +============================================================ +There are three lists: opened tickets, resolved tickets and closed tickets. +The following explanation applies to any of those lists. + * If no item has been found, one single message is displayed (no request of this category). + * If a number of items of only one category have been found, the list is displayed as is. + * Otherwise, there are several types of tickets to display. Each sub-list is preceeded by the name of the corresponding class.
'); - if (isset($aEditFields[PORTAL_ATTCODE_LOG])) + if (isset($aEditFields[$sLogAttCode])) { $oP->add("
"); - $oP->add('

'.$aEditFields[PORTAL_ATTCODE_LOG]['label'].'

'); - $oP->add($aEditFields[PORTAL_ATTCODE_LOG]['value']); + $oP->add('

'.$aEditFields[$sLogAttCode]['label'].'

'); + $oP->add($aEditFields[$sLogAttCode]['value']); $oP->add('
'); } else { - $oP->add('

'.MetaModel::GetLabel($sClass, PORTAL_ATTCODE_LOG).'

'); - $oP->add($oObj->GetAsHTML(PORTAL_ATTCODE_LOG)); + $oP->add('

'.MetaModel::GetLabel($sClass, $sLogAttCode).'

'); + $oP->add($oObj->GetAsHTML($sLogAttCode)); } $oP->add('