Compare commits

...

32 Commits

Author SHA1 Message Date
Denis Flaven
06e389f14c Fixed a potential XSS vulnerability.
SVN:2.0.1[3667]
2015-07-30 09:27:23 +00:00
Denis Flaven
e90f4e1232 #966 Prevent duplication of attachments when the form gets reloaded.
SVN:2.0.1[3294]
2014-07-30 08:24:35 +00:00
Denis Flaven
474df98dfa #966 (continue'd): Allow attachments of several files to the same ticket (the previous fix breaks after attaching 1 file)
SVN:2.0.1[3289]
2014-07-25 15:21:07 +00:00
Denis Flaven
ce88d10766 #966 Fix for a bug caused by Google Chrome version 36: the selected attachment was added several times (up to 10 or more!).
SVN:2.0.1[3283]
2014-07-21 15:43:57 +00:00
Denis Flaven
225eab9bd3 #966 Fix for a bug caused by Google Chrome version 36: the selected attachment was added several times (up to 10 or more!).
SVN:2.0.1[3282]
2014-07-21 15:43:34 +00:00
Denis Flaven
42b0a8c3cb Bug fix : missing semicolons were causing an error with IE9.
SVN:2.0.1[3109]
2014-03-26 15:01:44 +00:00
Denis Flaven
484fa42113 #870: when a user deletes all her/his shortcuts at once, this was deleting all the shortcuts for all users.
SVN:2.0.1[3101]
2014-03-12 14:09:15 +00:00
Denis Flaven
5c1149ac68 Protect the initialization with a try ... catch, in order to protect that rest of the page in case of trouble.
SVN:2.0.1[3076]
2014-02-05 17:11:55 +00:00
Romain Quetiez
1dbb94a2e1 Fix for the validation of mandatory fields in the portal (reintegrated from trunk)
SVN:2.0.1[3053]
2014-01-07 14:10:39 +00:00
Romain Quetiez
df1869dfe3 PHP Mail transport to allow 100% of recipients in BCC (proposed on github) -reintegrated from trunk
SVN:2.0.1[3046]
2013-12-13 10:30:21 +00:00
Denis Flaven
22ee1cd6f4 #856: allow asynchronous emails to have an empty 'to' recipient... (not used anyway)
SVN:2.0.1[3045]
2013-12-12 18:06:16 +00:00
Denis Flaven
0b14bf605c #795 Issue when using the actual (id) value of an external key as a reconciliation field
SVN:2.0.1[2917]
2013-10-16 08:49:53 +00:00
Denis Flaven
02717360d0 Show all types of Actions from the "Notifications/Actions" tab.
SVN:2.0.1[2912]
2013-10-15 12:01:20 +00:00
Denis Flaven
230710eb04 #791 Protect against single quotes in localized strings...
SVN:2.0.1[2910]
2013-10-14 17:11:30 +00:00
Denis Flaven
6581c02301 #793 provide the default '=' and '!=' operators for all types of Computed Fields.
SVN:2.0.1[2904]
2013-10-14 14:20:24 +00:00
Romain Quetiez
f925a21735 retrofit Change in the Notifications panel: show/create all types of Action (not only ActionEmail)
SVN:2.0.1[2897]
2013-10-11 14:22:17 +00:00
Denis Flaven
8796e5fa74 Retrofit the useful DoPostRequest function which was used (and defined) in several extensions.
SVN:2.0.1[2887]
2013-10-11 08:41:22 +00:00
Denis Flaven
249dd03b18 New pattern accepting the new global Top Level Domains (gTLD)
SVN:2.0.1[2868]
2013-09-27 07:31:21 +00:00
Denis Flaven
94ba2c3c1c Protect the deletion of objects with very long friendly names
SVN:2.0.1[2862]
2013-09-24 16:23:26 +00:00
Denis Flaven
7ec9022bd7 Allow for comparisons of the module's versions in the expression of dependencies. For example one can now say "itop-config-mgmt/>=2.0.2" for a dependency.
SVN:2.0.1[2854]
2013-09-23 13:22:02 +00:00
Romain Quetiez
7ba2cf59de Shortest fix ever!!!! Sort icons based on the class name (ksort => asort)
SVN:2.0.1[2851]
2013-09-13 10:53:19 +00:00
Romain Quetiez
fdacbf6d0f #763 Could not use "configure this list" once a stop watch has been added to the list, which is a pitty because such attributes are not aimed at being displayed in lists!
SVN:2.0.1[2849]
2013-09-11 09:59:23 +00:00
Romain Quetiez
580de372e0 #752 Notifications sent several times (or too late) when MySQL is hosted on another server
SVN:2.0.1[2830]
2013-08-23 07:36:20 +00:00
Denis Flaven
0faa33a0e5 Allow "Support Agents" to put an Incident in "Pending" state.
SVN:2.0.1[2815]
2013-08-01 08:28:41 +00:00
Denis Flaven
1aff819f8f #747 protect against the non-existence of UserRequest (since the module is not always installed)
SVN:2.0.1[2812]
2013-07-30 16:28:26 +00:00
Denis Flaven
0ac9fce207 #746 allow adding an AttributeBlob with is_null_allowed = true to an existing Data Model. (same issue fixed also for AttributeOneWayPassword).
SVN:2.0.1[2809]
2013-07-25 09:24:18 +00:00
Denis Flaven
8a8de2bcc3 Export the content of the CaseLogs in "spreadsheet" format, with some tricks to preserve the formatting in Excel.
SVN:2.0.1[2805]
2013-07-17 17:12:52 +00:00
Denis Flaven
3b4ff79ea3 Make the portal (slightly) more configurable
SVN:2.0.1[2798]
2013-07-12 14:02:13 +00:00
Romain Quetiez
0fcbc040fd Cosmetics on the user portal -reintegrated from trunk
SVN:2.0.1[2777]
2013-06-14 15:40:58 +00:00
Romain Quetiez
ae6e0c5242 #736 Could not delete objects unless you are authorized to bulk delete -reintegrated from trunk
SVN:2.0.1[2770]
2013-06-07 07:32:38 +00:00
Denis Flaven
1ce8046c46 #734 Fixed a regression on reconciliation keys during CSV import.
SVN:2.0.1[2762]
2013-05-29 08:57:35 +00:00
Romain Quetiez
56eba6de4c Created branch 2.0.1
SVN:2.0.1[2759]
2013-05-22 12:33:48 +00:00
31 changed files with 1346 additions and 1138 deletions

