diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml index d919af78c..e729d2103 100644 --- a/application/datamodel.application.xml +++ b/application/datamodel.application.xml @@ -1,14 +1,6 @@ - - portal/index.php - 1.0 - - - - - pages/UI.php 2.0 diff --git a/application/portalwebpage.class.inc.php b/application/portalwebpage.class.inc.php deleted file mode 100644 index 93109b76c..000000000 --- a/application/portalwebpage.class.inc.php +++ /dev/null @@ -1,1030 +0,0 @@ - - -/** - * Class PortalWebPage - * - * @copyright Copyright (C) 2010-2017 Combodo SARL - * @license http://opensource.org/licenses/AGPL-3.0 - */ - -require_once(APPROOT."/application/nicewebpage.class.inc.php"); -require_once(APPROOT."/application/applicationcontext.class.inc.php"); -require_once(APPROOT."/application/user.preferences.class.inc.php"); - -define('BUTTON_CANCEL', 1); -define('BUTTON_BACK', 2); -define('BUTTON_NEXT', 4); -define('BUTTON_FINISH', 8); - -define('PARAM_ARROW_SEP', '_x_'); - -class TransactionException extends Exception -{ -} - -/** - * Web page with some associated CSS and scripts (jquery) for a fancier display - * of the Portal web page - */ -class PortalWebPage extends NiceWebPage -{ - /** - * Portal menu - */ - protected $m_sWelcomeMsg; - protected $m_aMenuButtons; - protected $m_oCtx; - - - public function __construct($sTitle, $sAlternateStyleSheet = '') - { - $this->m_oCtx = new ContextTag('GUI:Portal'); - $this->m_sWelcomeMsg = ''; - $this->m_aMenuButtons = array(); - parent::__construct($sTitle); - $this->add_header("Content-type: text/html; charset=utf-8"); - $this->add_header("Cache-control: no-cache"); - $this->add_linked_stylesheet("../css/jquery.treeview.css"); - $this->add_linked_stylesheet("../css/jquery.autocomplete.css"); - $this->add_linked_stylesheet("../css/jquery.multiselect.css"); - $sAbsURLAppRoot = addslashes(utils::GetAbsoluteUrlAppRoot()); // Pass it to Javascript scripts - $sAbsURLModulesRoot = addslashes(utils::GetAbsoluteUrlModulesRoot()); // Pass it to Javascript scripts - $oAppContext = new ApplicationContext(); - $sAppContext = addslashes($oAppContext->GetForLink()); - $this->add_dict_entry('UI:FillAllMandatoryFields'); - if ($sAlternateStyleSheet != '') - { - $this->add_linked_stylesheet("../portal/$sAlternateStyleSheet/portal.css"); - } - else - { - $this->add_linked_stylesheet("../portal/portal.css"); - } - $this->add_linked_script('../js/jquery.layout.min.js'); - $this->add_linked_script('../js/jquery.ba-bbq.min.js'); - $this->add_linked_script("../js/jquery.tablehover.js"); - $this->add_linked_script("../js/jquery.treeview.js"); - $this->add_linked_script("../js/jquery.autocomplete.js"); - $this->add_linked_script("../js/jquery.positionBy.js"); - $this->add_linked_script("../js/jquery.popupmenu.js"); - $this->add_linked_script("../js/date.js"); - $this->add_linked_script("../js/jquery.tablesorter.min.js"); - $this->add_linked_script("../js/jquery.tablesorter.pager.js"); - $this->add_linked_script("../js/jquery.blockUI.js"); - $this->add_linked_script("../js/utils.js"); - $this->add_linked_script("../js/forms-json-utils.js"); - $this->add_linked_script("../js/swfobject.js"); - $this->add_linked_script("../js/jquery.qtip-1.0.min.js"); - $this->add_linked_script('../js/jquery.multiselect.js'); - $this->add_linked_script("../js/ajaxfileupload.js"); - $this->add_linked_script("../js/ckeditor/ckeditor.js"); - $this->add_linked_script("../js/ckeditor/adapters/jquery.js"); - - $this->add_linked_script("../js/jquery-ui-timepicker-addon.js"); - $this->add_linked_script("../js/jquery-ui-timepicker-addon-i18n.min.js"); - $this->add_linked_stylesheet("../css/jquery-ui-timepicker-addon.css"); - - $sJSDisconnectedMessage = json_encode(Dict::S('UI:DisconnectedDlgMessage')); - $sJSTitle = json_encode(Dict::S('UI:DisconnectedDlgTitle')); - $sJSLoginAgain = json_encode(Dict::S('UI:LoginAgain')); - $sJSStayOnThePage = json_encode(Dict::S('UI:StayOnThePage')); - $aDaysMin = array(Dict::S('DayOfWeek-Sunday-Min'), Dict::S('DayOfWeek-Monday-Min'), Dict::S('DayOfWeek-Tuesday-Min'), Dict::S('DayOfWeek-Wednesday-Min'), - Dict::S('DayOfWeek-Thursday-Min'), Dict::S('DayOfWeek-Friday-Min'), Dict::S('DayOfWeek-Saturday-Min')); - $aMonthsShort = array(Dict::S('Month-01-Short'), Dict::S('Month-02-Short'), Dict::S('Month-03-Short'), Dict::S('Month-04-Short'), Dict::S('Month-05-Short'), Dict::S('Month-06-Short'), - Dict::S('Month-07-Short'), Dict::S('Month-08-Short'), Dict::S('Month-09-Short'), Dict::S('Month-10-Short'), Dict::S('Month-11-Short'), Dict::S('Month-12-Short')); - $sTimeFormat = AttributeDateTime::GetFormat()->ToTimeFormat(); - $oTimeFormat = new DateTimeFormat($sTimeFormat); - $sJSLangShort = json_encode(strtolower(substr(Dict::GetUserLanguage(), 0, 2))); - - // Date picker options - $aPickerOptions = array( - 'showOn' => 'button', - 'buttonImage' => '../images/calendar.png', - 'buttonImageOnly' => true, - 'dateFormat' => AttributeDate::GetFormat()->ToDatePicker(), - 'constrainInput' => false, - 'changeMonth' => true, - 'changeYear' => true, - 'dayNamesMin' => $aDaysMin, - 'monthNamesShort' => $aMonthsShort, - 'firstDay' => (int) Dict::S('Calendar-FirstDayOfWeek'), - ); - $sJSDatePickerOptions = json_encode($aPickerOptions); - - // Time picker additional options - $aPickerOptions['showOn'] = ''; - $aPickerOptions['buttonImage'] = null; - $aPickerOptions['timeFormat'] = $oTimeFormat->ToDatePicker(); - $aPickerOptions['controlType'] = 'select'; - $aPickerOptions['closeText'] = Dict::S('UI:Button:Ok'); - $sJSDateTimePickerOptions = json_encode($aPickerOptions); - if ($sJSLangShort != '"en"') - { - // More options that cannot be passed via json_encode since they must be evaluated client-side - $aMoreJSOptions = ", - 'timeText': $.timepicker.regional[$sJSLangShort].timeText, - 'hourText': $.timepicker.regional[$sJSLangShort].hourText, - 'minuteText': $.timepicker.regional[$sJSLangShort].minuteText, - 'secondText': $.timepicker.regional[$sJSLangShort].secondText, - 'currentText': $.timepicker.regional[$sJSLangShort].currentText - }"; - $sJSDateTimePickerOptions = substr($sJSDateTimePickerOptions, 0, -1).$aMoreJSOptions; - } - $this->add_script( - <<< EOF - function PrepareWidgets() - { - // note: each action implemented here must be idempotent, - // because this helper function might be called several times on a given page - - $(".date-pick").datepicker($sJSDatePickerOptions); - - // Hack for the date and time picker addon issue on Chrome (see #1305) - // The workaround is to instantiate the widget on demand - // It relies on the same markup, thus reverting to the original implementation should be straightforward - $(".datetime-pick:not(.is-widget-ready)").each(function(){ - var oInput = this; - $(oInput).addClass('is-widget-ready'); - $('') - .insertAfter($(this)) - .on('click', function(){ - $(oInput) - .datetimepicker($sJSDateTimePickerOptions) - .datetimepicker('show') - .datetimepicker('option', 'onClose', function(dateText,inst){ - $(oInput).datetimepicker('destroy'); - }) - .on('click keypress', function(){ - $(oInput).datetimepicker('hide'); - }); - }); - }); - } -EOF - ); - - $this->add_ready_script( -<< 0) - { - this.truncatedList = true; - } - if (this.truncatedList) - { - $("tr td",table).removeClass('truncated'); - $("tr:last td",table).addClass('truncated'); - } - } - }); - - - $.tablesorter.addWidget({ - // give the widget a id - id: "myZebra", - // format is called when the on init and when a sorting has finished - format: function(table) - { - // Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc.. - $("tbody tr:even",table).addClass('even'); - $("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even'); - $("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even'); - $("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even'); - } - }); - - PrepareWidgets(); - - //$('.resizable').resizable(); // Make resizable everything that claims to be resizable ! - $('.caselog_header').click( function () { $(this).toggleClass('open').next('.caselog_entry,.caselog_entry_html').toggle(); }); - - $(document).ajaxSend(function(event, jqxhr, options) { - jqxhr.setRequestHeader('X-Combodo-Ajax', 'true'); - }); - $(document).ajaxError(function(event, jqxhr, options) { - if (jqxhr.status == 401) - { - $('
'+$sJSDisconnectedMessage+'
').dialog({ - modal:true, - title: $sJSTitle, - close: function() { $(this).remove(); }, - minWidth: 400, - buttons: [ - { text: $sJSLoginAgain, click: function() { window.location.href= GetAbsoluteUrlAppRoot()+'pages/UI.php' } }, - { text: $sJSStayOnThePage, click: function() { $(this).dialog('close'); } } - ] - }); - } - }); -} -catch(err) -{ - // Do something with the error ! - alert(err); -} -EOF -); - - $this->add_script( -<< 0) - { - bResult = ($('input[name='+sInputId+']:checked').length > 0); - } - else - { - // First select found... - bResult = ($('input:checked').length > 0); - } - if (!bResult) - { - alert(sMessage); - } - return bResult; - } - - - function GetAbsoluteUrlAppRoot() - { - return '$sAbsURLAppRoot'; - } - - function GetAbsoluteUrlModulesRoot() - { - return '$sAbsURLModulesRoot'; - } - - function AddAppContext(sURL) - { - var sContext = '$sAppContext'; - if (sContext.length > 0) - { - if (sURL.indexOf('?') == -1) - { - return sURL+'?'+sContext; - } - return sURL+'&'+sContext; - } - return sURL; - } - - function GoBack(sFormId) - { - var form = $('#'+sFormId); - var step_back = $('input[name=step_back]'); - - form.unbind('submit'); // De-activate validation - - step_back.val(1); - form.submit(); // Go - } - - function GoHome() - { - var form = $('FORM'); - form.unbind('submit'); // De-activate validation - window.location.href = window.location.href.replace(/operation=[^&]*&?/, ''); - return false; - } - - function SetWizardNextStep(sStep) - { - var next_step = $('input[id=next_step]'); - next_step.val(sStep); - } - - // For disabling the CKEditor at init time when the corresponding textarea is disabled ! - CKEDITOR.plugins.add( 'disabler', - { - init : function( editor ) - { - editor.on( 'instanceReady', function(e) - { - e.removeListener(); - $('#'+ editor.name).trigger('update'); - }); - } - - }); -EOF -); - - // For Wizard helper to process the ajax replies - $this->add('
'); - - // Customize the logo (unless a customer CSS has been defined) - if ($sAlternateStyleSheet == '') - { - if (file_exists(MODULESROOT.'branding/portal-logo.png')) - { - $sDisplayIcon = utils::GetAbsoluteUrlModulesRoot().'branding/portal-logo.png'; - $this->add_style("div#portal #logo {background: url(\"$sDisplayIcon\") no-repeat scroll 0 0 transparent;}"); - } - } - -} - - public function SetCurrentTab($sTabLabel = '') - { - } - - /** - * Specify a welcome message (optional) - */ - public function SetWelcomeMessage($sMsg) - { - $this->m_sWelcomeMsg = $sMsg; - } - - - /** - * Add a button to the portal's main menu - */ - public function AddMenuButton($sId, $sLabel, $sHyperlink) - { - $this->m_aMenuButtons[] = array('id' => $sId, 'label' => $sLabel, 'hyperlink' => $sHyperlink); - } - - var $m_bEnableDisconnectButton = true; - public function EnableDisconnectButton($bEnable) - { - $this->m_bEnableDisconnectButton = $bEnable; - } - - public function output() - { - $sApplicationBanner = ''; - if (!MetaModel::DBHasAccess(ACCESS_USER_WRITE)) - { - $sReadOnly = Dict::S('UI:AccessRO-Users'); - $sAdminMessage = trim(MetaModel::GetConfig()->Get('access_message')); - $sApplicationBanner .= '
'; - $sApplicationBanner .= ''; - $sApplicationBanner .= ' '.$sReadOnly.''; - if (strlen($sAdminMessage) > 0) - { - $sApplicationBanner .= ' : '.$sAdminMessage.''; - } - $sApplicationBanner .= '
'; - } - - $sMenu = ''; - if ($this->m_bEnableDisconnectButton) - { - $this->AddMenuButton('logoff', 'Portal:Disconnect', utils::GetAbsoluteUrlAppRoot().'pages/logoff.php?operation=do_logoff'); // This menu is always present and is the last one - } - foreach($this->m_aMenuButtons as $aMenuItem) - { - $sMenu .= "".Dict::S($aMenuItem['label']).""; - } - $this->s_content = '
'.$this->m_sWelcomeMsg.'
'.$sApplicationBanner.'
'.$this->s_content.'
'; - parent::output(); - } - - /** - * Displays a list of objects, without any hyperlink (except for the object's details) - * @param DBObjectSet $oSet The set of objects to display - * @param Array $aZList The ZList (list of field codes) to use for the tabular display - * @param String $sEmptyListMessage Message displayed whenever the list is empty - * @return string The HTML text representing the list - */ - public function DisplaySet($oSet, $aZList, $sEmptyListMessage = '') - { - if ($oSet->Count() > 0) - { - $sClass = $oSet->GetClass(); - if (is_subclass_of($sClass, 'cmdbAbstractObject')) - { - // Home-made and very limited display of an object set - - $sUniqueId = $sClass.$this->GetUniqueId(); - $this->add("
\n"); // The id here MUST be the same as currentId, otherwise the pagination will be broken - cmdbAbstractObject::DisplaySet($this, $oSet, array('currentId' => $sUniqueId, 'menu' => false, 'toolkit_menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList))); - $this->add("
\n"); - } - else - { - // Home-made and very limited display of an object set - $aAttribs = array(); - $aValues = array(); - $aAttribs['key'] = array('label' => MetaModel::GetName($sClass), 'description' => ''); - foreach($aZList as $sAttCode) - { - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - $aAttribs[$sAttCode] = array('label' => $oAttDef->GetLabel(), 'description' => $oAttDef->GetDescription()); - } - while($oObj = $oSet->Fetch()) - { - $aRow = array(); - - $aRow['key'] = ''.$oObj->GetName().''; - $sHilightClass = $oObj->GetHilightClass(); - if ($sHilightClass != '') - { - $aRow['@class'] = $sHilightClass; - } - foreach($aZList as $sAttCode) - { - $aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode); - } - $aValues[$oObj->GetKey()] = $aRow; - } - $this->table($aAttribs, $aValues); - } - } - elseif (strlen($sEmptyListMessage) > 0) - { - $this->add($sEmptyListMessage); - } - } - - /** - * Display the attributes of an object (no title, no form) - * @param Object $oObj Any kind of object - * @param aAttList The list of attributes to display - * @return void - */ - public function DisplayObjectDetails($oObj, $aAttList) - { - $sClass = get_class($oObj); - $aDetails = array(); - foreach($aAttList as $sAttCode) - { - $iFlags = $oObj->GetAttributeFlags($sAttCode); - $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); - if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0) ) - { - // Don't display linked set and non-visible attributes (in this state) - $sDisplayValue = $oObj->GetAsHTML($sAttCode); - $aDetails[] = array('label' => ''.MetaModel::GetLabel($sClass, $sAttCode).'', 'value' => $sDisplayValue); - } - } - $this->details($aDetails); - } - - /** - * DisplayObjectLinkset - * @param Object $oObj Any kind of object - * @param $sLinkSetAttCode The attribute code of the link set attribute to display - * @param $sRemoteAttCode The external key on the linked class, pointing to the remote objects - * @param $aZList The list of attribute of the remote object - * @param $sEmptyListMessage The message to display if the list is empty - * @return void - */ - public function DisplayObjectLinkset($oObj, $sLinkSetAttCode, $sRemoteAttCode, $aZList, $sEmptyListMessage = '', $oSearchRestriction = null) - { - if (empty($sEmptyListMessage)) - { - $sEmptyListMessage = Dict::S('UI:Search:NoObjectFound'); - } - - $oLinkSet = $oObj->Get($sLinkSetAttCode); - if ($oLinkSet->Count() > 0) - { - $sClass = $oLinkSet->GetClass(); - $oExtKeyToRemote = MetaModel::GetAttributeDef($sClass, $sRemoteAttCode); - $sRemoteClass = $oExtKeyToRemote->GetTargetClass(); - - if (is_null($oSearchRestriction)) - { - $oObjSearch = new DBObjectSearch($sRemoteClass); - } - else - { - $oObjSearch = $oSearchRestriction; - } - $oObjSearch->AddCondition_ReferencedBy($oLinkSet->GetFilter(), $sRemoteAttCode); - - $aExtraParams = array('menu' => false, 'toolkit_menu' => false, 'zlist' => false, 'extra_fields' => implode(',', $aZList)); - $oBlock = new DisplayBlock($oObjSearch, 'list', false); - $oBlock->Display($this, 1, $aExtraParams); - } - elseif (strlen($sEmptyListMessage) > 0) - { - $this->add($sEmptyListMessage); - } - } - - protected function DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, $sFieldName = null, $aFilterParams = array()) - { - if (is_null($sFieldName)) - { - $sFieldName = str_replace('->', PARAM_ARROW_SEP, $sAttSpec); - } - - $iPos = strpos($sAttSpec, '->'); - if ($iPos !== false) - { - $sAttCode = substr($sAttSpec, 0, $iPos); - $sSubSpec = substr($sAttSpec, $iPos + 2); - - if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) - { - throw new Exception("Invalid attribute code '$sClass/$sAttCode' in search specification '$sAttSpec'"); - } - - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); - if ($oAttDef->IsLinkSet()) - { - $sTargetClass = $oAttDef->GetLinkedClass(); - } - elseif ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) - { - $sTargetClass = $oAttDef->GetTargetClass(EXTKEY_ABSOLUTE); - } - else - { - throw new Exception("Attribute specification '$sAttSpec', '$sAttCode' should be either a link set or an external key"); - } - $this->DisplaySearchField($sTargetClass, $sSubSpec, $aExtraParams, $sPrefix, $sFieldName, $aFilterParams); - } - else - { - // $sAttSpec is an attribute code - // - $this->add(''); - $sFilterValue = utils::ReadParam($sPrefix.$sFieldName, '', false, 'raw_data'); - $sFilterOpCode = null; // Use the default 'loose' OpCode - $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttSpec); - if ($oAttDef->IsExternalKey()) - { - $sTargetClass = $oAttDef->GetTargetClass(); - $sFilterDefName = 'PORTAL_TICKETS_SEARCH_FILTER_'.$sAttSpec; - if (defined($sFilterDefName)) - { - try - { - $oFitlerWithParams = DBObjectSearch::FromOQL(constant($sFilterDefName)); - $sFilterOQL = $oFitlerWithParams->ToOQL(true, $aFilterParams); - $oAllowedValues = new DBObjectSet(DBObjectSearch::FromOQL($sFilterOQL), array(), $aFilterParams); - } - catch(OQLException $e) - { - throw new Exception("Incorrect filter '$sFilterDefName' for attribute '$sAttSpec': ".$e->getMessage()); - } - } - else - { - $oAllowedValues = new DBObjectSet(new DBObjectSearch($sTargetClass)); - } - - $iFieldSize = $oAttDef->GetMaxSize(); - $iMaxComboLength = $oAttDef->GetMaximumComboLength(); - $this->add(" "); - //$oWidget = UIExtKeyWidget::DIsplayFromAttCode($sAttSpec, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sPrefix.$sFieldName, false, '', $sPrefix, ''); - //$this->add($oWidget->Display($this, $aExtraParams, true /* bSearchMode */)); - $aExtKeyParams = $aExtraParams; - $aExtKeyParams['iFieldSize'] = $oAttDef->GetMaxSize(); - $aExtKeyParams['iMinChars'] = $oAttDef->GetMinAutoCompleteChars(); - // DisplayFromAttCode($this, $sAttCode, $sClass, $sTitle, $oAllowedValues, $value, $iInputId, $bMandatory, $sFieldName = '', $sFormPrefix = '', $aArgs, $bSearchMode = false) - $sHtml = UIExtKeyWidget::DisplayFromAttCode($this, $sAttSpec, $sClass, $oAttDef->GetLabel(), $oAllowedValues, $sFilterValue, $sPrefix.$sFieldName, false, $sPrefix.$sFieldName, $sPrefix, $aExtKeyParams, true); - $this->add($sHtml); - } - else - { - $aAllowedValues = MetaModel::GetAllowedValues_flt($sClass, $sAttSpec, $aExtraParams); - if (is_null($aAllowedValues)) - { - // Any value is possible, display an input box - $sSanitizedValue = htmlentities($sFilterValue, ENT_QUOTES, 'UTF-8'); - $this->add(" \n"); - } - else - { - //Enum field or external key, display a combo - $sValue = "\n"; - $this->add(" $sValue\n"); - } - } - unset($aExtraParams[$sFieldName]); - $this->add(' '); - - $sTip = $oAttDef->GetHelpOnSmartSearch(); - if (strlen($sTip) > 0) - { - $sTip = addslashes($sTip); - $sTip = str_replace(array("\n", "\r"), " ", $sTip); - // :input does represent in form visible input (INPUT, SELECT, TEXTAREA) - $this->add_ready_script("$(':input[name={$sPrefix}$sFieldName]').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"); - } - } - } - - /** - * Get The organization of the current user (i.e. the organization of its contact) - * @throws Exception - */ - function GetUserOrg() - { - $oOrg = null; - $iContactId = UserRights::GetContactId(); - $oContact = MetaModel::GetObject('Contact', $iContactId, false); // false => 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; - } - - public function DisplaySearchForm($sClass, $aAttList, $aExtraParams, $sPrefix, $bClosed = true) - { - $oUserOrg = $this->GetUserOrg(); - $aFilterParams = array('org_id' => $oUserOrg->GetKey(), 'contact_id' => UserRights::GetContactId()); - $sCSSClass = ($bClosed) ? 'DrawerClosed' : ''; - $this->add("
\n"); - $this->add_ready_script( -<<add("
\n"); // Don't use $_SERVER['SCRIPT_NAME'] since the form may be called asynchronously (from ajax.php) - // $this->add("

".Dict::Format('UI:SearchFor_Class_Objects', 'xxxxxx')."

\n"); - $this->add("

\n"); - foreach($aAttList as $sAttSpec) - { - //$oAppContext->Reset($sAttSpec); // Make sure the same parameter will not be passed twice - $this->DisplaySearchField($sClass, $sAttSpec, $aExtraParams, $sPrefix, null, $aFilterParams); - } - $this->add("

\n"); - $this->add("

\n"); - foreach($aExtraParams as $sName => $sValue) - { - // Note: use DumpHiddenParams() to transmit arrays as hidden params - if (is_scalar($sValue)) - { - $this->add("\n"); - } - } - // $this->add($oAppContext->GetForForm()); - $this->add("
\n"); - $this->add("
\n"); - $this->add("
\n"); - $this->add("
".Dict::S('UI:SearchToggle')."
\n"); - } - - /** - * Read parameters from the page - * Parameters that were absent from the page's parameters are not set in the resulting hash array - * @input string $sMethod Either get or post - * @return Hash Array of name => value corresponding to the parameters that were passed to the page - */ - public function ReadAllParams($sParamList, $sPrefix = 'attr_') - { - $aParams = explode(',', $sParamList); - $aValues = array(); - foreach($aParams as $sName) - { - $sName = trim($sName); - $value = utils::ReadParam($sPrefix.$sName, null, false, 'raw_data'); - if (!is_null($value)) - { - $aValues[$sName] = $value; - } - } - return $aValues; - } - - /** - * Outputs a list of parameters as hidden fields - * Example: attr_dummy[-123][id] = "blah" - * @param Hash $aParameters Array name => value for the parameters - * @param Array $aExclude The list of parameters that must not be handled this way (probably already in the visible part of the form) - * @return void - */ - protected function DumpHiddenParamsInternal($sName, $value) - { - if (is_array($value)) - { - foreach($value as $sKey => $item) - { - $this->DumpHiddenParamsInternal($sName.'['.$sKey.']', $item); - } - } - else - { - $this->Add(""); - } - } - - /** - * Outputs a list of parameters as hidden field into the current page - * (must be called when inside a form) - * @param Hash $aParameters Array name => value for the parameters - * @param Array $aExclude The list of parameters that must not be handled this way (probably already in the visible part of the form) - * @return void - */ - public function DumpHiddenParams($aParameters, $aExclude = null, $sPrefix = 'attr_') - { - foreach($aParameters as $sAttCode => $value) - { - if (is_null($aExclude) || !in_array($sAttCode, $aExclude)) - { - $this->DumpHiddenParamsInternal($sPrefix.$sAttCode, $value); - } - } - } - - public function PostedParamsToFilter($sClass, $aAttList, $sPrefix) - { - $oFilter = new DBObjectSearch($sClass); - $iCountParams = 0; - foreach($aAttList as $sAttSpec) - { - $sFieldName = str_replace('->', PARAM_ARROW_SEP, $sAttSpec); - $value = utils::ReadPostedParam($sPrefix.$sFieldName, null, 'raw_data'); - if (!is_null($value) && (is_array($value) ? count($value)>0 : strlen($value)>0)) - { - $oFilter->AddConditionAdvanced($sAttSpec, $value); - $iCountParams++; - } - } - if ($iCountParams == 0) - { - return null; - } - else - { - return $oFilter; - } - } - - /** - * Updates the object form POSTED arguments, and writes it into the DB (applies a stimuli if requested) - * @param DBObject $oObj The object to update - * $param array $aAttList If set, this will limit the list of updated attributes - * @return void - */ - public function DoUpdateObjectFromPostedForm(DBObject $oObj, $aAttList = null) - { - $sTransactionId = utils::ReadPostedParam('transaction_id', '', 'transaction_id'); - if (!utils::IsTransactionValid($sTransactionId)) - { - throw new TransactionException(); - } - - $sClass = get_class($oObj); - - $sStimulus = trim(utils::ReadPostedParam('apply_stimulus', '')); - $aExpectedAttributes = array(); - if (!empty($sStimulus)) - { - // Compute the target state - - $aTransitions = $oObj->EnumTransitions(); - if (!isset($aTransitions[$sStimulus])) - { - throw new ApplicationException(Dict::Format('UI:Error:Invalid_Stimulus_On_Object_In_State', $sStimulus, $oObj->GetName(), $oObj->GetStateLabel())); - } - $aExpectedAttributes = $oObj->GetTransitionAttributes($sStimulus /*, current state*/); - } - - $oObj->UpdateObjectFromPostedForm('' /* form prefix */, $aAttList, $aExpectedAttributes); - - // Optional: apply a stimulus - // - if (!empty($sStimulus)) - { - if (!$oObj->ApplyStimulus($sStimulus)) - { - throw new Exception("Cannot apply stimulus '$sStimulus' to {$oObj->GetName()}"); - } - } - - if ($oObj->IsModified()) - { - // Record the change - // - $oObj->DBUpdate(); - - // Trigger ? - // - $aClasses = MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL); - $aParams = array('class_list' => CMDBSource::Quote($aClasses)); - $oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT TriggerOnPortalUpdate AS t WHERE t.target_class IN (:class_list)"), array(), $aParams); - while ($oTrigger = $oSet->Fetch()) - { - $oTrigger->DoActivate($oObj->ToArgs('this')); - } - - $this->p("

".Dict::Format('UI:Class_Object_Updated', MetaModel::GetName(get_class($oObj)), $oObj->GetName())."

\n"); - } - $bLockEnabled = MetaModel::GetConfig()->Get('concurrent_lock_enabled'); - if ($bLockEnabled) - { - // Release the concurrent lock, if any - $sOwnershipToken = utils::ReadPostedParam('ownership_token', null, 'raw_data'); - if ($sOwnershipToken !== null) - { - // We're done, let's release the lock - iTopOwnershipLock::ReleaseLock(get_class($oObj), $oObj->GetKey(), $sOwnershipToken); - } - } - } - - /** - * Find the object of the specified Class/ID. - * @param WebPage $oP The current page - * @return DBObject The found object, or throws an exception in case of failure - */ - public function FindObjectFromArgs($aAllowedClasses = null) - { - $sClass = utils::ReadParam('class', '', true, 'class'); - $iId = utils::ReadParam('id', 0, true, 'integer'); - - if (empty($sClass)) - { - throw new Exception("Missing argument 'class'"); - } - if (!MetaModel::IsValidClass($sClass)) - { - throw new Exception("Wrong value for argument 'class': $sClass"); - } - if ($iId == 0) - { - throw new Exception("Missing argument 'id'"); - } - - if(!is_null($aAllowedClasses)) - { - $bAllowed = false; - foreach($aAllowedClasses as $sParentClass) - { - if (MetaModel::IsParentClass($sParentClass, $sClass)) - { - $bAllowed = true; - } - } - if (!$bAllowed) - { - throw new Exception("Class '$sClass not allowed in this implementation'"); - } - } - - $oObj = MetaModel::GetObject($sClass, $iId, false); - if (!is_object($oObj)) - { - throw new Exception("Could not find the object $sClass/$iId"); - } - return $oObj; - } - - var $m_sWizardId = null; - - public function WizardFormStart($sId = '', $sNextStep = null, $bAttachment = false, $sMethod = 'post') - { - $this->m_sWizardId = $sId; - - // multipart... needed for file upload - $this->add("
m_sWizardId}\" method=\"$sMethod\" enctype=\"multipart/form-data\" onsubmit=\"window.bInSubmit = true;\">\n"); - - $aPreviousSteps = $this->GetWizardStepHistory(); - if (utils::ReadParam('step_back', 0) == 1) - { - // Back into the past history - array_pop($aPreviousSteps); - } - else - { - // Moving forward - array_push($aPreviousSteps, utils::ReadParam('next_step')); - } - - $sStepHistory = implode(',', $aPreviousSteps); - $this->add(""); - - if (!is_null($sNextStep)) - { - $this->add(""); - } - $this->add(""); - - $sTransactionId = utils::GetNewTransactionId(); - $this->SetTransactionId($sTransactionId); - $this->add("\n"); - $this->add_ready_script("$(window).on('unload', function() { OnUnload('$sTransactionId') } );\n"); - } - - public function WizardFormButtons($iButtonFlags) - { - $aButtons = array(); - if ($iButtonFlags & BUTTON_CANCEL) - { - $aButtons[] = ""; - } - if ($iButtonFlags & BUTTON_BACK) - { - if (utils::ReadParam('step_back', 1) != 1) - { - $aButtons[] = "m_sWizardId}');\">"; - } - } - if ($iButtonFlags & BUTTON_NEXT) - { - $aButtons[] = ""; - } - if ($iButtonFlags & BUTTON_FINISH) - { - $aButtons[] = ""; - } - - $this->add('
'); - $this->add(implode('', $aButtons)); - $this->add('
'); - } - - public function WizardFormEnd() - { - $this->add("
\n"); - } - - public function GetWizardStep() - { - if (utils::ReadParam('step_back', 0) == 1) - { - // Take the value into the history - one level above - $aPreviousSteps = $this->GetWizardStepHistory(); - array_pop($aPreviousSteps); - return end($aPreviousSteps); - } - else - { - return utils::ReadParam('next_step'); - } - } - - protected function GetWizardStepHistory() - { - $sRawHistory = trim(utils::ReadParam('step_history', '', false, 'raw_data')); - if (strlen($sRawHistory) == 0) - { - return array(); - } - else - { - return explode(',', $sRawHistory); - } - } - - public function WizardCheckSelectionOnSubmit($sMessageIfNoSelection, $sInputName = '') - { - $this->add_ready_script( -<<m_sWizardId}').submit(function() { - return CheckSelection('$sMessageIfNoSelection', '$sInputName'); - }); -EOF - ); - } -} - -?> diff --git a/css/light-grey.css b/css/light-grey.css index e54cbac0b..77ae93da5 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -2732,6 +2732,13 @@ span.search-button, span.refresh-button { overflow: auto; border: #ccc 1px solid; } +#setup .module-selection-body .wiz-choice:checked ~ .description #itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked) ~ .description::after, #setup .module-selection-body .wiz-choice:checked ~ .description #itop-ticket-mgmt-itil-enhanced-portal:not(:checked) ~ .description::after { + content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore."; + display: block; + margin-top: 0.5em; + font-weight: bold; + color: rgba(230, 0, 0, 0.722); +} .mfp-close { cursor: pointer !important; } diff --git a/css/light-grey.scss b/css/light-grey.scss index ce87018d0..becdc0da3 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -3131,6 +3131,18 @@ span.search-button, span.refresh-button { height: 28em; overflow: auto; border: #ccc 1px solid; + .wiz-choice:checked ~ .description{ + #itop-ticket-mgmt-simple-ticket-enhanced-portal:not(:checked), + #itop-ticket-mgmt-itil-enhanced-portal:not(:checked){ + ~ .description::after { + content: "Legacy portal is no longer part of iTop, by leaving this option unchecked your portal users won't be able to access iTop anymore."; + display: block; + margin-top: 0.5em; + font-weight: bold; + color: #e60000b8; + } + } + } } } .mfp-close { diff --git a/datamodels/2.x/installation.xml b/datamodels/2.x/installation.xml index f4c013b53..4decbfe83 100755 --- a/datamodels/2.x/installation.xml +++ b/datamodels/2.x/installation.xml @@ -99,8 +99,8 @@ itop-ticket-mgmt-simple-ticket-enhanced-portal - Enhanced Customer Portal - Replace the built-in customer portal with a more modern version, working better with hand-held devices and bringing new features + Customer Portal + itop-portal itop-portal-base @@ -134,8 +134,8 @@ itop-ticket-mgmt-itil-enhanced-portal - Enhanced Customer Portal - Replace the built-in customer portal with a more modern version, working better with hand-held devices and bringing new features + Customer Portal + itop-portal itop-portal-base diff --git a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml index f87f6db50..5ccbe2aa9 100755 --- a/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml +++ b/datamodels/2.x/itop-incident-mgmt-itil/datamodel.itop-incident-mgmt-itil.xml @@ -1,17 +1,5 @@ - - - - - - - - - - org_id AND caller_id = :contact->id]]> - org_id]]> - Ticket diff --git a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml index a34c4b9f5..b820ddcaf 100644 --- a/datamodels/2.x/itop-portal/datamodel.itop-portal.xml +++ b/datamodels/2.x/itop-portal/datamodel.itop-portal.xml @@ -1,9 +1,8 @@ - - pages/exec.php?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal + pages/exec.php?exec_module=itop-portal-base&exec_page=index.php&portal_id=itop-portal 1.0 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 bb68432d5..9986104fc 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 @@ -1,17 +1,5 @@ - - - - - - - - - - org_id AND caller_id = :contact->id]]> - org_id]]> - Ticket 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 1419b12d1..e0e49d684 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 @@ -1,23 +1,5 @@ - - - - - - - - - - - - - - - org_id AND caller_id = :contact->id]]> - - org_id]]> - Ticket diff --git a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml index b1709c8a1..5abea2324 100755 --- a/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml +++ b/datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml @@ -2,17 +2,6 @@ service_id AND sc.org_id = :this->org_id AND slt.request_type = :request_type AND slt.priority = :this->priority]]> - - - - - - - - - - - diff --git a/portal/images/bg.png b/portal/images/bg.png deleted file mode 100644 index 085a6e160..000000000 Binary files a/portal/images/bg.png and /dev/null differ diff --git a/portal/images/company_logo.png b/portal/images/company_logo.png deleted file mode 100644 index 53be45a2a..000000000 Binary files a/portal/images/company_logo.png and /dev/null differ diff --git a/portal/images/dockbar_bg.png b/portal/images/dockbar_bg.png deleted file mode 100644 index 771338883..000000000 Binary files a/portal/images/dockbar_bg.png and /dev/null differ diff --git a/portal/images/header_bg.png b/portal/images/header_bg.png deleted file mode 100644 index 3bcebce68..000000000 Binary files a/portal/images/header_bg.png and /dev/null differ diff --git a/portal/index.php b/portal/index.php index f2bada8e3..df35cae6c 100644 --- a/portal/index.php +++ b/portal/index.php @@ -1,1448 +1,40 @@ /** - * iTop User Portal main page + * Copyright (C) 2013-2019 Combodo SARL + * + * This file is part of iTop. + * + * iTop is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * iTop is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * * - * @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', '', '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->error(Dict::Format('UI:Error_Details', $e->getMessage())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) @@ -1470,4 +62,3 @@ catch(Exception $e) IssueLog::Error($e->getMessage()); } } -?> diff --git a/portal/portal.css b/portal/portal.css deleted file mode 100644 index 32b6b9596..000000000 --- a/portal/portal.css +++ /dev/null @@ -1,237 +0,0 @@ -html, body { - margin: 0; - padding:0; - overflow-y: auto; - font-family: Arial,Helvetica,Verdana,sans-serif; - font-size: 8pt; -} -#portal { - height: 100%; -} -#content { - margin: auto; - padding-left: 10px; - padding-right: 10px; - no.text-align: center; - overflow-y: auto; - max-width: 90%; - min-width: 960px; - position: relative; - display: block; - clear: both; -} -div#portal #welcome { - background: url("./images/dockbar_bg.png") repeat-x scroll 0 0 #97A1AE; - border-bottom: 1px solid #636364; - font-size: 13px; - padding: 1px 5px; - position: relative; - z-index: 300; - text-align:right; - color: #2C2F34; - font-weight: bold; - text-shadow: 1px 1px #FFFFFF; -} -div#portal #banner { - height: 116px; - display: block; - vertical-align:middle; - background-color: #f6f6f1; - background: none repeat scroll 0 0 transparent; - margin: 0 auto; - max-width: 90%; - min-width: 960px; - position: relative; - margin-bottom: 1em; -} - -div#portal #logo { - background: url(./images/company_logo.png?v=2) 0 50% no-repeat; - margin-top:6px; - margin-bottom:6px; - margin-left:20px; - margin-right:20px; - height: 60px; - border: 0; - vertical-align: middle; - text-align: center; - display: block; - line-height: 116px; -} -#menu { - background: url("./images/bg.png") repeat-x scroll 0 0 #414445; - clear: both; - min-height: 2.4em; - line-height: 3em; - -moz-border-radius: 4px; - vertical-align: middle; -} -#portal_menu { - height: 60px; -} - -div.button { - font-size: 1.1em; - font-weight: bold; - text-decoration: none; -} -a.button , a.button:visited { - line-height: 1.2em; - color: #FFFFFF; - font-size: 1.1em; - font-weight: bold; - text-decoration: none; - display: inline; - clear:both; -} -a.button:hover span { - background: #000; -} -a.button span { - vertical-align:middle; - margin: 0 1px; - padding: 5px; - padding-left: 15px; - padding-right: 15px; - -moz-border-radius: 4px; - border-radius: 4px; -} - -#close_form_table { - width: 100%; - padding: 20px; -} - -#request_details td { - text-align:left; -} - -#request_details td fieldset{ - xxxheight:100%; -} - -#form_details { - display: inline-block; -} - -div.wizContainer -{ - border: 1px solid #C8C9CA; - -moz-border-radius: 4px 4px 4px 4px; - background-color: #fff; - padding: 3px; -} - -.wizContainer table { - nodisplay: inline-block; - text-align: left; -} - -#user_request_comment { - width: 30em; - height: 20em; -} - -#buttons { - margin-top: 1em; -} -div#buttons #btn_cancel { - margin-right: 50px; -} - -div#buttons #btn_back { - margin-left: 50px; - margin-right: 5px; -} - -div#buttons #btn_next { - margin-left: 5px; -} - -div#buttons #btn_finish { - margin-left: 5px; -} -table.listContainer { - clear: both; - width: 100%; -} -h1 { - background: url("./images/header_bg.png") repeat-x scroll 0 0 #D4D4D4; - border-color: #C8C9CA #9E9E9E #9E9E9E #C8C9CA; - border-style: solid; - border-width: 1px; - font-size: 1.1em; - font-weight: bold; - color: #222222; - font-weight: bold; - text-shadow: 1px 1px #FFFFFF; - padding: 5px; - -moz-border-radius: 4px 4px 4px 4px; - margin-top: 5px; -} - -div.DrawerHandle { - display:none; -} -div.HRDrawer { - background: transparent; - border: 0; - height: 0.5em; -} - -.SearchDrawer { - background-color: #F9EDBF; - border: 0; - -moz-border-radius: 4px 4px 4px 4px; -} -.SearchDrawer label { - background: transparent; -} -#open_incidents, #open_requests, #open_changes, #request_details { - margin-bottom: 1em; -} -legend { - background: url("./images/header_bg.png") repeat-x scroll 0 0 #D4D4D4; - border-color: #C8C9CA #9E9E9E #9E9E9E #C8C9CA; - border-style: solid; - border-width: 1px; - font-size: 1.1em; - font-weight: bold; - color: #222222; - font-weight: bold; - text-shadow: 1px 1px #FFFFFF; - padding: 5px; - -moz-border-radius: 4px 4px 4px 4px; - margin-top:0; -} -table.details > tbody > tr > td { - padding-bottom: 5px; - padding-top: 3px; - padding-right: 5px; - border: 0; -} -.label { - font-weight: bold; -} -.caselog { - display:block; - width: 100%; -} -.caselog textarea { - resize: none; -} -.edit_item { - margin-bottom: 1em; -} -div.edit_item span div table { - width: 100%; -} -div.edit_item span div table tbody tr td textarea{ - width: 99%; -} -div#ticket_shortcuts form { - display: inline-block; -} -.cke_contents { - border: 1px #C8C9CA solid !important; -} \ No newline at end of file diff --git a/portal/readme.txt b/portal/readme.txt deleted file mode 100644 index 94bbf3d17..000000000 --- a/portal/readme.txt +++ /dev/null @@ -1,72 +0,0 @@ - ---- 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: comma separated list of search criteria (attcodes) for closed tickets -PORTAL_TICKETS_SEARCH_FILTER_attcode: an OQL query to limit the list of values available in the search form (drop-down list). One define per entry in PORTAL_TICKETS_SEARCH_CRITERIA - - -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 -PORTAL__DISPLAY_QUERY: selection of displayable objects (use parameters contact->attcode to check things against the user/contact) -PORTAL__DISPLAY_POWERUSER_QUERY: selection of displayable objects for power users (use parameters contact->attcode to check things against the user/contact) - - -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.