User portal: enable the creation of Incident tickets (ITIL + requires a change in the configuration file -see the readme file)

SVN:trunk[2959]
This commit is contained in:
Romain Quetiez
2013-10-28 16:50:13 +00:00
parent e92d193347
commit a333bcb084
8 changed files with 386 additions and 143 deletions

View File

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

View File

@@ -7,15 +7,16 @@
<constant id="PORTAL_VALIDATE_SERVICECATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT Service AS s JOIN SLA AS sla ON sla.service_id=s.id JOIN lnkContractToSLA AS ln ON ln.sla_id=sla.id JOIN CustomerContract AS cc ON ln.contract_id=cc.id WHERE cc.org_id = :org_id AND s.id = :id]]></constant>
<constant id="PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT ServiceSubcategory AS Sub JOIN Service AS Svc ON Sub.service_id = Svc.id WHERE Sub.id=:id]]></constant>
<constant id="PORTAL_ALL_PARAMS" xsi:type="string" _delta="define"><![CDATA[from_service_id,org_id,caller_id,service_id,servicesubcategory_id,title,description,impact,urgency,workgroup_id,moreinfo,caller_id,start_date,end_date,duration,impact_duration]]></constant>
<constant id="PORTAL_ATTCODE_LOG" xsi:type="string" _delta="define"><![CDATA[ticket_log]]></constant>
<constant id="PORTAL_ATTCODE_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_commment]]></constant>
<constant id="PORTAL_REQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency,workgroup_id,ticket_log]]></constant>
<constant id="PORTAL_ATTCODE_TYPE" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_SET_TYPE_FROM" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_TICKETS_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_TYPE_TO_CLASS" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_USERREQUEST_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[ticket_log]]></constant>
<constant id="PORTAL_USERREQUEST_USER_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_commment]]></constant>
<constant id="PORTAL_USERREQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency,workgroup_id,ticket_log]]></constant>
<constant id="PORTAL_USERREQUEST_TYPE" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_USERREQUEST_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_TICKETS_SEARCH_CRITERIA" xsi:type="string" _delta="define"><![CDATA[ref,start_date,close_date,service_id,caller_id]]></constant>
<constant id="PORTAL_TICKETS_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_TICKET_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
<constant id="PORTAL_USERREQUEST_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_USERREQUEST_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
</constants>
<classes>
<class id="UserRequest" _delta="define">

View File

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

View File

@@ -3,19 +3,27 @@
<constants>
<constant id="PORTAL_POWER_USER_PROFILE" xsi:type="string" _delta="define"><![CDATA[Portal power user]]></constant>
<constant id="PORTAL_SERVICECATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :org_id AND s.status != 'obsolete']]></constant>
<constant id="PORTAL_SERVICE_SUBCATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT ServiceSubcategory WHERE service_id = :svc_id AND request_type="service_request" AND ServiceSubcategory.status != "obsolete"]]></constant>
<constant id="PORTAL_SERVICE_SUBCATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT ServiceSubcategory WHERE service_id = :svc_id AND ServiceSubcategory.status != "obsolete"]]></constant>
<constant id="PORTAL_VALIDATE_SERVICECATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :org_id AND s.id = :id AND s.status != 'obsolete']]></constant>
<constant id="PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT ServiceSubcategory AS Sub JOIN Service AS Svc ON Sub.service_id = Svc.id WHERE Sub.id=:id AND Sub.status != 'obsolete']]></constant>
<constant id="PORTAL_ALL_PARAMS" xsi:type="string" _delta="define"><![CDATA[from_service_id,org_id,caller_id,service_id,servicesubcategory_id,title,description,impact,emergency,moreinfo,caller_id,start_date,end_date,duration,impact_duration]]></constant>
<constant id="PORTAL_ATTCODE_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
<constant id="PORTAL_ATTCODE_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_comment]]></constant>
<constant id="PORTAL_REQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency]]></constant>
<constant id="PORTAL_ATTCODE_TYPE" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_SET_TYPE_FROM" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_TICKETS_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_SET_TYPE_FROM" xsi:type="string" _delta="define"><![CDATA[request_type]]></constant>
<constant id="PORTAL_TYPE_TO_CLASS" xsi:type="string" _delta="define"><![CDATA[{"service_request":"UserRequest","incident":"Incident"}]]></constant>
<constant id="PORTAL_TICKETS_SEARCH_CRITERIA" xsi:type="string" _delta="define"><![CDATA[ref,start_date,close_date,service_id,caller_id]]></constant>
<constant id="PORTAL_TICKETS_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_TICKET_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
<constant id="PORTAL_USERREQUEST_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
<constant id="PORTAL_USERREQUEST_USER_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_comment]]></constant>
<constant id="PORTAL_USERREQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency]]></constant>
<constant id="PORTAL_USERREQUEST_TYPE" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_USERREQUEST_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_USERREQUEST_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_USERREQUEST_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
<constant id="PORTAL_INCIDENT_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
<constant id="PORTAL_INCIDENT_USER_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_comment]]></constant>
<constant id="PORTAL_INCIDENT_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency]]></constant>
<constant id="PORTAL_INCIDENT_TYPE" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_INCIDENT_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_INCIDENT_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_INCIDENT_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
</constants>
<classes>
<class id="UserRequest" _delta="define">