View File

@@ -240,6 +240,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$aStruct['last_synchro']."</p>";
}
$sSynchroIcon = '&nbsp;<img style="vertical-align:middle;" id="synchro_icon" src="../images/locked.png"/>';
$sTip = addslashes($sTip);
$oPage->add_ready_script("$('#synchro_icon').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
}
@@ -431,15 +432,28 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
if (count($aTriggers) > 0)
{
// Display notifications regarding the object
$iId = $this->GetKey();
$sTriggersList = implode(',', $aTriggers);
$oNotifSearch = DBObjectSearch::FromOQL("SELECT EventNotificationEmail AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN ($sTriggersList) AND Ev.object_id = $iId");
$oNotifSet = new DBObjectSet($oNotifSearch);
$sCount = ($oNotifSet->Count() > 0) ? ' ('.$oNotifSet->Count().')' : '';
$aNotifSearches = array();
$iNotifsCount = 0;
$aNotificationClasses = MetaModel::EnumChildClasses('EventNotification', ENUM_CHILD_CLASSES_EXCLUDETOP);
foreach($aNotificationClasses as $sNotifClass)
{
$aNotifSearches[$sNotifClass] = DBObjectSearch::FromOQL("SELECT $sNotifClass AS Ev JOIN Trigger AS T ON Ev.trigger_id = T.id WHERE T.id IN ($sTriggersList) AND Ev.object_id = $iId");
$oNotifSet = new DBObjectSet($aNotifSearches[$sNotifClass]);
$iNotifsCount += $oNotifSet->Count();
}
// Display notifications regarding the object: on block per subclass to have the intersting columns
$sCount = ($iNotifsCount > 0) ? ' ('.$iNotifsCount.')' : '';
$oPage->SetCurrentTab(Dict::S('UI:NotificationsTab').$sCount);
$oBlock = new DisplayBlock($oNotifSearch, 'list', false);
$oBlock->Display($oPage, 'notifications', array('menu' => false));
foreach($aNotificationClasses as $sNotifClass)
{
$oPage->p(MetaModel::GetClassIcon($sNotifClass, true).'&nbsp;'.MetaModel::GetName($sNotifClass));
$oBlock = new DisplayBlock($aNotifSearches[$sNotifClass], 'list', false);
$oBlock->Display($oPage, 'notifications_'.$sNotifClass, array('menu' => false));
}
}
}
}
@@ -1219,6 +1233,13 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$aRow[] = '<td>'.date('H:i:s', $iDate).'</td>';
}
}
else if($oAttDef instanceof AttributeCaseLog)
{
$rawValue = $oObj->Get($sAttCodeEx);
$outputValue = str_replace("\n", "<br/>", htmlentities($rawValue->__toString(), ENT_QUOTES, 'UTF-8'));
// Trick for Excel: treat the content as text even if it begins with an equal sign
$aRow[] = '<td x:str>'.$outputValue.'</td>';
}
else
{
$rawValue = $oObj->Get($sAttCodeEx);

View File

@@ -231,7 +231,7 @@ abstract class Dashboard
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
{
$oPage->add('<h1>'.Dict::S($this->sTitle).'</h1>');
$oPage->add('<h1>'.htmlentities(Dict::S($this->sTitle), ENT_QUOTES, 'UTF-8', false).'</h1>');
$oLayout = new $this->sLayoutClass;
$oLayout->Render($oPage, $this->aCells, $bEditMode, $aExtraParams);
if (!$bEditMode)

View File

@@ -1059,7 +1059,7 @@ class DashletBadge extends Dashlet
$oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']);
ksort($aClasses);
asort($aClasses);
$aValues = array();
foreach($aClasses as $sClass => $sClass)
{

View File

@@ -64,6 +64,7 @@ class PortalWebPage extends NiceWebPage
$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");

View File

@@ -15,6 +15,6 @@
</itoptab>
<itoptab name="UI:NotificationsMenu:Actions">
<h2><itopstring>UI:NotificationsMenu:AvailableActions</itopstring></h2>
<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT ActionEmail</itopblock>
<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql">SELECT Action</itopblock>
</itoptab>
</itoptabs>

View File

@@ -869,6 +869,60 @@ class utils
static public function GetSafeId($sId)
{
return str_replace(array(':', '[', ']', '+', '-'), '_', $sId);
}
}
/**
* Helper to execute an HTTP POST request
* Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
* originaly named after do_post_request
* Does not require cUrl but requires openssl for performing https POSTs.
*
* @param string $sUrl The URL to POST the data to
* @param hash $aData The data to POST as an array('param_name' => value)
* @param string $sOptionnalHeaders Additional HTTP headers as a string with newlines between headers
* @return string The result of the POST request
* @throws Exception
*/
static public function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null)
{
// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
$sData = http_build_query($aData);
$aParams = array('http' => array(
'method' => 'POST',
'content' => $sData,
'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
));
if ($sOptionnalHeaders !== null)
{
$aParams['http']['header'] .= $sOptionnalHeaders;
}
$ctx = stream_context_create($aParams);
$fp = @fopen($sUrl, 'rb', false, $ctx);
if (!$fp)
{
global $php_errormsg;
if (isset($php_errormsg))
{
throw new Exception("Wrong URL: $sUrl, $php_errormsg");
}
elseif ((strtolower(substr($sUrl, 0, 5)) == 'https') && !extension_loaded('openssl'))
{
throw new Exception("Cannot connect to $sUrl: missing module 'openssl'");
}
else
{
throw new Exception("Wrong URL: $sUrl");
}
}
$response = @stream_get_contents($fp);
if ($response === false)
{
throw new Exception("Problem reading data from $sUrl, $php_errormsg");
}
return $response;
}
}
?>

View File

