/** * iTop User Portal main page * * @copyright Copyright (C) 2010-2013 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ require_once('../approot.inc.php'); 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 protect the portal against malicious usages * Throws an exception if the current user is not allowed to view the object details */ function ValidateObject($oObject) { if (IsPowerUser()) { $sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_POWERUSER_QUERY'; } else { $sValidationDefine = 'PORTAL_'.strtoupper(get_class($oObject)).'_DISPLAY_QUERY'; } if (defined($sValidationDefine)) { $sValidationOql = constant($sValidationDefine); $oSearch = DBObjectSearch::FromOQL($sValidationOql); $oSearch->AddCondition('id', $oObject->GetKey()); if ($iUser = UserRights::GetContactId()) { $oContact = MetaModel::GetObject('Contact', $iUser); $aArgs = $oContact->ToArgs('contact'); } else { $aArgs = array(); } $oSet = new DBObjectSet($oSearch, array(), $aArgs); if ($oSet->Count() == 0) { throw new SecurityException('You are not allowed to access the object '.get_class($oObject).'::'.$oObject->GetKey()); } } } /** * 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 = (PORTAL_TYPE_TO_CLASS == '') ? array() : 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 * @return void */ function DisplayMainMenu(WebPage $oP) { $oP->AddMenuButton('showongoing', 'Portal:ShowOngoing', '../portal/index.php?operation=show_ongoing'); $oP->AddMenuButton('newrequest', 'Portal:CreateNewRequest', '../portal/index.php?operation=create_request'); $oP->AddMenuButton('showclosed', 'Portal:ShowClosed', '../portal/index.php?operation=show_closed'); if (UserRights::CanChangePassword()) { $oP->AddMenuButton('change_pwd', 'Portal:ChangeMyPassword', '../portal/index.php?loginop=change_pwd'); } } /** * Displays the current tickets * @param WebPage $oP The current web page * @return void */ function ShowOngoingTickets(WebPage $oP) { $oP->add("
\n"); $oP->add("

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

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

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

\n"); ListResolvedRequests($oP); $oP->add("
\n"); } /** * Displays the closed tickets * @param WebPage $oP The current web page * @return void */ function ShowClosedTickets(WebPage $oP) { $oP->add("
\n"); //$oP->add("

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

\n"); ListClosedTickets($oP); $oP->add("
\n"); } /** * Displays the form to select a Service Category Id (among the valid ones for the specified user Organization) * @param WebPage $oP Web page for the form output * @param Organization $oUserOrg The organization of the current user * @return void */ function SelectServiceCategory($oP, $oUserOrg) { $aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); $oSearch = DBObjectSearch::FromOQL(PORTAL_SERVICECATEGORY_QUERY); $oSearch->AllowAllData(); // In case the user has the rights on his org only $oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey())); if ($oSet->Count() == 1) { $oService = $oSet->Fetch(); $iSvcCategory = $oService->GetKey(); // Only one Category, skip this step in the wizard SelectServiceSubCategory($oP, $oUserOrg, $iSvcCategory); } else { $oP->add("
\n"); $oP->WizardFormStart('request_wizard', 1); $oP->add("

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

\n"); $oP->add("\n"); while($oService = $oSet->Fetch()) { $id = $oService->GetKey(); $sChecked = ""; if (isset($aParameters['service_id']) && ($id == $aParameters['service_id'])) { $sChecked = "checked"; } $oP->p(""); } $oP->add("

"); $oP->p("

".$oService->GetAsHTML('description')."

\n"); $oP->DumpHiddenParams($aParameters, array('service_id')); $oP->add(""); $oP->WizardFormButtons(BUTTON_NEXT | BUTTON_CANCEL); // NO back button since it's the first step of the Wizard $oP->WizardFormEnd(); $oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectOneService')); $oP->add("
\n"); } } /** * Displays the form to select a Service Subcategory Id (among the valid ones for the specified user Organization) * and based on the page's parameter 'service_id' * @param WebPage $oP Web page for the form output * @param Organization $oUserOrg The organization of the current user * @param $iSvcId Id of the selected service in case of pass-through (when there is only one service) * @return void */ function SelectServiceSubCategory($oP, $oUserOrg, $iSvcId = null) { $aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); if ($iSvcId == null) { $iSvcId = $aParameters['service_id']; } else { $aParameters['service_id'] = $iSvcId; } $iDefaultSubSvcId = isset($aParameters['servicesubcategory_id']) ? $aParameters['servicesubcategory_id'] : 0; $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) { // Only one sub service, skip this step of the wizard $oSubService = $oSet->Fetch(); $iSubSvdId = $oSubService->GetKey(); SelectRequestTemplate($oP, $oUserOrg, $iSvcId, $iSubSvdId); } else { $oServiceCategory = MetaModel::GetObject('Service', $iSvcId, false, true /* allow all data*/); if (is_object($oServiceCategory)) { $oP->add("
\n"); $oP->add("

".Dict::Format('Portal:SelectSubcategoryFrom_Service', $oServiceCategory->GetName())."

\n"); $oP->WizardFormStart('request_wizard', $iDefaultWizNext); $oP->add("\n"); while($oSubService = $oSet->Fetch()) { $id = $oSubService->GetKey(); $sChecked = ""; if ($id == $iDefaultSubSvcId) { $sChecked = "checked"; } $oP->add(""); $oP->add(""); $oP->add(""); $oP->add(""); } $oP->add("
"); $oP->add("

"); $oP->add("
"); $oP->add("

"); $oP->add("

".$oSubService->GetAsHTML('description')."

"); $oP->add("
\n"); $oP->DumpHiddenParams($aParameters, array('servicesubcategory_id')); $oP->add(""); $oP->WizardFormButtons(BUTTON_BACK | BUTTON_NEXT | BUTTON_CANCEL); //Back button automatically discarded if on the first page $oP->WizardFormEnd(); $oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectAServiceSubCategory')); $oP->add("
\n"); } else { $oP->p("Error: Invalid Service: id = $iSvcId"); } } } /** * Displays the form to select a Template * @param WebPage $oP Web page for the form output * @param Organization $oUserOrg The organization of the current user * @param $iSvcId Id of the selected service in case of pass-through (when there is only one service) * @param integer $iSubSvcId The identifier of the sub-service (fall through when there is only one sub-service) * @return void */ function SelectRequestTemplate($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null) { $aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); if (!is_null($iSvcId)) { $aParameters['service_id'] = $iSvcId; } if (!is_null($iSubSvcId)) { $aParameters['servicesubcategory_id'] = $iSubSvcId; } $iDefaultTemplate = isset($aParameters['template_id']) ? $aParameters['template_id'] : 0; if (MetaModel::IsValidClass('Template')) { $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'], 'servicesubcategory_id' => $aParameters['servicesubcategory_id'] )); if ($oSet->Count() == 0) { RequestCreationForm($oP, $oUserOrg, $aParameters['service_id'], $aParameters['servicesubcategory_id'], 0); return; } elseif ($oSet->Count() == 1) { $oTemplate = $oSet->Fetch(); $iTemplateId = $oTemplate->GetKey(); RequestCreationForm($oP, $oUserOrg, $aParameters['service_id'], $aParameters['servicesubcategory_id'], $iTemplateId); return; } $oServiceSubCategory = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false); if (is_object($oServiceSubCategory)) { $oP->add("
\n"); $oP->add("

".Dict::Format('Portal:SelectRequestTemplate', $oServiceSubCategory->GetName())."

\n"); $oP->WizardFormStart('request_wizard', 3); $oP->add("\n"); while($oTemplate = $oSet->Fetch()) { $id = $oTemplate->GetKey(); $sChecked = ""; if ($id == $iDefaultTemplate) { $sChecked = "checked"; } $oP->add(""); $oP->add(""); $oP->add(""); $oP->add(""); } $oP->add("
"); $oP->p(""); $oP->add(""); $oP->p(""); $oP->p($oTemplate->GetAsHTML('description')); $oP->add("
\n"); $oP->DumpHiddenParams($aParameters, array('template_id')); $oP->add(""); $oP->WizardFormButtons(BUTTON_BACK | BUTTON_NEXT | BUTTON_CANCEL); //Back button automatically discarded if on the first page $oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectATemplate')); $oP->WizardFormEnd(); $oP->add("
\n"); } else { $oP->p("Error: Invalid servicesubcategory_id = ".$aParameters['servicesubcategory_id']); } } else { RequestCreationForm($oP, $oUserOrg, $aParameters['service_id'], $aParameters['servicesubcategory_id']); return; } } /** * 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) * @param integer $iSubSvcId The identifier of the sub-service (fall through when there is only one sub-service) * @param integer $iTemplateId The identifier of the template (fall through when there is only one template) * @return void */ function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null, $iTemplateId = null) { $aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); if (!is_null($iSvcId)) { $aParameters['service_id'] = $iSvcId; } if (!is_null($iSubSvcId)) { $aParameters['servicesubcategory_id'] = $iSubSvcId; } if (!is_null($iTemplateId)) { $aParameters['template_id'] = $iTemplateId; } $sDescription = ''; if (isset($aParameters['template_id']) && ($aParameters['template_id'] != 0)) { $aTemplateFields = array(); $oTemplate = MetaModel::GetObject('Template', $aParameters['template_id'], false); if (is_object($oTemplate)) { $oFieldSearch = DBObjectSearch::FromOQL('SELECT TemplateField WHERE template_id = :template_id'); $oFieldSearch->AllowAllData(); $oFieldSet = new DBObjectSet($oFieldSearch, array('order' => true), array('template_id' => $oTemplate->GetKey())); while($oField = $oFieldSet->Fetch()) { $sAttCode = $oField->Get('code'); if (isset($aParameters[$sAttCode])) { $oField->Set('initial_value', $aParameters[$sAttCode]); } $aTemplateFields[$sAttCode] = $oField; } } } $oServiceCategory = MetaModel::GetObject('Service', $aParameters['service_id'], false, true /* allow all data*/); $oServiceSubCategory = MetaModel::GetObject('ServiceSubcategory', $aParameters['servicesubcategory_id'], false, true /* allow all data*/); if (is_object($oServiceCategory) && is_object($oServiceSubCategory)) { $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($sClass, 'service_id'); $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $oServiceCategory->GetName()); $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) { $value = ''; if (isset($aParameters[$sAttCode])) { $value = $aParameters[$sAttCode]; $oRequest->Set($sAttCode, $value); } } $aFieldsMap = array(); foreach($aList as $sAttCode) { $value = ''; $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $iFlags = $oRequest->GetAttributeFlags($sAttCode); if (isset($aParameters[$sAttCode])) { $value = $aParameters[$sAttCode]; } $aArgs = array('this' => $oRequest); $sInputId = 'attr_'.$sAttCode; $aFieldsMap[$sAttCode] = $sInputId; $sValue = "".$oRequest->GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).''; $aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sValue); } $aHidden = array(); if (!empty($aTemplateFields)) { foreach ($aTemplateFields as $sAttCode => $oField) { $sValue = $oField->GetFormElement($oP, $sClass); if ($oField->Get('input_type') == 'hidden') { $aHidden[] = $sValue; } else { $aDetails[] = array('label' => $oField->GetAsHTML('label'), 'value' => $sValue); } } } $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"); $oP->add_linked_script("../js/wizard.utils.js"); $oP->add_linked_script("../js/linkswidget.js"); $oP->add_linked_script("../js/extkeywidget.js"); $oP->add_linked_script("../js/jquery.blockUI.js"); $oP->add("
\n"); $oP->add("

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

\n"); $oP->WizardFormStart('request_form', 4); $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; } } } if (isset($aParameters['template_id']) && ($aParameters['template_id'] != 0)) { $oP->add(""); } $oP->add(implode("\n", $aHidden)); $oAttPlugin = new AttachmentPlugIn(); $oAttPlugin->OnDisplayRelations($oRequest, $oP, true /* edit */); $oP->add(""); $oP->WizardFormButtons(BUTTON_BACK | BUTTON_FINISH | BUTTON_CANCEL); //Back button automatically discarded if on the first page $oP->WizardFormEnd(); $oP->add("
\n"); $iFieldsCount = count($aFieldsMap); $sJsonFieldsMap = json_encode($aFieldsMap); $oP->add_ready_script( <<add_ready_script(InlineImage::EnableCKEditorImageUpload($oRequest, utils::GetUploadTempId($oP->GetTransactionId()))); } else { // User not authorized to use this service ? //ShowOngoingTickets($oP); } } /** * 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 */ function DoCreateRequest($oP, $oUserOrg) { $aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS.',template_id'); $sTransactionId = utils::ReadPostedParam('transaction_id', ''); if (!utils::IsTransactionValid($sTransactionId)) { $oP->add("

".Dict::S('UI:Error:ObjectAlreadyCreated')."

\n"); //ShowOngoingTickets($oP); return; } // Validate the parameters // 1) ServiceCategory $oSearch = DBObjectSearch::FromOQL(PORTAL_VALIDATE_SERVICECATEGORY_QUERY); $oSearch->AllowAllData(); // In case the user has the rights on his org only $oSet = new CMDBObjectSet($oSearch, array(), array('id' => $aParameters['service_id'], 'org_id' => $oUserOrg->GetKey())); if ($oSet->Count() != 1) { // Invalid service for the current user ! throw new Exception("Invalid Service Category: id={$aParameters['service_id']} - count: ".$oSet->Count()); } $oServiceCategory = $oSet->Fetch(); // 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) { // Invalid subcategory throw new Exception("Invalid ServiceSubcategory: id={$aParameters['servicesubcategory_id']} for service category ".$oServiceCategory->GetName()."({$aParameters['service_id']}) - count: ".$oSet->Count()); } $oServiceSubCategory = $oSet->Fetch(); $sClass = ComputeClass($oServiceSubCategory->GetKey()); $oRequest = MetaModel::NewObject($sClass); $aAttList = array_merge(explode(',', GetConstant($sClass, 'FORM_ATTRIBUTES')), array('service_id', 'servicesubcategory_id')); $oRequest->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList); $oRequest->Set('org_id', $oUserOrg->GetKey()); $oRequest->Set('caller_id', UserRights::GetContactId()); if (isset($aParameters['moreinfo'])) { // There is a template, insert it into the description $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); $oRequest->Set($sLogAttCode, $aParameters['moreinfo']); } $sTypeAttCode = GetConstant($sClass, 'TYPE'); if (($sTypeAttCode != '') && (PORTAL_SET_TYPE_FROM != '')) { $oRequest->Set($sTypeAttCode, $oServiceSubCategory->Get(PORTAL_SET_TYPE_FROM)); } if (MetaModel::IsValidAttCode($sClass, 'origin')) { $oRequest->Set('origin', 'portal'); } $oAttPlugin = new AttachmentPlugIn(); $oAttPlugin->OnFormSubmit($oRequest); list($bRes, $aIssues) = $oRequest->CheckToWrite(); if ($bRes) { if (isset($aParameters['template_id'])) { $oTemplate = MetaModel::GetObject('Template', $aParameters['template_id']); $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); $oRequest->Set($sLogAttCode, $oTemplate->GetPostedValuesAsText($oRequest)."\n"); $oRequest->DBInsertNoReload(); $oTemplate->RecordExtraDataFromPostedForm($oRequest); } else { $oRequest->DBInsertNoReload(); } $oP->add("

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

\n"); //DisplayObject($oP, $oRequest, $oUserOrg); ShowOngoingTickets($oP); } else { RequestCreationForm($oP, $oUserOrg); $sIssueDesc = Dict::Format('UI:ObjectCouldNotBeWritten', implode(', ', $aIssues)); $oP->add_ready_script("alert('".addslashes($sIssueDesc)."');"); } } /** * Prompts the user for creating a new request * @param WebPage $oP The current web page * @return void */ function CreateRequest(WebPage $oP, Organization $oUserOrg) { switch($oP->GetWizardStep()) { case 0: default: SelectServiceCategory($oP, $oUserOrg); break; case 1: SelectServiceSubCategory($oP, $oUserOrg); break; case 2: SelectRequestTemplate($oP, $oUserOrg); break; case 3: RequestCreationForm($oP, $oUserOrg); break; case 4: DoCreateRequest($oP, $oUserOrg); break; } } /** * 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 * @return void */ function ListOpenRequests(WebPage $oP) { $oUserOrg = GetUserOrg(); $aClassToSet = array(); $iUser = UserRights::GetContactId(); $oContact = MetaModel::GetObject('Contact', UserRights::GetContactId()); foreach (GetTicketClasses() as $sClass) { if (IsPowerUser()) { $sValidationDefine = 'PORTAL_'.strtoupper($sClass).'_DISPLAY_POWERUSER_QUERY'; } else { $sValidationDefine = 'PORTAL_'.strtoupper($sClass).'_DISPLAY_QUERY'; } if (defined($sValidationDefine)) { $sOQL = constant($sValidationDefine); $oSearch = DBObjectSearch::FromOQL($sOQL); $sOQLCondition = $oSearch->GetClassAlias().".status NOT IN ('closed', 'resolved')"; $oExpr = Expression::FromOQL($sOQLCondition); $oSearch->AddConditionExpression($oExpr); } else { $sOQL = "SELECT $sClass WHERE org_id = :org_id AND status NOT IN ('closed', 'resolved')"; $oSearch = DBObjectSearch::FromOQL($sOQL); if ($iUser > 0 && !IsPowerUser()) { $oSearch->AddCondition('caller_id', $iUser); } } $aClassToSet[$sClass] = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey(), 'caller_id' => $iUser, 'contact' => $oContact)); } DisplayRequestLists($oP, $aClassToSet); } /** * Lists all the currently resolved (not yet closed) User Requests for the current user * @param WebPage $oP The current web page * @return void */ function ListResolvedRequests(WebPage $oP) { $oUserOrg = GetUserOrg(); $aClassToSet = array(); foreach (GetTicketClasses() as $sClass) { $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())); } DisplayRequestLists($oP, $aClassToSet); } /** * Lists all the currently closed tickets * @param WebPage $oP The current web page * @return void */ function ListClosedTickets(WebPage $oP) { $aAttSpecs = explode(',', PORTAL_TICKETS_SEARCH_CRITERIA); // Remove the caller_id form the search criteria if the user is not a Portal Power User // since the user is only allowed to see her/his own tickets foreach($aAttSpecs as $idx => $sAttCode) { if (($sAttCode == 'caller_id') && !IsPowerUser()) { unset($aAttSpecs[$idx]); } } $aClasses = GetTicketClasses(); $sMainClass = reset($aClasses); $oP->DisplaySearchForm($sMainClass, $aAttSpecs, array('operation' => 'show_closed'), 'search_', false /* => not closed */); $oUserOrg = GetUserOrg(); $oP->add("

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

\n"); $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); } /** * Display an object - to be customized * @param WebPage $oP The current web page * @param Object $oObj Any kind of object * @param Object $oUserOrg The organization of the logged in user * @return void */ function DisplayObject($oP, $oObj, $oUserOrg) { if (in_array(get_class($oObj), GetTicketClasses())) { ShowDetailsRequest($oP, $oObj); } else { throw new Exception("The class ".get_class($oObj)." is not handled through the portal"); } } /** * Displays the details of a request * @param WebPage $oP The current web page * @param Object $oObj The target object * @return void */ function ShowDetailsRequest(WebPage $oP, $oObj) { $sClass = get_class($oObj); $sLogAttCode = GetConstant($sClass, 'PUBLIC_LOG'); $sUserCommentAttCode = GetConstant($sClass, 'USER_COMMENT'); $bIsReopenButton = false; $bIsCloseButton = false; $bIsEscalateButton = false; $bEditAttachments = false; $aEditAtt = array(); // List of attributes editable in the main form if (!MetaModel::DBIsReadOnly()) { switch($oObj->GetState()) { case 'resolved': $aEditAtt = array(); $aTransitions = $oObj->EnumTransitions(); $oSet = DBObjectSet::FromObject($oObj); // Add the "Reopen" button if this is valid action if (array_key_exists('ev_reopen', $aTransitions) && UserRights::IsStimulusAllowed($sClass, 'ev_reopen', $oSet)) { $bIsReopenButton = true; MakeStimulusForm($oP, $oObj, 'ev_reopen', array($sLogAttCode)); } // Add the "Close" button if this is valid action if (array_key_exists('ev_close', $aTransitions) && UserRights::IsStimulusAllowed($sClass, 'ev_close', $oSet)) { $bIsCloseButton = true; MakeStimulusForm($oP, $oObj, 'ev_close', array('user_satisfaction', $sUserCommentAttCode)); } break; case 'closed': // By convention 'closed' is the final state of a ticket and nothing can be done in such a state break; default: // In all other states, the only possible action is to update the ticket (both the case log and the attachments) // This update is possible only if the case log field is not read-only or hidden in the current state $iFlags = $oObj->GetAttributeFlags($sLogAttCode); $bReadOnly = (($iFlags & (OPT_ATT_READONLY | OPT_ATT_HIDDEN)) != 0); if ($bReadOnly) { $aEditAtt = array(); $bEditAttachments = false; } else { $aEditAtt = array( $sLogAttCode => '????' ); $bEditAttachments = true; } break; } } // REFACTORISER LA MISE EN FORME $oP->add("

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

\n"); $aAttList = json_decode(GetConstant($sClass, 'DETAILS_ZLIST'), true); switch($oObj->GetState()) { case 'closed': $aAttList['centered'][] = 'user_satisfaction'; $aAttList['centered'][] = $sUserCommentAttCode; } // Remove the edited attribute from the shown attributes // foreach($aEditAtt as $sAttCode => $foo) { foreach($aAttList as $col => $aColumn) { if (in_array($sAttCode, $aColumn)) { if(($index = array_search($sAttCode, $aColumn)) !== false) { unset($aAttList[$col][$index]); } } } } $oP->add("
\n"); $oP->WizardFormStart('request_form', null); $oP->add('
'); $oP->add(''); $oP->add(''); $oP->add(''); $oP->add(''); $oP->add(''); if (array_key_exists('centered', $aAttList)) { $oP->add(''); $oP->add(''); $oP->add(''); } // REFACTORISER $oP->add(''); $oP->add(''); $oP->add(''); $oP->add(''); $oP->add('
'); $oP->DisplayObjectDetails($oObj, $aAttList['col:left']); $oP->add(''); $oP->DisplayObjectDetails($oObj, $aAttList['col:right']); $oP->add('
'); $oP->DisplayObjectDetails($oObj, $aAttList['centered']); $oP->add('
'); $oAttPlugin = new AttachmentPlugIn(); if ($bEditAttachments) { $oAttPlugin->EnableDelete(false); $oAttPlugin->OnDisplayRelations($oObj, $oP, true /* edit */); } else { $oAttPlugin->OnDisplayRelations($oObj, $oP, false /* read */); } $oP->add('
'); //$oP->add("
\n"); //$oP->add(''); $oP->add(''); $oP->add(''); $oP->add(''); $oP->add(''); $oP->add('
'); //$oP->add("

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

\n"); $oP->add(""); $oP->add("GetKey()."\">"); $oP->add(""); $oP->add("\n"); $oP->add_script( << $foo) { $sValue = $oObj->Get($sAttCode); $sDisplayValue = $oObj->GetEditValue($sAttCode); $aArgs = array('this' => $oObj, 'formPrefix' => ''); $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); $sInputId = 'input_'.$sAttCode; $sHTMLValue = "".cmdbAbstractObject::GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', 0 /*$iFlags*/, $aArgs).''; $aEditFields[$sAttCode] = array( 'label' => MetaModel::GetLabel($sClass, $sAttCode), 'value' => $sHTMLValue ); } foreach($aEditFields as $sAttCode => $aFieldSpec) { if ($sAttCode == $sLogAttCode) { // Skip, the public log will be displayed below the buttons continue; } $oP->add("
"); $oP->add('

'.$aFieldSpec['label'].'

'); $oP->add($aFieldSpec['value']); $oP->add('
'); } if($bIsReopenButton) { $sStimulusCode = 'ev_reopen'; $sTitle = addslashes(Dict::S('Portal:Button:ReopenTicket')); $sOk = addslashes(Dict::S('UI:Button:Ok')); $oP->p(''); } if($bIsCloseButton) { $sStimulusCode = 'ev_close'; $sTitle = addslashes(Dict::S('Portal:Button:CloseTicket')); $sOk = addslashes(Dict::S('UI:Button:Ok')); $oP->p(''); } elseif (count($aEditAtt) > 0) { $oP->p(''); } if ($bIsEscalateButton) { $sStimulusCode = 'ev_timeout'; $oP->p(''); } $oP->add('
'); if (isset($aEditFields[$sLogAttCode])) { $oP->add("
"); $oP->add('

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

'); $oP->add($aEditFields[$sLogAttCode]['value']); $oP->add('
'); // Replace the text area with CKEditor // To change the default settings of the editor, // a) edit the file /js/ckeditor/config.js // b) or override some of the configuration settings, using the second parameter of ckeditor() $aConfig = array(); $sLanguage = strtolower(trim(UserRights::GetUserLanguage())); $aConfig['language'] = $sLanguage; $aConfig['contentsLanguage'] = $sLanguage; $aConfig['extraPlugins'] = 'disabler'; $aConfig['placeholder'] = Dict::S('UI:CaseLogTypeYourTextHere'); $sConfigJS = json_encode($aConfig); $oP->add_ready_script("$('#input_$sLogAttCode').ckeditor(function() { /* callback code */ }, $sConfigJS);"); // Transform $iId into a CKEdit } else { $oP->add('

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

'); $oP->add($oObj->GetAsHTML($sLogAttCode)); } $oP->add('
'); $oP->add(''); // Enable the button to upload images (or cut and paste of images) $oP->add_ready_script(InlineImage::EnableCKEditorImageUpload($oObj, utils::GetUploadTempId($oP->GetTransactionId()))); $oP->WizardFormEnd(); $oP->add(''); } /** * Create form to apply a stimulus * @param WebPage $oP The current web page * @param Object $oObj The target object * @param String $sStimulusCode Stimulus that will be applied * @param Array $aEditAtt List of attributes to edit * @return void */ function MakeStimulusForm(WebPage $oP, $oObj, $sStimulusCode, $aEditAtt) { static $bHasStimulusForm = false; $sDialogId = $sStimulusCode."_dialog"; $sFormId = $sStimulusCode."_form"; $sCancelButtonLabel = Dict::S('UI:Button:Cancel'); $oP->add('
'); if (!$bHasStimulusForm) { $bHasStimulusForm = true; $oP->add_script( << Can fail if (is_object($oContact)) { $oOrg = MetaModel::GetObject('Organization', $oContact->Get('org_id'), false); // false => can fail } else { throw new Exception(Dict::S('Portal:ErrorNoContactForThisUser')); } return $oOrg; } /** * Determine if the current user can be considered as being a portal power user */ function IsPowerUSer() { $iUserID = UserRights::GetUserId(); $sOQLprofile = "SELECT URP_Profiles AS p JOIN URP_UserProfile AS up ON up.profileid=p.id WHERE up.userid = :user AND p.name = :profile"; $oProfileSet = new DBObjectSet( DBObjectSearch::FromOQL($sOQLprofile), array(), array( 'user' => $iUserID, 'profile' => PORTAL_POWER_USER_PROFILE, ) ); $bRes = ($oProfileSet->count() > 0); return $bRes; } /////////////////////////////////////////////////////////////////////////////// // // Main program // /////////////////////////////////////////////////////////////////////////////// try { require_once(APPROOT.'/application/startup.inc.php'); require_once(APPROOT.'/application/portalwebpage.class.inc.php'); $oAppContext = new ApplicationContext(); $sOperation = utils::ReadParam('operation', ''); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(false /* bMustBeAdmin */, true /* IsAllowedToPortalUsers */); // Check user rights and prompt if needed ApplicationContext::SetUrlMakerClass('MyPortalURLMaker'); utils::InitArchiveMode(); $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())); } else { $oUserOrg = GetUserOrg(); $sCode = $oUserOrg->Get('code'); $sAlternateStylesheet = ''; if (@file_exists("./$sCode/portal.css")) { $sAlternateStylesheet = "$sCode"; } $oP = new PortalWebPage(Dict::S('Portal:Title'), $sAlternateStylesheet); $oP->EnableDisconnectButton(utils::CanLogOff()); $oP->SetWelcomeMessage(Dict::Format('Portal:WelcomeUserOrg', UserRights::GetUserFriendlyName(), $oUserOrg->GetName())); if (is_object($oUserOrg)) { switch($sOperation) { case 'show_closed': $oP->set_title(Dict::S('Portal:ShowClosed')); DisplayMainMenu($oP); ShowClosedTickets($oP); break; case 'create_request': $oP->set_title(Dict::S('Portal:CreateNewRequest')); DisplayMainMenu($oP); if (!MetaModel::DBIsReadOnly()) { CreateRequest($oP, $oUserOrg); } break; case 'details': $oP->set_title(Dict::S('Portal:TitleDetailsFor_Request')); DisplayMainMenu($oP); $oObj = $oP->FindObjectFromArgs(GetTicketClasses()); ValidateObject($oObj); DisplayObject($oP, $oObj, $oUserOrg); break; case 'update_request': $oP->set_title(Dict::S('Portal:TitleDetailsFor_Request')); DisplayMainMenu($oP); if (!MetaModel::DBIsReadOnly()) { $oObj = $oP->FindObjectFromArgs(GetTicketClasses()); ValidateObject($oObj); $aAttList = array( GetConstant(get_class($oObj), 'PUBLIC_LOG'), 'user_satisfaction', GetConstant(get_class($oObj), 'USER_COMMENT') ); try { $oP->DoUpdateObjectFromPostedForm($oObj, $aAttList); $oObj->Reload(); // Make sure the object is in good shape to be displayed } catch(TransactionException $e) { $oP->add("

".Dict::S('UI:Error:ObjectAlreadyUpdated')."

\n"); } DisplayObject($oP, $oObj, $oUserOrg); } break; case 'show_ongoing': default: $oP->set_title(Dict::S('Portal:ShowOngoing')); DisplayMainMenu($oP); ShowOngoingTickets($oP); } } } $oP->output(); } catch(CoreException $e) { require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

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

\n"); $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); //$oP->p($e->getTraceAsString()); $oP->output(); if (MetaModel::IsLogEnabledIssue()) { if (MetaModel::IsValidClass('EventIssue')) { try { $oLog = new EventIssue(); $oLog->Set('message', $e->getMessage()); $oLog->Set('userinfo', ''); $oLog->Set('issue', $e->GetIssue()); $oLog->Set('impact', 'Page could not be displayed'); $oLog->Set('callstack', $e->getTrace()); $oLog->Set('data', $e->getContextData()); $oLog->DBInsertNoReload(); } catch(Exception $e) { IssueLog::Error("Failed to log issue into the DB"); } } IssueLog::Error($e->getMessage()); } // For debugging only //throw $e; } catch(Exception $e) { require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

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

\n"); $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); //$oP->p($e->getTraceAsString()); $oP->output(); if (MetaModel::IsLogEnabledIssue()) { if (MetaModel::IsValidClass('EventIssue')) { try { $oLog = new EventIssue(); $oLog->Set('message', $e->getMessage()); $oLog->Set('userinfo', ''); $oLog->Set('issue', 'PHP Exception'); $oLog->Set('impact', 'Page could not be displayed'); $oLog->Set('callstack', $e->getTrace()); $oLog->Set('data', array()); $oLog->DBInsertNoReload(); } catch(Exception $e) { IssueLog::Error("Failed to log issue into the DB"); } } IssueLog::Error($e->getMessage()); } } ?>