View File

@@ -7,15 +7,16 @@
<constant id="PORTAL_VALIDATE_SERVICECATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT Service AS s JOIN lnkCustomerContractToService AS l1 ON l1.service_id=s.id JOIN CustomerContract AS cc ON l1.customercontract_id=cc.id WHERE cc.org_id = :org_id AND s.id = :id AND s.status != 'obsolete']]></constant>
<constant id="PORTAL_VALIDATE_SERVICESUBCATEGORY_QUERY" xsi:type="string" _delta="define"><![CDATA[SELECT ServiceSubcategory AS Sub JOIN Service AS Svc ON Sub.service_id = Svc.id WHERE Sub.id=:id AND Sub.status != 'obsolete']]></constant>
<constant id="PORTAL_ALL_PARAMS" xsi:type="string" _delta="define"><![CDATA[from_service_id,org_id,caller_id,service_id,servicesubcategory_id,title,description,impact,emergency,moreinfo,caller_id,start_date,end_date,duration,impact_duration]]></constant>
<constant id="PORTAL_ATTCODE_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
<constant id="PORTAL_ATTCODE_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_comment]]></constant>
<constant id="PORTAL_REQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency,public_log]]></constant>
<constant id="PORTAL_ATTCODE_TYPE" xsi:type="string" _delta="define"><![CDATA[request_type]]></constant>
<constant id="PORTAL_SET_TYPE_FROM" xsi:type="string" _delta="define"><![CDATA[request_type]]></constant>
<constant id="PORTAL_TICKETS_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_TYPE_TO_CLASS" xsi:type="string" _delta="define"><![CDATA[]]></constant>
<constant id="PORTAL_USERREQUEST_PUBLIC_LOG" xsi:type="string" _delta="define"><![CDATA[public_log]]></constant>
<constant id="PORTAL_USERREQUEST_USER_COMMENT" xsi:type="string" _delta="define"><![CDATA[user_comment]]></constant>
<constant id="PORTAL_USERREQUEST_FORM_ATTRIBUTES" xsi:type="string" _delta="define"><![CDATA[title,description,impact,urgency,public_log]]></constant>
<constant id="PORTAL_USERREQUEST_TYPE" xsi:type="string" _delta="define"><![CDATA[request_type]]></constant>
<constant id="PORTAL_USERREQUEST_LIST_ZLIST" xsi:type="string" _delta="define"><![CDATA[finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id]]></constant>
<constant id="PORTAL_TICKETS_SEARCH_CRITERIA" xsi:type="string" _delta="define"><![CDATA[ref,start_date,close_date,service_id,caller_id]]></constant>
<constant id="PORTAL_TICKETS_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_TICKET_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
<constant id="PORTAL_USERREQUEST_CLOSED_ZLIST" xsi:type="string" _delta="define"><![CDATA[title,start_date,close_date,servicesubcategory_id]]></constant>
<constant id="PORTAL_USERREQUEST_DETAILS_ZLIST" xsi:type="string" _delta="define"><![CDATA[{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}]]></constant>
</constants>
<classes>
<class id="UserRequest" _delta="define">

View File

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

View File