@@ -140,7 +140,7 @@ class AsyncSendEmail extends AsyncTask
MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeInteger("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>Email::ORIGINAL_FORMAT, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeText("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeLongText("message", array("allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));

View File

@@ -2078,7 +2078,7 @@ class AttributeEmailAddress extends AttributeString
public function GetValidationPattern()
{
// return "^([0-9a-zA-Z]([-.\\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\\w]*[0-9a-zA-Z]\\.)+[a-zA-Z]{2,9})$";
return "^[a-zA-Z0-9._&-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$";
return "^[a-zA-Z0-9._&-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,}$";
}
public function GetAsHTML($sValue, $oHostObject = null, $bLocalize = true)
@@ -3360,26 +3360,26 @@ class AttributeBlob extends AttributeDefinition
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!isset($aCols[$sPrefix]))
if (!array_key_exists($sPrefix, $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$sMimeType = $aCols[$sPrefix];
$sMimeType = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
if (!isset($aCols[$sPrefix.'_data']))
if (!array_key_exists($sPrefix.'_data', $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_data' from {$sAvailable}");
}
$data = $aCols[$sPrefix.'_data'];
$data = isset($aCols[$sPrefix.'_data']) ? $aCols[$sPrefix.'_data'] : null;
if (!isset($aCols[$sPrefix.'_filename']))
if (!array_key_exists($sPrefix.'_filename', $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_filename' from {$sAvailable}");
}
$sFileName = $aCols[$sPrefix.'_filename'];
$sFileName = isset($aCols[$sPrefix.'_filename']) ? $aCols[$sPrefix.'_filename'] : '';
$value = new ormDocument($data, $sMimeType, $sFileName);
return $value;
@@ -4127,19 +4127,19 @@ class AttributeOneWayPassword extends AttributeDefinition
public function FromSQLToValue($aCols, $sPrefix = '')
{
if (!isset($aCols[$sPrefix]))
if (!array_key_exists($sPrefix, $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '$sPrefix' from {$sAvailable}");
}
$hashed = $aCols[$sPrefix];
$hashed = isset($aCols[$sPrefix]) ? $aCols[$sPrefix] : '';
if (!isset($aCols[$sPrefix.'_salt']))
if (!array_key_exists($sPrefix.'_salt', $aCols))
{
$sAvailable = implode(', ', array_keys($aCols));
throw new MissingColumnException("Missing column '".$sPrefix."_salt' from {$sAvailable}");
}
$sSalt = $aCols[$sPrefix.'_salt'];
$sSalt = isset($aCols[$sPrefix.'_salt']) ? $aCols[$sPrefix.'_salt'] : '';
$value = new ormPassword($hashed, $sSalt);
return $value;
@@ -4506,7 +4506,7 @@ class AttributeComputedFieldVoid extends AttributeDefinition
public function GetBasicFilterOperators()
{
return array();
return array("="=>"equals", "!="=>"differs from");
}
public function GetBasicFilterLooseOperator()
{

View File

@@ -296,10 +296,17 @@ class BulkChange
$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
{
// The foreign attribute is one of our reconciliation key
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
$oReconFilter->AddCondition($sForeignAttCode, $value);
if ($sForeignAttCode == 'id')
{
$value = (int) $aRowData[$iCol];
}
else
{
// The foreign attribute is one of our reconciliation key
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
$aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
}

View File

@@ -176,7 +176,7 @@ abstract class CMDBObject extends DBObject
$oMyChangeOp->Set("objclass", MetaModel::GetRootClass(get_class($this)));
$oMyChangeOp->Set("objkey", $objkey);
$oMyChangeOp->Set("fclass", get_class($this));
$oMyChangeOp->Set("fname", $this->GetRawName());
$oMyChangeOp->Set("fname", substr($this->GetRawName(), 0, 255)); // Protect against very long friendly names
$iId = $oMyChangeOp->DBInsertNoReload();
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2013 Combodo SARL
//
// This file is part of iTop.
//
@@ -162,8 +162,7 @@ class ormStopWatch
}
else
{
$iElapsedTemp = ''; //$this->ComputeDuration($oHostObject, $oAttDef, $this->iLastStart, time());
$aProperties['Elapsed'] = $this->iTimeSpent.' + '.$iElapsedTemp.' s + <img src="../images/indicator.gif">';
$aProperties['Elapsed'] = 'running <img src="../images/indicator.gif">';
}
$aProperties['Started'] = $oAttDef->SecondsToDate($this->iStarted);
@@ -183,7 +182,7 @@ class ormStopWatch
}
$aProperties[$iPercent.'%'] = $sThresholdDesc;
}
$sRes = "<TABLE class=\"listResults\">";
$sRes = "<TABLE>";
$sRes .= "<TBODY>";
foreach ($aProperties as $sProperty => $sValue)
{
@@ -387,9 +386,9 @@ class CheckStopWatchThresholds implements iBackgroundProcess
public function Process($iTimeLimit)
{
$aList = array();
foreach (MetaModel::GetClasses() as $sClass)
{
$aList = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
{
if ($oAttDef instanceof AttributeStopWatch)
@@ -398,8 +397,8 @@ class CheckStopWatchThresholds implements iBackgroundProcess
{
$iPercent = $aThresholdData['percent']; // could be different than the index !
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < NOW()";
//echo $sExpression."<br/>\n";
$sNow = date('Y-m-d H:i:s');
$sExpression = "SELECT $sClass WHERE {$sAttCode}_laststart AND {$sAttCode}_{$iThreshold}_triggered = 0 AND {$sAttCode}_{$iThreshold}_deadline < '$sNow'";
$oFilter = DBObjectSearch::FromOQL($sExpression);
$oSet = new DBObjectSet($oFilter);
while ((time() < $iTimeLimit) && ($oObj = $oSet->Fetch()))
@@ -407,7 +406,6 @@ class CheckStopWatchThresholds implements iBackgroundProcess
$sClass = get_class($oObj);
$aList[] = $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold;
//echo $sClass.'::'.$oObj->GetKey().' '.$sAttCode.' '.$iThreshold."\n";
// Execute planned actions
//
@@ -416,7 +414,6 @@ class CheckStopWatchThresholds implements iBackgroundProcess
$sVerb = $aActionData['verb'];
$aParams = $aActionData['params'];
$sParams = implode(', ', $aParams);
//echo "Calling: $sVerb($sParams)<br/>\n";
$aCallSpec = array($oObj, $sVerb);
call_user_func_array($aCallSpec, $aParams);
}
@@ -438,12 +435,12 @@ class CheckStopWatchThresholds implements iBackgroundProcess
// Activate any existing trigger
//
$sClassList = implode("', '", MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_ALL));
$oSet = new DBObjectSet(
$oTriggerSet = new DBObjectSet(
DBObjectSearch::FromOQL("SELECT TriggerOnThresholdReached AS t WHERE t.target_class IN ('$sClassList') AND stop_watch_code=:stop_watch_code AND threshold_index = :threshold_index"),
array(), // order by
array('stop_watch_code' => $sAttCode, 'threshold_index' => $iThreshold)
);
while ($oTrigger = $oSet->Fetch())
while ($oTrigger = $oTriggerSet->Fetch())
{
$oTrigger->DoActivate($oObj->ToArgs('this'));
}
@@ -454,9 +451,6 @@ class CheckStopWatchThresholds implements iBackgroundProcess
}
$iProcessed = count($aList);
return "Triggered $iProcessed threshold(s)";
return "Triggered $iProcessed threshold(s):".implode(", ", $aList);
}
}
?>

View File

@@ -350,7 +350,8 @@ EOF
$oPage->add('</span>');
$oPage->add('<div style="clear:both"></div>');
$sMaxUpload = $this->GetMaxUpload();
$oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file" onChange="ajaxFileUpload();"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
$oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
$oPage->add_ready_script('$("#file").off("change.itop-attachments").on("change.itop-attachments", function() {ajaxFileUpload();});'); // Workaround for a Chrome 36 bug causing multiple (12!) times the same upload. See http://www.redmine.org/issues/17151
$oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
$oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
$oPage->add('</fieldset>');

View File

@@ -33,4 +33,11 @@ define('PORTAL_REQUEST_FORM_ATTRIBUTES', 'title,description,impact,urgency,workg
define('PORTAL_ATTCODE_TYPE', ''); // optional if the type has to be set
define('PORTAL_SET_TYPE_FROM', ''); // The attribute to get the type from (Subcategory)
define('PORTAL_TICKETS_LIST_ZLIST', 'finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id');
define('PORTAL_TICKETS_SEARCH_CRITERIA','ref,start_date,close_date,service_id,caller_id');
define('PORTAL_TICKETS_CLOSED_ZLIST', 'title,start_date,close_date,servicesubcategory_id');
// json encoded lists for the portal...
define('PORTAL_TICKET_DETAILS_ZLIST', '{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}');
?>

View File

@@ -350,7 +350,8 @@ EOF
$oPage->add('</span>');
$oPage->add('<div style="clear:both"></div>');
$sMaxUpload = $this->GetMaxUpload();
$oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file" onChange="ajaxFileUpload();"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
$oPage->p(Dict::S('Attachments:AddAttachment').'<input type="file" name="file" id="file"><span style="display:none;" id="attachment_loading">&nbsp;<img src="../images/indicator.gif"></span> '.$sMaxUpload);
$oPage->add_ready_script('$("#file").off("change.itop-attachments").on("change.itop-attachments", function() {ajaxFileUpload();});'); // Workaround for a Chrome 36 bug causing multiple (12!) times the same upload. See http://www.redmine.org/issues/17151
$oPage->p('<span style="display:none;" id="attachment_loading">Loading, please wait...</span>');
$oPage->p('<input type="hidden" id="attachment_plugin" name="attachment_plugin"/>');
$oPage->add('</fieldset>');

View File

@@ -1452,28 +1452,31 @@
$sUserString = CMDBChange::GetCurrentUserName();
$oMyChange->Set("userinfo", $sUserString."(automatic resolution)");
$iChangeId = $oMyChange->DBInsert();
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
$oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL),
array(),
array(
'ticket' => $this->GetKey(),
)
);
//automatically resolve child requests
while($oRequest = $oChildRequestSet->Fetch())
{
if ( $oRequest->Get('status') != 'resolved')
{
$oRequest->set('servicesubcategory_id',$this->Get('servicesubcategory_id'));
$oRequest->set('service_id',$this->Get('service_id'));
$oRequest->set('team_id',$this->Get('team_id'));
$oRequest->set('agent_id',$this->Get('agent_id'));
$oRequest->set('resolution_code',$this->Get('resolution_code'));
$oRequest->set('solution','Automatically resolved by incident:[[Incident:'.$this->Get('ref').']]');
$oRequest->ApplyStimulus('ev_autoresolve');
$oRequest->DBUpdateTracked($oMyChange);
}
}
if (MetaModel::IsValidClass('UserRequest'))
{
$sOQL = "SELECT UserRequest WHERE parent_request_id=:ticket";
$oChildRequestSet = new DBObjectSet(DBObjectSearch::FromOQL($sOQL),
array(),
array(
'ticket' => $this->GetKey(),
)
);
//automatically resolve child requests
while($oRequest = $oChildRequestSet->Fetch())
{
if ( $oRequest->Get('status') != 'resolved')
{
$oRequest->set('servicesubcategory_id',$this->Get('servicesubcategory_id'));
$oRequest->set('service_id',$this->Get('service_id'));
$oRequest->set('team_id',$this->Get('team_id'));
$oRequest->set('agent_id',$this->Get('agent_id'));
$oRequest->set('resolution_code',$this->Get('resolution_code'));
$oRequest->set('solution','Automatically resolved by incident:[[Incident:'.$this->Get('ref').']]');
$oRequest->ApplyStimulus('ev_autoresolve');
$oRequest->DBUpdateTracked($oMyChange);
}
}
}
//automatically resolve child incidents
$sOQL = "SELECT Incident WHERE parent_incident_id=:ticket";
@@ -1507,6 +1510,8 @@
<type>LifecycleAction</type>
<code><![CDATA[ public function UpdateChildRequestLog()
{
if (!MetaModel::IsValidClass('UserRequest')) return true; // Do nothing
$sLogPublic = utils::ReadPostedParam('attr_public_log', null,false,'raw_data');
if ( $sLogPublic != null)
{

View File

@@ -315,6 +315,7 @@
<action id="ev_reassign" xsi:type="stimulus">allow</action>
<action id="ev_resolve" xsi:type="stimulus">allow</action>
<action id="ev_close" xsi:type="stimulus">allow</action>
<action id="ev_pending" xsi:type="stimulus">allow</action>
</actions>
</group>
<group id="class:UserRequest">

View File

@@ -33,4 +33,9 @@ define('PORTAL_REQUEST_FORM_ATTRIBUTES', 'title,description,impact,urgency');
define('PORTAL_ATTCODE_TYPE', ''); // optional if the type has to be set
define('PORTAL_SET_TYPE_FROM', ''); // The attribute to get the type from (Subcategory)
define('PORTAL_TICKETS_LIST_ZLIST', 'finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id');
define('PORTAL_TICKETS_SEARCH_CRITERIA','ref,start_date,close_date,service_id,caller_id');
define('PORTAL_TICKETS_CLOSED_ZLIST', 'title,start_date,close_date,servicesubcategory_id');
// json encoded lists for the portal...
define('PORTAL_TICKET_DETAILS_ZLIST', '{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}');
?>

View File

@@ -33,4 +33,9 @@ define('PORTAL_REQUEST_FORM_ATTRIBUTES', 'title,description,impact,urgency');
define('PORTAL_ATTCODE_TYPE', 'request_type'); // optional if the type has to be set
define('PORTAL_SET_TYPE_FROM', 'request_type'); // The attribute to get the type from (Subcategory)
define('PORTAL_TICKETS_LIST_ZLIST', 'finalclass,title,start_date,status,servicesubcategory_id,priority,caller_id');
define('PORTAL_TICKETS_SEARCH_CRITERIA','ref,start_date,close_date,service_id,caller_id');
define('PORTAL_TICKETS_CLOSED_ZLIST', 'title,start_date,close_date,servicesubcategory_id');
// json encoded lists for the portal...
define('PORTAL_TICKET_DETAILS_ZLIST', '{"col:left":["ref","caller_id","servicesubcategory_id","title","description","solution"],"col:right":["status","priority","start_date","resolution_date","last_update","agent_id"]}');
?>

View File

@@ -782,6 +782,7 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
'Portal:Refresh' => 'Neu laden',
'Portal:Back' => 'Zurück',
'Portal:WelcomeUserOrg' => 'Wilkommen %1$s, von %2$s',
'Portal:TitleDetailsFor_Request' => 'Deails für Benutzeranfrage',
'Portal:ShowOngoing' => 'Zeige offene Requests',
'Portal:ShowClosed' => 'Zeige geschlossene Requests',
'Portal:CreateNewRequest' => 'Einen neuen Request erstellen',

View File

@@ -754,7 +754,7 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Apply_Stimulus_On_Object_In_State_ToTarget_State' => 'Applying %1$s on object: %2$s in state %3$s to target state: %4$s.',
'UI:ObjectCouldNotBeWritten' => 'The object could not be written: %1$s',
'UI:PageTitle:FatalError' => 'iTop - Fatal Error',
'UI:SystemIntrusion' => 'Access denied. You have trying to perform an operation that is not allowed for you.',
'UI:SystemIntrusion' => 'Access denied. You have requested an operation that is not allowed for you.',
'UI:FatalErrorMessage' => 'Fatal error, iTop cannot continue.',
'UI:Error_Details' => 'Error: %1$s.',
@@ -943,6 +943,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Portal:Refresh' => 'Refresh',
'Portal:Back' => 'Back',
'Portal:WelcomeUserOrg' => 'Welcome %1$s, from %2$s',
'Portal:TitleDetailsFor_Request' => 'Details for request',
'Portal:ShowOngoing' => 'Show open requests',
'Portal:ShowClosed' => 'Show closed requests',
'Portal:CreateNewRequest' => 'Create a new request',

View File

@@ -943,6 +943,7 @@ Cuando se asocien con un disparador, cada acción recibe un número de "orden",
'Portal:Refresh' => 'Actualizar',
'Portal:Back' => 'Atrás',
'Portal:WelcomeUserOrg' => 'Bienvenido %1$s, de %2$s',
'Portal:TitleDetailsFor_Request' => 'Detalles de la Solicitud',
'Portal:ShowOngoing' => 'Mostrar Requerimientos Abiertos',
'Portal:ShowClosed' => 'Mostrar Requerimientos Cerrados',
'Portal:CreateNewRequest' => 'Crear Requerimiento',

View File

@@ -786,6 +786,7 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'Portal:Refresh' => 'Rafraîchir',
'Portal:Back' => 'Retour',
'Portal:WelcomeUserOrg' => 'Bienvenue %1$s (%2$s)',
'Portal:TitleDetailsFor_Request' => 'Détail de la requête',
'Portal:ShowOngoing' => 'Requêtes en cours',
'Portal:ShowClosed' => 'Requêtes fermées',
'Portal:CreateNewRequest' => 'Créer une nouvelle requête',

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,7 @@ jQuery.extend(
var fileId = 'jUploadFile' + id;
var form = jQuery('<form action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>');
var oldElement = jQuery('#' + fileElementId);
var newElement = jQuery(oldElement).clone();
var newElement = jQuery(oldElement).clone(true);
jQuery(oldElement).attr('id', fileId);
jQuery(oldElement).before(newElement);
jQuery(oldElement).appendTo(form);
@@ -131,12 +131,12 @@ jQuery.extend(
jQuery.handleError(s, xml, null, e);
}
}, 100)
}, 100);
xml = null
xml = null;
}
}
};
// Timeout checker
if ( s.timeout > 0 )
{
@@ -197,7 +197,9 @@ jQuery.extend({
s.error( xhr, status, e );
// If we have some XML response text (e.g. from an AJAX call) then log it in the console
else if(xhr.responseText)
console.log(xhr.responseText);
{
//console.log(xhr.responseText);
}
}
});

View File

@@ -438,59 +438,70 @@ function sprintf(format, etc) {
this.construct = function(settings) {
return this.each(function() {
config = $.extend(this.config, $.tablesorterPager.defaults, settings);
return this.each(function() {
var table = this, pager = config.container;
try
{
config = $.extend(this.config, $.tablesorterPager.defaults, settings);
var table = this, pager = config.container;
this.ajax_request = null;
this.ajax_request = null;
config.selectedSize = parseInt($(".pagesize",pager).val());
setPageSize(table,config.selectedSize, false);
restoreParams(table, config);
//$(this).trigger("appendCache"); // Load the data
//console.log($.tablesorterPager);
applySelection(table);
config.selectedSize = parseInt($(".pagesize",pager).val());
$('.gotopage',pager).click(function() {
var idx = $(this).attr('page');
table.config.page = idx;
moveToPage(table);
});
$(config.cssFirst,pager).click(function() {
moveToFirstPage(table);
return false;
});
$(config.cssNext,pager).click(function() {
moveToNextPage(table);
return false;
});
$(config.cssPrev,pager).click(function() {
moveToPrevPage(table);
return false;
});
$(config.cssLast,pager).click(function() {
moveToLastPage(table);
return false;
});
$(config.cssPageSize,pager).change(function() {
setPageSize(table,parseInt($(this).val()), true);
return false;
});
$(table).find(':checkbox.checkAll').removeAttr('onclick').click(function() {
return checkAll(table, pager, this.checked);
});
setPageSize(table,config.selectedSize, false);
restoreParams(table, config);
$(table).bind('load_selection', function() {
loadSelection(table, pager);
//$(this).trigger("appendCache"); // Load the data
//console.log($.tablesorterPager);
applySelection(table);
});
$(table).bind('check_all', function() {
checkAll(table, pager, true);
});
$('.gotopage',pager).click(function() {
var idx = $(this).attr('page');
table.config.page = idx;
moveToPage(table);
});
$(config.cssFirst,pager).click(function() {
moveToFirstPage(table);
return false;
});
$(config.cssNext,pager).click(function() {
moveToNextPage(table);
return false;
});
$(config.cssPrev,pager).click(function() {
moveToPrevPage(table);
return false;
});
$(config.cssLast,pager).click(function() {
moveToLastPage(table);
return false;
});
$(config.cssPageSize,pager).change(function() {
setPageSize(table,parseInt($(this).val()), true);
return false;
});
$(table).find(':checkbox.checkAll').removeAttr('onclick').click(function() {
return checkAll(table, pager, this.checked);
});
$(table).bind('load_selection', function() {
loadSelection(table, pager);
applySelection(table);
});
$(table).bind('check_all', function() {
checkAll(table, pager, true);
});
}
catch(err)
{
if (console && console.log)
{
console.log(err);
}
}
});
};
}

View File

@@ -126,13 +126,7 @@ class Swift_Transport_MailTransport implements Swift_Transport
$toHeader = $message->getHeaders()->get('To');
$subjectHeader = $message->getHeaders()->get('Subject');
if (!$toHeader)
{
throw new Swift_TransportException(
'Cannot send message without a recipient'
);
}
$to = $toHeader->getFieldBody();
$to = $toHeader ? $toHeader->getFieldBody() : '';
$subject = $subjectHeader ? $subjectHeader->getFieldBody() : '';
$reversePath = $this->_getReversePath($message);
@@ -143,7 +137,10 @@ class Swift_Transport_MailTransport implements Swift_Transport
$messageStr = $message->toString();
$message->getHeaders()->set($toHeader);
if ($toHeader)
{
$message->getHeaders()->set($toHeader);
}
$message->getHeaders()->set($subjectHeader);
//Separate headers from body

View File

@@ -938,9 +938,9 @@ try
$id = utils::ReadParam('id', '');
$oObj = MetaModel::GetObject($sClass, $id);
$aObjects[] = $oObj;
if (!UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, DBObjectSet::FromObject($oObj)))
if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromObject($oObj)))
{
throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClass));
throw new SecurityException(Dict::Format('UI:Error:DeleteNotAllowedOn_Class', $sClassLabel));
}
}
else
@@ -957,11 +957,21 @@ try
{
$aObjects[] = MetaModel::GetObject($sClass, $iId);
}
if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects)))
if (count($aObjects) == 1)
{
throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClass));
if (!UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, DBObjectSet::FromArray($sClass, $aObjects)))
{
throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel));
}
}
else
{
if (!UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, DBObjectSet::FromArray($sClass, $aObjects)))
{
throw new SecurityException(Dict::Format('UI:Error:BulkDeleteNotAllowedOn_Class', $sClassLabel));
}
$oP->set_title(Dict::S('UI:BulkDeletePageTitle'));
}
$oP->set_title(Dict::S('UI:BulkDeletePageTitle'));
}
// Go for the common part... (delete single, delete bulk, delete confirmed)
cmdbAbstractObject::DeleteObjects($oP, $sClass, $aObjects, ($operation != 'bulk_delete_confirmed'), 'bulk_delete_confirmed');

View File

@@ -936,6 +936,7 @@ EOF
case 'shortcut_delete_go':
$oSearch = new DBObjectSearch('Shortcut');
$oSearch->AddCondition('user_id', UserRights::GetUserId(), '=');
$aShortcuts = utils::ReadMultipleSelection($oSearch);
foreach ($aShortcuts as $iShortcut)
{

View File

@@ -84,33 +84,43 @@ function SelectServiceCategory($oP, $oUserOrg)
{
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS);
$oP->add("<div class=\"wizContainer\" id=\"form_select_service\">\n");
$oP->WizardFormStart('request_wizard', 1);
$oP->add("<h1 id=\"select_category\">".Dict::S('Portal:SelectService')."</h1>\n");
$oP->add("<table>\n");
$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()));
while($oService = $oSet->Fetch())
if ($oSet->Count() == 1)
{
$id = $oService->GetKey();
$sChecked = "";
if (isset($aParameters['service_id']) && ($id == $aParameters['service_id']))
{
$sChecked = "checked";
}
$oP->p("<tr><td style=\"vertical-align:top\"><p><input name=\"attr_service_id\" $sChecked type=\"radio\" id=\"service_$id\" value=\"$id\"></p></td><td style=\"vertical-align:top\"><p><b><label for=\"service_$id\">".$oService->GetName()."</label></b></p>");
$oP->p("<p>".$oService->GetAsHTML('description')."</p></td></tr>");
$oService = $oSet->Fetch();
$iSvcCategory = $oService->GetKey();
// Only one Category, skip this step in the wizard
SelectServiceSubCategory($oP, $oUserOrg, $iSvcCategory);
}
$oP->add("</table>\n");
else
{
$oP->add("<div class=\"wizContainer\" id=\"form_select_service\">\n");
$oP->WizardFormStart('request_wizard', 1);
$oP->DumpHiddenParams($aParameters, array('service_id'));
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
$oP->WizardFormButtons(BUTTON_BACK | BUTTON_NEXT | BUTTON_CANCEL);
$oP->WizardFormEnd();
$oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectOneService'));
$oP->add("</div>\n");
$oP->add("<h1 id=\"select_category\">".Dict::S('Portal:SelectService')."</h1>\n");
$oP->add("<table>\n");
while($oService = $oSet->Fetch())
{
$id = $oService->GetKey();
$sChecked = "";
if (isset($aParameters['service_id']) && ($id == $aParameters['service_id']))
{
$sChecked = "checked";
}
$oP->p("<tr><td style=\"vertical-align:top\"><p><input name=\"attr_service_id\" $sChecked type=\"radio\" id=\"service_$id\" value=\"$id\"></p></td><td style=\"vertical-align:top\"><p><b><label for=\"service_$id\">".$oService->GetName()."</label></b></p>");
$oP->p("<p>".$oService->GetAsHTML('description')."</p></td></tr>");
}
$oP->add("</table>\n");
$oP->DumpHiddenParams($aParameters, array('service_id'));
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
$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("</div>\n");
}
}
/**
@@ -118,14 +128,23 @@ function SelectServiceCategory($oP, $oUserOrg)
* 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)
function SelectServiceSubCategory($oP, $oUserOrg, $iSvcId = null)
{
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS);
$iSvcId = $aParameters['service_id'];
$iWizardButtons = 0;
if ($iSvcId == null)
{
$iSvcId = $aParameters['service_id'];
}
else
{
$aParameters['service_id'] = $iSvcId;
$iWizardButtons = BUTTON_NEXT;
}
$iDefaultSubSvcId = isset($aParameters['servicesubcategory_id']) ? $aParameters['servicesubcategory_id'] : 0;
$iDefaultWizNext = 2;
@@ -133,45 +152,56 @@ function SelectServiceSubCategory($oP, $oUserOrg)
$oSearch = DBObjectSearch::FromOQL(PORTAL_SERVICE_SUBCATEGORY_QUERY);
$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()));
$oServiceCategory = MetaModel::GetObject('Service', $iSvcId, false, true /* allow all data*/);
if (is_object($oServiceCategory))
if ($oSet->Count() == 1)
{
$oP->add("<div class=\"wizContainer\" id=\"form_select_servicesubcategory\">\n");
$oP->add("<h1 id=\"select_subcategory\">".Dict::Format('Portal:SelectSubcategoryFrom_Service', $oServiceCategory->GetName())."</h1>\n");
$oP->WizardFormStart('request_wizard', $iDefaultWizNext);
$oP->add("<table>\n");
while($oSubService = $oSet->Fetch())
{
$id = $oSubService->GetKey();
$sChecked = "";
if ($id == $iDefaultSubSvcId)
{
$sChecked = "checked";
}
$oP->add("<tr>");
$oP->add("<td style=\"vertical-align:top\">");
$oP->add("<p><input name=\"attr_servicesubcategory_id\" $sChecked type=\"radio\" id=\"servicesubcategory_$id\" value=\"$id\"></p>");
$oP->add("</td>");
$oP->add("<td style=\"vertical-align:top\">");
$oP->add("<p><b><label for=\"servicesubcategory_$id\">".$oSubService->GetName()."</label></b></p>");
$oP->add("<p>".$oSubService->GetAsHTML('description')."</p>");
$oP->add("</td>");
$oP->add("</tr>");
}
$oP->add("</table>\n");
$oP->DumpHiddenParams($aParameters, array('servicesubcategory_id'));
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
$oP->WizardFormButtons(BUTTON_BACK | BUTTON_NEXT | BUTTON_CANCEL);
$oP->WizardFormEnd();
$oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectAServiceSubCategory'));
$oP->add("</div>\n");
// Only one sub service, skip this step of the wizard
$oSubService = $oSet->Fetch();
$iSubSvdId = $oSubService->GetKey();
RequestCreationForm($oP, $oUserOrg, $iSvcId, $iSubSvdId);
}
else
{
$oP->p("Error: Invalid Service: id = $iSvcId");
$oServiceCategory = MetaModel::GetObject('Service', $iSvcId, false, true /* allow all data*/);
if (is_object($oServiceCategory))
{
$oP->add("<div class=\"wizContainer\" id=\"form_select_servicesubcategory\">\n");
$oP->add("<h1 id=\"select_subcategory\">".Dict::Format('Portal:SelectSubcategoryFrom_Service', $oServiceCategory->GetName())."</h1>\n");
$oP->WizardFormStart('request_wizard', $iDefaultWizNext);
$oP->add("<table>\n");
while($oSubService = $oSet->Fetch())
{
$id = $oSubService->GetKey();
$sChecked = "";
if ($id == $iDefaultSubSvcId)
{
$sChecked = "checked";
}
$oP->add("<tr>");
$oP->add("<td style=\"vertical-align:top\">");
$oP->add("<p><input name=\"attr_servicesubcategory_id\" $sChecked type=\"radio\" id=\"servicesubcategory_$id\" value=\"$id\"></p>");
$oP->add("</td>");
$oP->add("<td style=\"vertical-align:top\">");
$oP->add("<p><b><label for=\"servicesubcategory_$id\">".$oSubService->GetName()."</label></b></p>");
$oP->add("<p>".$oSubService->GetAsHTML('description')."</p>");
$oP->add("</td>");
$oP->add("</tr>");
}
$oP->add("</table>\n");
$oP->DumpHiddenParams($aParameters, array('servicesubcategory_id'));
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"create_request\">");
$iWizardButtons |= BUTTON_NEXT | BUTTON_CANCEL;
$oP->WizardFormButtons($iWizardButtons);
$oP->WizardFormEnd();
$oP->WizardCheckSelectionOnSubmit(Dict::S('Portal:PleaseSelectAServiceSubCategory'));
$oP->add("</div>\n");
}
else
{
$oP->p("Error: Invalid Service: id = $iSvcId");
}
}
}
@@ -179,9 +209,11 @@ function SelectServiceSubCategory($oP, $oUserOrg)
* Displays the form for the final step of the UserRequest 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 (in cal of fall through, when there is only one service)
* @param integer $iSubSvcId The identifier of the sub-service (in cal of fall through, when there is only one sub-service)
* @return void
*/
function RequestCreationForm($oP, $oUserOrg)
function RequestCreationForm($oP, $oUserOrg, $iSvcId = null, $iSubSvcId = null)
{
$oP->add_script(
<<<EOF
@@ -190,7 +222,15 @@ function RequestCreationForm($oP, $oUserOrg)
EOF
);
$aParameters = $oP->ReadAllParams(PORTAL_ALL_PARAMS);
if ($iSvcId != null)
{
$aParameters['service_id'] = $iSvcId;
}
if ($iSubSvcId != null)
{
$aParameters['servicesubcategory_id'] = $iSubSvcId;
}
// Example: $aList = array('title', 'description', 'impact', 'emergency');
$aList = explode(',', PORTAL_REQUEST_FORM_ATTRIBUTES);
@@ -418,7 +458,7 @@ function ListOpenRequests(WebPage $oP)
$oSearch->AddCondition('caller_id', $iUser);
}
$oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey()));
$aZList = array('finalclass', 'title', 'start_date', 'status', 'servicesubcategory_id', 'priority', 'caller_id');
$aZList = explode(',', PORTAL_TICKETS_LIST_ZLIST);
$oP->DisplaySet($oSet, $aZList, Dict::S('Portal:NoOpenRequest'));
}
@@ -439,7 +479,7 @@ function ListResolvedRequests(WebPage $oP)
$oSearch->AddCondition('caller_id', $iUser);
}
$oSet = new CMDBObjectSet($oSearch, array(), array('org_id' => $oUserOrg->GetKey()));
$aZList = array('finalclass', 'title', 'start_date', 'status', 'servicesubcategory_id', 'priority', 'caller_id');
$aZList = explode(',', PORTAL_TICKETS_LIST_ZLIST);
$oP->DisplaySet($oSet, $aZList, Dict::S('Portal:NoOpenRequest'));
}
@@ -450,8 +490,8 @@ function ListResolvedRequests(WebPage $oP)
*/
function ListClosedTickets(WebPage $oP)
{
$aAttSpecs = array('ref', 'start_date', 'close_date', 'service_id', 'caller_id');
$aZList = array('title', 'start_date', 'close_date', 'servicesubcategory_id');
$aAttSpecs = explode(',', PORTAL_TICKETS_SEARCH_CRITERIA);
$aZList = explode(',', PORTAL_TICKETS_CLOSED_ZLIST);
$oP->DisplaySearchForm('UserRequest', $aAttSpecs, array('operation' => 'show_closed'), 'search_', false /* => not closed */);
@@ -471,10 +511,8 @@ function ListClosedTickets(WebPage $oP)
$oSearch->AddCondition('caller_id', $iUser);
}
$oSet1 = new CMDBObjectSet($oSearch);
$oP->add("<p>\n");
$oP->add("<h1>".Dict::S('Portal:ClosedRequests')."</h1>\n");
$oP->DisplaySet($oSet1, $aZList, Dict::S('Portal:NoClosedRequest'));
$oP->add("</p>\n");
}
@@ -505,7 +543,7 @@ function DisplayObject($oP, $oObj, $oUserOrg)
* @return void
*/
function ShowDetailsRequest(WebPage $oP, $oObj)
{
{
$sClass = get_class($oObj);
$bIsEscalateButton = false;
@@ -559,7 +597,8 @@ function ShowDetailsRequest(WebPage $oP, $oObj)
switch($sClass)
{
case 'UserRequest':
$aAttList = array('col:left'=> array('ref','caller_id','servicesubcategory_id','title','description'),'col:right'=> array('status','priority','start_date','resolution_date','last_update','agent_id'));
$aAttList = json_decode(PORTAL_TICKET_DETAILS_ZLIST, true);
switch($oObj->GetState())
{
case 'closed':
@@ -879,11 +918,13 @@ try
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())
{
@@ -892,12 +933,14 @@ try
break;
case 'details':
$oP->set_title(Dict::S('Portal:TitleDetailsFor_Request'));
DisplayMainMenu($oP);
$oObj = $oP->FindObjectFromArgs(array('UserRequest'));
DisplayObject($oP, $oObj, $oUserOrg);
break;
case 'update_request':
$oP->set_title(Dict::S('Portal:TitleDetailsFor_Request'));
DisplayMainMenu($oP);
if (!MetaModel::DBIsReadOnly())
{
@@ -926,6 +969,7 @@ try
case 'show_ongoing':
default:
$oP->set_title(Dict::S('Portal:ShowOngoing'));
DisplayMainMenu($oP);
ShowOngoingTickets($oP);
}

View File

@@ -173,6 +173,20 @@ class ModuleDiscovery
protected static function DependencyIsResolved($sDepString, $aOrderedModules)
{
$bResult = false;
$aModuleVersions = array();
// Separate the module names from their version for an easier comparison later
foreach($aOrderedModules as $sModuleId)
{
if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches))
{
$aModuleVersions[$aMatches[1]] = $aMatches[2];
}
else
{
// No version number found, assume 1.0.0
$aModuleVersions[$sModuleId] = '1.0.0';
}
}
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches))
{
$aReplacements = array();
@@ -180,17 +194,38 @@ class ModuleDiscovery
{
foreach($aMatch as $sModuleId)
{
if (in_array($sModuleId, $aOrderedModules))
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
// where the operator is < <= = > >= (by default >=)
if(preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches))
{
// module is present
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
else
{
// module is not present
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
$sModuleName = $aModuleMatches[1];
$sOperator = $aModuleMatches[2];
if ($sOperator == '')
{
$sOperator = '>=';
}
$sExpectedVersion = $aModuleMatches[3];
if (array_key_exists($sModuleName, $aModuleVersions))
{
// module is present, check the version
$sCurrentVersion = $aModuleVersions[$sModuleName];
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator))
{
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
else
{
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
}
else
{
// module is not present
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
}
}
}

View File

@@ -237,6 +237,7 @@ if (!empty($sExpression))
header("Cache-control:", true);
$sFields = implode(',', $aFields);
$oP->add_style('table br {mso-data-placement:same-cell;}'); // Trick for Excel: keep line breaks inside the same cell !
cmdbAbstractObject::DisplaySetAsHTMLSpreadsheet($oP, $oSet, array('fields' => $sFields, 'fields_advanced' => $bFieldsAdvanced, 'localize_values' => $bLocalize));
break;