@@ -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(
<<<EOF
// Create the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('UserRequest', '');
EOF
);
$aParameters = $oP->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 = "<span id=\"field_{$sInputId}\">".$oRequest->GetFormElementForField($oP, get_class($oRequest), $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).'</span>';
$sValue = "<span id=\"field_{$sInputId}\">".$oRequest->GetFormElementForField($oP, $sClass, $sAttCode, $oAttDef, $value, '', 'attr_'.$sAttCode, '', $iFlags, $aArgs).'</span>';
$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' => '<textarea id="attr_moreinfo" class="resizable ui-resizable" cols="40" rows="8" name="attr_moreinfo" title="" style="margin: 0px; resize: none; position: static; display: block; height: 145px; width: 339px;">'.$sDescription.'</textarea>');
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(
<<<EOF
// Create the object once at the beginning of the page...
var oWizardHelper = new WizardHelper('$sClass', '');
EOF
);
$oP->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("<div class=\"wizContainer\" id=\"form_request_description\">\n");
$oP->add("<h1 id=\"title_request_form\">".Dict::S('Portal:DescriptionOfTheRequest')."</h1>\n");
$oP->WizardFormStart('request_form', 4);
//$oP->add("<table>\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("<input type=\"hidden\" id=\"attr_$sAttCode\" name=\"attr_$sAttCode\" value=\"$sValue\">");
$aFieldsMap[$sAttCode] = 'attr_'.$sAttCode;
}
}
}
$oAttPlugin = new AttachmentPlugIn();
$oAttPlugin->OnDisplayRelations($oRequest, $oP, true /* edit */);
$oP->DumpHiddenParams($aParameters, $aList);
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
$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("<h1>".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName(get_class($oRequest)))."</h1>\n");
$oP->add("<h1>".Dict::Format('UI:Title:Object_Of_Class_Created', $oRequest->GetName(), MetaModel::GetName($sClass))."</h1>\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("<h2>".MetaModel::GetName($sClass)."</h2>\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("<h1>".Dict::S('Portal:ClosedRequests')."</h1>\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("<h1 id=\"title_request_details\">".$oObj->GetIcon()."&nbsp;".Dict::Format('Portal:TitleRequestDetailsFor_Request', $oObj->GetName())."</h1>\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('<tr>');
$oP->add('<td colspan="2" style="vertical-align:top;">');
if (isset($aEditFields[PORTAL_ATTCODE_LOG]))
if (isset($aEditFields[$sLogAttCode]))
{
$oP->add("<div class=\"edit_item\">");
$oP->add('<h1>'.$aEditFields[PORTAL_ATTCODE_LOG]['label'].'</h1>');
$oP->add($aEditFields[PORTAL_ATTCODE_LOG]['value']);
$oP->add('<h1>'.$aEditFields[$sLogAttCode]['label'].'</h1>');
$oP->add($aEditFields[$sLogAttCode]['value']);
$oP->add('</div>');
}
else
{
$oP->add('<h1>'.MetaModel::GetLabel($sClass, PORTAL_ATTCODE_LOG).'</h1>');
$oP->add($oObj->GetAsHTML(PORTAL_ATTCODE_LOG));
$oP->add('<h1>'.MetaModel::GetLabel($sClass, $sLogAttCode).'</h1>');
$oP->add($oObj->GetAsHTML($sLogAttCode));
}
$oP->add('</td>');
$oP->add('</tr>');
@@ -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);

69
portal/readme.txt Normal file
View File

@@ -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_<TICKET-CLASS>_PUBLIC_LOG: name of the public log attribute
PORTAL_<TICKET-CLASS>_USER_COMMENT: name of the user comment attribute (legacy, used to be user_commmmment)
PORTAL_<TICKET-CLASS>_FORM_ATTRIBUTES: attributes proposed to the end-user in the edition form
PORTAL_<TICKET-CLASS>_TYPE: optional attribute to be set with the value of "request type"
PORTAL_<TICKET-CLASS>_LIST_ZLIST: list of attribute displayed in the lists (opened and resolved)
PORTAL_<TICKET-CLASS>_CLOSED_ZLIST: list of attribute displayed in the list of closed tickets
PORTAL_<TICKET-CLASS>_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_<MY-CLASS>_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_<TICKET-CLASS>_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.