Compare commits

...

37 Commits

Author SHA1 Message Date
Denis Flaven
89e98a4ad7 (retrofit from trunk) Security: prevent grouping on password fields since it may lead to disclosure of the encrypted version of the password.
SVN:2.1.0[4247]
2016-06-22 14:01:45 +00:00
Denis Flaven
649a9833e9 (retrofit from trunk) Properly sanitize the "switch_env" parameter and take it into account only if it contains a valid value.
SVN:2.1.0[4241]
2016-06-22 12:16:20 +00:00
Romain Quetiez
b3c2396da8 #1209 Setup or Backup failing with french error message 'Effacement du fichier ...' Regression introduced in [r3868]. Occurs when a backup fails and prevents users from seeing the mysql error report.
SVN:2.1.0[4012]
2016-04-22 09:42:05 +00:00
Denis Flaven
5717ae1889 (retrofit from trunk) Optimization/bug (!): Never use the whole object as a placeholder in ApplyParams !!
SVN:2.1.0[3934]
2016-02-29 16:27:17 +00:00
Denis Flaven
823b33b0f5 Use one-way encryption for storing the token used for the "Forgotten password" feature.
SVN:2.1.0[3923]
2016-02-19 18:21:54 +00:00
Denis Flaven
9561ae6292 (retrofit from trunk) #1202: Fix for a security vulnerability in the Configuration Editor.
SVN:2.1.0[3906]
2016-02-11 10:32:53 +00:00
Romain Quetiez
e25a89dac1 #1174 Support HTML fields in the bulk modify form (capability to disable/enable) the field live (retrofit from trunk)
SVN:2.1.0[3893]
2016-02-01 15:22:49 +00:00
Romain Quetiez
cc7f3d46a6 #1165 backup with errors fills up tmp-directories with lots of backup-files (retrofit from trunk)
SVN:2.1.0[3892]
2016-02-01 14:57:54 +00:00
Denis Flaven
95d6b640b1 #1150: Spurious message "A restore is running..." - FIXED !
SVN:2.1.0[3867]
2016-01-20 16:08:12 +00:00
Denis Flaven
4ee0c62701 Support of derived classes in "add_remove" edition mode for AttributeLinkSet fields (the search form was not refreshing / loading properly when toggling the class to search for).
SVN:2.1.0[3825]
2015-11-20 14:29:30 +00:00
Denis Flaven
15f521d470 Make ReloadSearchForm work properly when the "submit" event handler is declared either with or without a "namespace" portion (e.g. 'submit.itop' vs 'submit') - retrofit from trunk
SVN:2.1.0[3819]
2015-11-09 10:52:45 +00:00
Romain Quetiez
86e6c8295f Instrumented the code to help in solving the "restore runing" issue. Retrofit from trunk of [3733] and [3595]: better error handling... check error.log!
SVN:2.1.0[3738]
2015-09-09 09:44:39 +00:00
Romain Quetiez
9f1338ee2a #1130 CAS authentication security leak when cas_memberof is left empty
SVN:2.1.0[3684]
2015-08-18 13:42:47 +00:00
Denis Flaven
96c8ee5e4d Retrofit of file based "transactions" as an alternative to session based ones.
SVN:2.1.0[3669]
2015-08-05 14:12:34 +00:00
Denis Flaven
8e863d4890 Fixed a potential XSS vulnerability.
SVN:2.1.0[3664]
2015-07-30 09:12:42 +00:00
Denis Flaven
cd769d5e49 Bug fix: typo causing the generation of invalid SQL queries (in some rare cases). - fix for the 2.1.0 branch.
SVN:2.1.0[3654]
2015-07-28 12:29:03 +00:00
Denis Flaven
04677fc2c7 Better error reporting (thanks to Stefan Goethals for suggesting it).
SVN:2.1.0[3624]
2015-07-06 17:07:07 +00:00
Denis Flaven
ed6a464d8b Bug fix: don't accept attachments (like images) via Chrome's copy/paste since it may duplicate the text content of a normal copy/paste and moreover causes troubles because there is no file name associated with the pasted content.
SVN:2.1.0[3622]
2015-07-06 14:33:21 +00:00
Denis Flaven
5794f7d1ca #1107: Make sure that all settings are preserved upon update.
SVN:2.1.0[3614]
2015-07-01 08:43:10 +00:00
Denis Flaven
7d42aa48cd Bug fix: make Excel export work on results of the global search.
SVN:2.1.0[3603]
2015-06-22 10:03:30 +00:00
Denis Flaven
614dd21aa9 Added an alternate implementation for storing "transaction" identifiers on disk instead of inside the $_SESSION variable.
SVN:2.1.0[3599]
2015-06-20 13:40:17 +00:00
Denis Flaven
706e6c56ff Mutex instrumentation for troubleshooting...
SVN:2.1.0[3596]
2015-06-19 14:48:13 +00:00
Romain Quetiez
f048c6cb1f JSON/REST: When specifying a case log entry (or the whole), it was not possible to set the user name without knowing a valid user id -retrofit from trunk
SVN:2.1.0[3594]
2015-06-16 09:51:51 +00:00
Romain Quetiez
1804903ee4 Make sure that the SQL mutexes are specific to the current iTop instance, but still preserving the capability for the setup to detect an already running cron job with or without a valid config file -reintegrated from trunk
SVN:2.1.0[3592]
2015-06-15 09:55:31 +00:00
Denis Flaven
17a8a896dc Bug fix: do NOT display the call stack in case of error/exception since it may contain sensitive information.
SVN:2.1.0[3589]
2015-06-05 13:52:07 +00:00
Romain Quetiez
ed85260bb3 Implemented GetForJSON and FromJSONToValue for AttributeLinkedSet (though this is not used for the Rest/JSON services which are doing much more)
SVN:2.1.0[3586]
2015-05-26 11:56:14 +00:00
Romain Quetiez
3c7fac7504 Make it possible to overload RestUtils (static methods called with static:: instead of self::) - iTop NOW REQUIRES PHP 5.3: we have verified, there are very installations of iTop made on PHP 5.2. It is worth to note that PHP 5.3 is already end of life (5.4 will become end of life in 8 months) -retrofit from trunk
SVN:2.1.0[3585]
2015-05-26 11:46:49 +00:00
Romain Quetiez
93feb700e8 ormStopWatch::GetElapsedTime not working in case of queries containing :this-> parameters (the prototype of GetElapsedTime has changed and is NOT compatible with the previous one) -reintegrated from trunk
SVN:2.1.0[3565]
2015-04-27 09:27:00 +00:00
Denis Flaven
48456082a9 Bug fix: prevent a crash of the web services when trying to log a non scalar paramater value...
SVN:2.1.0[3551]
2015-04-16 15:36:09 +00:00
Romain Quetiez
fbc0456496 Modules implementing a lifecycle written in PHP (and having actions executed on transitions) do not work until 2.1.0. The compatibility patch had been implemented but it was not working. Retrofitted from trunk
SVN:2.1.0[3548]
2015-04-16 13:53:02 +00:00
Denis Flaven
0fc3ee12b1 #1088: support of HTMLEditor in the PortalWebPage, for example if the description of a ticket is in HTML.
SVN:2.1.0[3540]
2015-04-07 15:56:16 +00:00
Denis Flaven
ce5f8c93cd Bug fix: properly compute the URLs/URIs for the soap server (and its extensions)
SVN:2.1.0[3537]
2015-04-07 09:59:53 +00:00
Denis Flaven
b38eb97323 Enhancement: allow the API to create entries with a specified user_login.
SVN:2.1.0[3516]
2015-03-24 17:09:51 +00:00
Denis Flaven
e6eb32cd21 #594: properly display attachments inside "properties" by closing the span and the fieldset in non-edit mode.
SVN:2.1.0[3512]
2015-03-23 17:56:40 +00:00
Denis Flaven
c2429f2d05 - Properly handle "suggested" attachments
- Properly pass the name of the uploaded file to the internal JS event

SVN:2.1.0[3497]
2015-02-12 18:03:36 +00:00
Romain Quetiez
4e4f2b4db9 #1060 Internal: improved the symptoms when calling MetaModel::GetAttributeDef with an invalid attribute code (feedback on the class name and no more FATAL errors) -retrofit from trunk
SVN:2.1.0[3493]
2015-02-09 13:15:21 +00:00
Romain Quetiez
2d7f963316 Branching on the revision used to build the official release 2.1.0
SVN:2.1.0[3485]
2015-01-06 08:57:15 +00:00
39 changed files with 736 additions and 215 deletions

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -781,8 +781,12 @@ class RestUtils
$oSearch = new DBObjectSearch($sClass);
foreach ($oCriteria as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
if (is_object($value) || is_array($value))
{
$value = json_encode($value);
}
$aCriteriaReport[] = "$sAttCode: $value ($realValue)";
}
$oSet = new DBObjectSet($oSearch);
@@ -814,7 +818,7 @@ class RestUtils
{
if (is_object($key))
{
$res = self::FindObjectFromCriteria($sClass, $key);
$res = static::FindObjectFromCriteria($sClass, $key);
}
elseif (is_numeric($key))
{
@@ -878,7 +882,7 @@ class RestUtils
$oSearch = new DBObjectSearch($sClass);
foreach ($key as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
$oSearch->AddCondition($sAttCode, $realValue, '=');
}
}
@@ -922,7 +926,7 @@ class RestUtils
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if ($oAttDef instanceof AttributeExternalKey)
{
$oExtKeyObject = self::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$oExtKeyObject = static::FindObjectFromKey($oAttDef->GetTargetClass(), $value, true /* allow null */);
$value = ($oExtKeyObject != null) ? $oExtKeyObject->GetKey() : 0;
}
elseif ($oAttDef instanceof AttributeLinkedSet)
@@ -935,7 +939,7 @@ class RestUtils
$aLinks = array();
foreach($value as $oValues)
{
$oLnk = self::MakeObjectFromFields($sLnkClass, $oValues);
$oLnk = static::MakeObjectFromFields($sLnkClass, $oValues);
$aLinks[] = $oLnk;
}
$value = DBObjectSet::FromArray($sLnkClass, $aLinks);
@@ -966,7 +970,7 @@ class RestUtils
$oObject = MetaModel::NewObject($sClass);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);
@@ -993,7 +997,7 @@ class RestUtils
$sClass = get_class($oObject);
foreach ($aFields as $sAttCode => $value)
{
$realValue = self::MakeValue($sClass, $sAttCode, $value);
$realValue = static::MakeValue($sClass, $sAttCode, $value);
try
{
$oObject->Set($sAttCode, $realValue);

View File

@@ -312,7 +312,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

@@ -733,7 +733,8 @@ abstract class DashletGroupBy extends Dashlet
if (is_subclass_of($sAttType, 'AttributeFriendlyName')) continue;
if ($sAttType == 'AttributeExternalField') continue;
if (is_subclass_of($sAttType, 'AttributeExternalField')) continue;
if ($sAttType == 'AttributeOneWayPassword') continue;
$sLabel = $this->oModelReflection->GetLabel($sClass, $sAttCode);
$aGroupBy[$sAttCode] = $sLabel;

View File

@@ -393,7 +393,7 @@ class DisplayBlock
{
if (isset($aExtraParams['group_by_label']))
{
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$oGroupByExp = Expression::FromOQL($aExtraParams['group_by']);
$sGroupByLabel = $aExtraParams['group_by_label'];
}
else
@@ -404,6 +404,21 @@ class DisplayBlock
$sGroupByLabel = MetaModel::GetLabel($this->m_oFilter->GetClass(), $aExtraParams['group_by']);
}
// Security filtering
$aFields = $oGroupByExp->ListRequiredFields();
foreach($aFields as $sFieldAlias)
{
if (preg_match('/^([^.]+)\\.([^.]+)$/', $sFieldAlias, $aMatches))
{
$sFieldClass = $this->m_oFilter->GetClassName($aMatches[1]);
$oAttDef = MetaModel::GetAttributeDef($sFieldClass, $aMatches[2]);
if ($oAttDef instanceof AttributeOneWayPassword)
{
throw new Exception('Grouping on password fields is not supported.');
}
}
}
$aGroupBy = array();
$aGroupBy['grouped_by_1'] = $oGroupByExp;
$sSql = MetaModel::MakeGroupByQuery($this->m_oFilter, $aQueryParams, $aGroupBy, true);

View File

@@ -305,16 +305,20 @@ class LoginWebPage extends NiceWebPage
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
elseif ($oUser->Get('reset_pwd_token') != $sToken)
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
$oEncryptedToken = $oUser->Get('reset_pwd_token');
if (!$oEncryptedToken->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-EnterPassword', $oUser->GetFriendlyName())."</p>\n");
$sInconsistenPwdMsg = Dict::S('UI:Login:RetypePwdDoesNotMatch');
$this->add_script(
<<<EOF
function DoCheckPwd()
{
@@ -326,18 +330,19 @@ function DoCheckPwd()
return true;
}
EOF
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
);
$this->add("<form method=\"post\">\n");
$this->add("<table>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"new_pwd\">".Dict::S('UI:Login:NewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"new_pwd\" name=\"new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td style=\"text-align:right\"><label for=\"retype_new_pwd\">".Dict::S('UI:Login:RetypeNewPasswordPrompt').":</label></td><td style=\"text-align:left\"><input type=\"password\" id=\"retype_new_pwd\" name=\"retype_new_pwd\" value=\"\" /></td></tr>\n");
$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"><span class=\"btn_border\"><input type=\"submit\" onClick=\"return DoCheckPwd();\" value=\"".Dict::S('UI:Button:ChangePassword')."\" /></span></td></tr>\n");
$this->add("</table>\n");
$this->add("<input type=\"hidden\" name=\"loginop\" value=\"do_reset_pwd\" />\n");
$this->add("<input type=\"hidden\" name=\"auth_user\" value=\"".htmlentities($sAuthUser, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("<input type=\"hidden\" name=\"token\" value=\"".htmlentities($sToken, ENT_QUOTES, 'UTF-8')."\" />\n");
$this->add("</form>\n");
$this->add("</div\n");
}
}
}
@@ -357,21 +362,25 @@ EOF
{
$this->add("<p>".Dict::Format('UI:ResetPwd-Error-WrongLogin', $sAuthUser)."</p>\n");
}
elseif ($oUser->Get('reset_pwd_token') != $sToken)
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
$oEncryptedPassword = $oUser->Get('reset_pwd_token');
if (!$oEncryptedPassword->CheckPassword($sToken))
{
$this->add("<p>".Dict::S('UI:ResetPwd-Error-InvalidToken')."</p>\n");
}
else
{
// Trash the token and change the password
$oUser->Set('reset_pwd_token', '');
$oUser->SetPassword($sNewPwd); // Does record the change into the DB
$this->add("<p>".Dict::S('UI:ResetPwd-Ready')."</p>");
$sUrl = utils::GetAbsoluteUrlAppRoot();
$this->add("<p><a href=\"$sUrl\">".Dict::S('UI:ResetPwd-Login')."</a></p>");
}
$this->add("</div\n");
}
$this->add("</div\n");
}
public function DisplayChangePwdForm($bFailedLogin = false)

View File

@@ -90,6 +90,8 @@ class PortalWebPage extends NiceWebPage
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script('../js/jquery.multiselect.min.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_ready_script(
<<<EOF
try
@@ -231,6 +233,20 @@ EOF
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
);

View File

@@ -28,10 +28,11 @@ require_once(APPROOT.'/core/cmdbobject.class.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
session_name('itop-'.md5(APPROOT));
session_start();
if (isset($_REQUEST['switch_env']))
$sSwitchEnv = utils::ReadParam('switch_env', null);
if (($sSwitchEnv != null) && (file_exists(APPCONF.$sSwitchEnv.'/'.ITOP_CONFIG_FILE)))
{
$sEnv = $_REQUEST['switch_env'];
$_SESSION['itop_env'] = $sEnv;
$_SESSION['itop_env'] = $sSwitchEnv;
$sEnv = $sSwitchEnv;
// TODO: reset the credentials as well ??
}
else if (isset($_SESSION['itop_env']))

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,8 @@
/**
* This class records the pending "transactions" corresponding to forms that have not been
* submitted yet, in order to prevent double submissions. When created a transaction remains valid
* until the user's session expires
* until the user's session expires. This class is actually a wrapper to the underlying implementation
* which choice is configured via the parameter 'transaction_storage'
*
* @package iTop
* @copyright Copyright (C) 2010-2012 Combodo SARL
@@ -28,6 +29,81 @@
class privUITransaction
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return 'notransactions'; // Any value will do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
IssueLog::Error("Incorrect value '".MetaModel::GetConfig()->Get('transaction_storage')."' for 'transaction_storage', the class '$sClass' does not exists. Using privUITransactionSession instead for storing sessions.");
$sClass = 'privUITransactionSession';
}
return (string)$sClass::GetNewTransactionId();
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return true; // All values are valid
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
return $sClass::IsTransactionValid($id, $bRemoveTransaction);
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTranscationId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bTransactionsEnabled = MetaModel::GetConfig()->Get('transactions_enabled');
if (!$bTransactionsEnabled)
{
return; // Nothing to do
}
$sClass = 'privUITransaction'.MetaModel::GetConfig()->Get('transaction_storage');
if (!class_exists($sClass, false))
{
$sClass = 'privUITransactionSession';
}
$sClass::RemoveTransaction($id);
}
}
/**
* The original (and by default) mechanism for storing transaction information
* as an array in the $_SESSION variable
*
*/
class privUITransactionSession
{
/**
* Create a new transaction id, store it in the session and return its id
@@ -99,4 +175,178 @@ class privUITransaction
}
}
}
?>
/**
* An alternate implementation for storing the transactions as temporary files
* Useful when using an in-memory storage for the session which do not
* guarantee mutual exclusion for writing
*/
class privUITransactionFile
{
/**
* Create a new transaction id, store it in the session and return its id
* @param void
* @return int The identifier of the new transaction
*/
public static function GetNewTransactionId()
{
if (!is_dir(APPROOT.'data/transactions'))
{
if (!is_writable(APPROOT.'data'))
{
throw new Exception('The directory "'.APPROOT.'data" must be writable to the application.');
}
if (!@mkdir(APPROOT.'data/transactions'))
{
throw new Exception('Failed to create the directory "'.APPROOT.'data/transactions". Ajust the rights on the parent directory or let an administrator create the transactions directory and give the web sever enough rights to write into it.');
}
}
if (!is_writable(APPROOT.'data/transactions'))
{
throw new Exception('The directory "'.APPROOT.'data/transactions" must be writable to the application.');
}
self::CleanupOldTransactions();
$id = basename(tempnam(APPROOT.'data/transactions', self::GetUserPrefix()));
self::Info('GetNewTransactionId: Created transaction: '.$id);
return (string)$id;
}
/**
* Check whether a transaction is valid or not and (optionally) remove the valid transaction from
* the session so that another call to IsTransactionValid for the same transaction id
* will return false
* @param int $id Identifier of the transaction, as returned by GetNewTransactionId
* @param bool $bRemoveTransaction True if the transaction must be removed
* @return bool True if the transaction is valid, false otherwise
*/
public static function IsTransactionValid($id, $bRemoveTransaction = true)
{
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
$bResult = file_exists($sFilepath);
if ($bResult)
{
if ($bRemoveTransaction)
{
$bResult = @unlink($sFilepath);
if (!$bResult)
{
self::Error('IsTransactionValid: FAILED to remove transaction '.$id);
}
else
{
self::Info('IsTransactionValid: OK. Removed transaction: '.$id);
}
}
}
else
{
self::Info("IsTransactionValid: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
return $bResult;
}
/**
* Removes the transaction specified by its id
* @param int $id The Identifier (as returned by GetNewTransactionId) of the transaction to be removed.
* @return void
*/
public static function RemoveTransaction($id)
{
$bSuccess = true;
$sFilepath = APPROOT.'data/transactions/'.$id;
clearstatcache(true, $sFilepath);
if(!file_exists($sFilepath))
{
$bSuccess = false;
self::Error("RemoveTransaction: Transaction '$id' not found. Pending transactions for this user:\n".implode("\n", self::GetPendingTransactions()));
}
$bSuccess = @unlink($sFilepath);
if (!$bSuccess)
{
self::Error('RemoveTransaction: FAILED to remove transaction '.$id);
}
else
{
self::Info('RemoveTransaction: OK '.$id);
}
return $bSuccess;
}
/**
* Cleanup old transactions which have been pending since more than 24 hours
* Use filemtime instead of filectime since filectime may be affected by operations on the directory (like changing the access rights)
*/
protected static function CleanupOldTransactions()
{
$iLimit = time() - 24*3600;
clearstatcache();
$aTransactions = glob(APPROOT.'data/transactions/*-*');
foreach($aTransactions as $sFileName)
{
if (filemtime($sFileName) < $iLimit)
{
@unlink($sFileName);
self::Info('CleanupOldTransactions: Deleted transaction: '.$sFileName);
}
}
}
/**
* For debugging purposes: gets the pending transactions of the current user
* as an array, with the date of the creation of the transaction file
*/
protected static function GetPendingTransactions()
{
clearstatcache();
$aResult = array();
$aTransactions = glob(APPROOT.'data/transactions/'.self::GetUserPrefix().'*');
foreach($aTransactions as $sFileName)
{
$aResult[] = date('Y-m-d H:i:s', filemtime($sFileName)).' - '.basename($sFileName);
}
sort($aResult);
return $aResult;
}
protected static function GetUserPrefix()
{
$sPrefix = substr(UserRights::GetUser(), 0, 10);
$sPrefix = preg_replace('/[^a-zA-Z0-9-_]/', '_', $sPrefix);
return $sPrefix.'-';
}
protected static function Info($sText)
{
self::Write('Info | '.$sText);
}
protected static function Warning($sText)
{
self::Write('Warning | '.$sText);
}
protected static function Error($sText)
{
self::Write('Error | '.$sText);
}
protected static function Write($sText)
{
$bLogEnabled = MetaModel::GetConfig()->Get('log_transactions');
if ($bLogEnabled)
{
$hLogFile = @fopen(APPROOT.'log/transactions.log', 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}
}
}

View File

@@ -80,7 +80,7 @@ class UIHTMLEditorWidget
// Could also be bound to 'instanceReady.ckeditor'
$oPage->add_ready_script("$('#$iId').bind('validate', function(evt, sFormId) { return ValidateCKEditField('$iId', '', {$this->m_sMandatory}, sFormId, '') } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
return $sHtmlValue;
}

View File

@@ -294,7 +294,7 @@ class UILinksWidgetDirect
$valuesDef = $oLinksetDef->GetValuesDef();
if ($valuesDef === null)
{
$oFilter = new DBObjectSearch($this->sLinkedClass);
$oFilter = new DBObjectSearch($sRemoteClass);
}
else
{

View File

@@ -892,6 +892,106 @@ class AttributeLinkedSet extends AttributeDefinition
return $oSet;
}
/**
* Helper to get a value that will be JSON encoded
* The operation is the opposite to FromJSONToValue
*/
public function GetForJSON($value)
{
$aRet = array();
if (is_object($value) && ($value instanceof DBObjectSet))
{
$value->Rewind();
while ($oObj = $value->Fetch())
{
$sObjClass = get_class($oObj);
// Show only relevant information (hide the external key to the current object)
$aAttributes = array();
foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
{
if ($sAttCode == 'finalclass')
{
if ($sObjClass == $this->GetLinkedClass())
{
// Simplify the output if the exact class could be determined implicitely
continue;
}
}
if ($sAttCode == $this->GetExtKeyToMe()) continue;
if ($oAttDef->IsExternalField()) continue;
if (!$oAttDef->IsDirectField()) continue;
if (!$oAttDef->IsScalar()) continue;
$attValue = $oObj->Get($sAttCode);
$aAttributes[$sAttCode] = $oAttDef->GetForJSON($attValue);
}
$aRet[] = $aAttributes;
}
}
return $aRet;
}
/**
* Helper to form a value, given JSON decoded data
* The operation is the opposite to GetForJSON
*/
public function FromJSONToValue($json)
{
$sTargetClass = $this->Get('linked_class');
$aLinks = array();
foreach($json as $aValues)
{
if (isset($aValues['finalclass']))
{
$sLinkClass = $aValues['finalclass'];
if (!is_subclass_of($sLinkClass, $sTargetClass))
{
throw new CoreException('Wrong class for link attribute specification', array('requested_class' => $sLinkClass, 'expected_class' => $sTargetClass));
}
}
elseif (MetaModel::IsAbstract($sTargetClass))
{
throw new CoreException('Missing finalclass for link attribute specification');
}
else
{
$sLinkClass = $sTargetClass;
}
$oLink = MetaModel::NewObject($sLinkClass);
foreach ($aValues as $sAttCode => $sValue)
{
$oLink->Set($sAttCode, $sValue);
}
// Check (roughly) if such a link is valid
$aErrors = array();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sAttCode => $oAttDef)
{
if ($oAttDef->IsExternalKey())
{
if (($oAttDef->GetTargetClass() == $this->GetHostClass()) || (is_subclass_of($this->GetHostClass(), $oAttDef->GetTargetClass())))
{
continue; // Don't check the key to self
}
}
if ($oAttDef->IsWritable() && $oAttDef->IsNull($oLink->Get($sAttCode)) && !$oAttDef->IsNullAllowed())
{
$aErrors[] = $sAttCode;
}
}
if (count($aErrors) > 0)
{
throw new CoreException("Missing value for mandatory attribute(s): ".implode(', ', $aErrors));
}
$aLinks[] = $oLink;
}
$oSet = DBObjectSet::FromArray($sTargetClass, $aLinks);
return $oSet;
}
public function Equals($val1, $val2)
{
if ($val1 === $val2) return true;

View File

@@ -809,6 +809,30 @@ class Config
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transaction_storage' => array(
'type' => 'string',
'description' => 'The type of mechanism to use for storing the unique identifiers for transactions (Session|File).',
'default' => 'Session',
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'transactions_enabled' => array(
'type' => 'bool',
'description' => 'Whether or not the whole mechanism to prevent multiple submissions of a page is enabled.',
'default' => true,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
'log_transactions' => array(
'type' => 'bool',
'description' => 'Whether or not to enable the debug log for the transactions.',
'default' => false,
'value' => '',
'source_of_value' => '',
'show_in_conf_sample' => false,
),
);
public function IsProperty($sPropCode)
@@ -1462,6 +1486,8 @@ class Config
$aSettings['log_notification'] = $this->m_bLogNotification;
$aSettings['log_issue'] = $this->m_bLogIssue;
$aSettings['log_web_service'] = $this->m_bLogWebService;
$aSettings['log_queries'] = $this->m_bLogQueries;
$aSettings['query_cache_enabled'] = $this->m_bQueryCacheEnabled;
$aSettings['min_display_limit'] = $this->m_iMinDisplayLimit;
$aSettings['max_display_limit'] = $this->m_iMaxDisplayLimit;
$aSettings['standard_reload_interval'] = $this->m_iStandardReloadInterval;
@@ -1469,6 +1495,7 @@ class Config
$aSettings['secure_connection_required'] = $this->m_bSecureConnectionRequired;
$aSettings['default_language'] = $this->m_sDefaultLanguage;
$aSettings['allowed_login_types'] = $this->m_sAllowedLoginTypes;
$aSettings['ext_auth_variable'] = $this->m_sExtAuthVariable;
$aSettings['encryption_key'] = $this->m_sEncryptionKey;
$aSettings['csv_import_charsets'] = $this->m_aCharsets;
@@ -1534,6 +1561,8 @@ class Config
'log_notification' => $this->m_bLogNotification,
'log_issue' => $this->m_bLogIssue,
'log_web_service' => $this->m_bLogWebService,
'log_queries' => $this->m_bLogQueries,
'query_cache_enabled' => $this->m_bQueryCacheEnabled,
'secure_connection_required' => $this->m_bSecureConnectionRequired,
);
foreach($aBoolValues as $sKey => $bValue)
@@ -1572,6 +1601,7 @@ class Config
'db_collation' => $this->m_sDBCollation,
'default_language' => $this->m_sDefaultLanguage,
'allowed_login_types' => $this->m_sAllowedLoginTypes,
'ext_auth_variable' => $this->m_sExtAuthVariable,
'encryption_key' => $this->m_sEncryptionKey,
'csv_import_charsets' => $this->m_aCharsets,
);

View File

@@ -2088,12 +2088,12 @@ abstract class DBObject implements iDisplay
{
if (is_string($actionHandler))
{
// Old (pre-2.1.0) action definition without any parameter
$aActionCallSpec = array($this, $sActionHandler);
// Old (pre-2.1.0 modules) action definition without any parameter
$aActionCallSpec = array($this, $actionHandler);
if (!is_callable($aActionCallSpec))
{
throw new CoreException("Unable to call action: ".get_class($this)."::$sActionHandler");
throw new CoreException("Unable to call action: ".get_class($this)."::$actionHandler");
return;
}
$bRet = call_user_func($aActionCallSpec, $sStimulusCode);

View File

@@ -53,9 +53,8 @@ class DBObjectSearch
public function __construct($sClass, $sClassAlias = null)
{
if (is_null($sClassAlias)) $sClassAlias = $sClass;
assert('is_string($sClass)');
assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference
// => idee d'un assert avec call stack (autre utilisation = echec sur query SQL)
if(!is_string($sClass)) throw new Exception('DBObjectSearch::__construct called with a non-string parameter: $sClass = '.print_r($sClass, true));
if(!MetaModel::IsValidClass($sClass)) throw new Exception('DBObjectSearch::__construct called for an invalid class: "'.$sClass.'"');
$this->m_aSelectedClasses = array($sClassAlias => $sClass);
$this->m_aClasses = array($sClassAlias => $sClass);
@@ -449,7 +448,7 @@ class DBObjectSearch
public function AddCondition($sFilterCode, $value, $sOpCode = null)
{
MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
MyHelpers::CheckKeyInArray('filter code in class: '.$this->GetClass(), $sFilterCode, MetaModel::GetClassFilterDefs($this->GetClass()));
$oFilterDef = MetaModel::GetClassFilterDef($this->GetClass(), $sFilterCode);
$oField = new FieldExpression($sFilterCode, $this->GetClassAlias());

View File

@@ -59,8 +59,11 @@ class FileLog
$hLogFile = @fopen($this->m_sFile, 'a');
if ($hLogFile !== false)
{
flock($hLogFile, LOCK_EX);
$sDate = date('Y-m-d H:i:s');
fwrite($hLogFile, "$sDate | $sText\n");
fflush($hLogFile);
flock($hLogFile, LOCK_UN);
fclose($hLogFile);
}
}

View File

@@ -836,6 +836,10 @@ abstract class MetaModel
final static public function GetAttributeDef($sClass, $sAttCode)
{
self::_check_subclass($sClass);
if (!isset(self::$m_aAttribDefs[$sClass][$sAttCode]))
{
throw new Exception("Unknown attribute $sAttCode from class $sClass");
}
return self::$m_aAttribDefs[$sClass][$sAttCode];
}
@@ -3075,7 +3079,7 @@ abstract class MetaModel
$sExtAttCode = $oAtt->GetExtAttCode();
// Translate mainclass.extfield => remoteclassalias.remotefieldcode
$oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode);
foreach ($oRemoteAttDef->GetSQLExpressions() as $sColID => $sRemoteAttExpr)
foreach ($oRemoteAttDef->GetSQLExpressions() as $sColId => $sRemoteAttExpr)
{
$aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);
//echo "<p><b>aTranslateNow[$sTargetAlias][$sAttCode.$sColId] = new FieldExpression($sExtAttCode, $sKeyClassAlias);</b></p>\n";
@@ -5428,9 +5432,11 @@ abstract class MetaModel
{
// Some environment parameters are objects, we just need scalars
if (is_object($replace)) continue;
$aSearches[] = '$'.$sSearch.'$';
$aReplacements[] = (string) $replace;
else
{
$aSearches[] = '$'.$sSearch.'$';
$aReplacements[] = (string) $replace;
}
}
return str_replace($aSearches, $aReplacements, $aInput);
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2013 Combodo SARL
// Copyright (C) 2013-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -24,7 +24,7 @@
* Relies on MySQL locks because the API sem_get is not always present in the
* installed PHP.
*
* @copyright Copyright (C) 2013 Combodo SARL
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class iTopMutex
@@ -37,8 +37,19 @@ class iTopMutex
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
{
// Compute the name of a lock for mysql
// Note: the name is server-wide!!!
// Note: names are server-wide!!! So let's make the name specific to this iTop instance
$oConfig = utils::GetConfig(); // Will return an empty config when called during the setup
$sDBName = $oConfig->GetDBName();
$sDBSubname = $oConfig->GetDBSubname();
$this->sName = 'itop.'.$sName;
if (substr($sName, -strlen($sDBName.$sDBSubname)) != $sDBName.$sDBSubname)
{
// If the name supplied already ends with the expected suffix
// don't add it twice, since the setup may try to detect an already
// running cron job by its mutex, without knowing if the config already exists or not
$this->sName .= $sDBName.$sDBSubname;
}
$this->bLocked = false; // Not yet locked
if (!array_key_exists($this->sName, self::$aAcquiredLocks))
@@ -48,7 +59,6 @@ class iTopMutex
// It is a MUST to create a dedicated session each time a lock is required, because
// using GET_LOCK anytime on the same session will RELEASE the current and unique session lock (known issue)
$oConfig = utils::GetConfig();
$sDBHost = is_null($sDBHost) ? $oConfig->GetDBHost() : $sDBHost;
$sDBUser = is_null($sDBUser) ? $oConfig->GetDBUser() : $sDBUser;
$sDBPwd = is_null($sDBPwd) ? $oConfig->GetDBPwd() : $sDBPwd;
@@ -121,7 +131,43 @@ class iTopMutex
$this->bLocked = true;
self::$aAcquiredLocks[$this->sName]++;
}
return ($res === '1');
if (($res !== '1') && ($res !== '0'))
{
$sMsg = 'GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null';
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
return ($res !== '0');
}
/**
* Check if the mutex is locked WITHOUT TRYING TO ACQUIRE IT
* @returns bool True if the mutex is in use, false otherwise
*/
public function IsLocked()
{
if ($this->bLocked)
{
return true; // Already acquired
}
if (self::$aAcquiredLocks[$this->sName] > 0)
{
return true;
}
$res = $this->QueryToScalar("SELECT IS_FREE_LOCK('".$this->sName."')"); // IS_FREE_LOCK detects some error cases that IS_USED_LOCK do not detect
if (is_null($res))
{
$sMsg = "MySQL Error, IS_FREE_LOCK('".$this->sName."') returned null. Error (".mysqli_errno($this->hDBLink).") = '".mysqli_error($this->hDBLink)."'";
IssueLog::Error($sMsg);
throw new Exception($sMsg);
}
else if ($res == '1')
{
// Lock is free
return false;
}
return true;
}
/**

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -23,7 +23,7 @@ define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\
/**
* Class to store a "case log" in a structured way, keeping track of its successive entries
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2015 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormCaseLog {
@@ -384,7 +384,7 @@ class ormCaseLog {
}
public function AddLogEntryFromJSON($oJson)
public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
{
$sText = isset($oJson->message) ? $oJson->message : '';
@@ -394,16 +394,24 @@ class ormCaseLog {
{
throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
}
try
if ($bCheckUserId && ($oJson->user_id != 0))
{
$oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
try
{
$oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
}
catch(Exception $e)
{
throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
}
$iUserId = $oUser->GetKey();
$sOnBehalfOf = $oUser->GetFriendlyName();
}
catch(Exception $e)
else
{
throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
$iUserId = $oJson->user_id;
$sOnBehalfOf = $oJson->user_login;
}
$iUserId = $oUser->GetKey();
$sOnBehalfOf = $oUser->GetFriendlyName();
}
else
{

View File

@@ -86,8 +86,9 @@ class ormStopWatch
* Get the working elapsed time since the start of the stop watch
* even if it is currently running
* @param oAttDef AttributeDefinition Attribute hosting the stop watch
* @param oObject Hosting object (used for query parameters)
*/
public function GetElapsedTime($oAttDef)
public function GetElapsedTime($oAttDef, $oObject)
{
if (is_null($this->iLastStart))
{
@@ -95,7 +96,7 @@ class ormStopWatch
}
else
{
$iElapsed = $this->ComputeDuration($this, $oAttDef, $this->iLastStart, time());
$iElapsed = $this->ComputeDuration($oObject, $oAttDef, $this->iLastStart, time());
return $this->iTimeSpent + $iElapsed;
}
}

View File

@@ -400,7 +400,7 @@ abstract class UserInternal extends User
MetaModel::Init_InheritAttributes();
// When set, this token allows for password reset
MetaModel::Init_AddAttribute(new AttributeString("reset_pwd_token", array("allowed_values"=>null, "sql"=>"reset_pwd_token", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeOneWayPassword("reset_pwd_token", array("allowed_values"=>null, "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
// Display lists
MetaModel::Init_SetZListItems('details', array('contactid', 'first_name', 'email', 'login', 'language', 'profile_list', 'allowed_org_list')); // Attributes to be displayed for the complete details
@@ -1303,8 +1303,9 @@ class CAS_SelfRegister implements iSelfRegister
}
else
{
// No membership required, anybody will pass
$bFound = true;
// No membership: no way to create the user that should exist prior to authentication
phpCAS::log("User ".phpCAS::getUser().": missing user account in iTop (or iTop badly configured, Cf setting cas_memberof)");
$bFound = false;
}
if (!$bFound)

View File

@@ -206,6 +206,11 @@ legend.transparent {
padding-left:14px;
background: url(../images/mini-arrow-orange.gif) no-repeat left;
}
.ui-widget-content td a.cke_toolbox_collapser {
padding-left: 0;
}
p a:hover, td a:hover {
text-decoration:underline;
color:#EB8F00;

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2015 Combodo SARL
//
// This file is part of iTop.
//
@@ -250,6 +250,7 @@ EOF
$('#attachment_plugin').trigger('remove_attachment', [att_id]);
return false; // Do not submit the form !
}
function ajaxFileUpload()
{
//starting setting some animation when the ajax starts and completes
@@ -347,7 +348,7 @@ EOF
$sIcon = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$sDownloadLink = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=download_document&class=Attachment&id='.$iAttId.'&field=contents';
$sPreview = $oDoc->IsPreviewAvailable() ? 'true' : 'false';
$oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'+data.result.att_id+'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
$oPage->add('<div class="attachment" id="display_attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'<input id="attachment_'.$iAttId.'" type="hidden" name="attachments[]" value="'.$iAttId.'"/></a><br/>&nbsp;<input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/>&nbsp;</div>');
$oPage->add_ready_script("$('#attachment_plugin').trigger('add_attachment', [$iAttId, '".addslashes($sFileName)."']);");
}
}
@@ -368,6 +369,7 @@ $oPage->add_ready_script(
url: GetAbsoluteUrlModulesRoot()+'itop-attachments/ajax.attachment.php',
formData: { operation: 'add', temp_id: '$sTempId', obj_class: '$sClass' },
dataType: 'json',
pasteZone: null, // Don't accept files via Chrome's copy/paste
done: function (e, data) {
if(typeof(data.result.error) != 'undefined')
{
@@ -383,7 +385,7 @@ $oPage->add_ready_script(
{
$('#display_attachment_'+data.result.att_id).hover( function() { $(this).children(':button').toggleClass('btn_hidden'); } );
}
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.msg]);
$('#attachment_plugin').trigger('add_attachment', [data.result.att_id, data.result.msg]);
}
}
},
@@ -440,7 +442,6 @@ EOF
);
$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>');
if ($this->m_bDeleteEnabled)
{
$oPage->add_ready_script('$(".attachment").hover( function() {$(this).children(":button").toggleClass("btn_hidden"); } );');
@@ -466,7 +467,9 @@ EOF
$oPage->add('<div class="attachment" id="attachment_'.$iAttId.'"><a data-preview="'.$sPreview.'" href="'.$sDownloadLink.'"><img src="'.$sIcon.'"><br/>'.$sFileName.'</a><input type="hidden" name="attachments[]" value="'.$iAttId.'"/><br/>&nbsp;&nbsp;</div>');
}
}
$oPage->add('</span>');
}
$oPage->add('</fieldset>');
$sPreviewNotAvailable = addslashes(Dict::S('Attachments:PreviewNotAvailable'));
$iMaxWidth = MetaModel::GetModuleSetting('itop-attachments', 'preview_max_width', 290);
$oPage->add_ready_script("$(document).tooltip({ items: '.attachment a', position: { my: 'left top', at: 'right top', using: function( position, feedback ) { $( this ).css( position ); }}, content: function() { if ($(this).attr('data-preview') == 'true') { return('<img style=\"max-width:{$iMaxWidth}px\" src=\"'+$(this).attr('href')+'\"></img>');} else { return '$sPreviewNotAvailable'; }}});");

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2014 Combodo SARL
// Copyright (C) 2013-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Backup from an interactive session
*
* @copyright Copyright (C) 2013 Combodo SARL
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -77,9 +77,8 @@ try
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
if ($oRestoreMutex->TryLock())
if (!$oRestoreMutex->IsLocked())
{
$oRestoreMutex->Unlock();
$sFile = utils::ReadParam('file', '', false, 'raw_data');
$sToken = str_replace(' ', '', (string)microtime());
$sTokenFile = APPROOT.'/data/restore.'.$sToken.'.tok';
@@ -119,7 +118,9 @@ EOF
{
$sEnvironment = utils::ReadParam('environment', 'production', false, 'raw_data');
$oRestoreMutex = new iTopMutex('restore.'.$sEnvironment);
IssueLog::Info("Backup Restore - Acquiring the LOCK 'restore.$sEnvironment'");
$oRestoreMutex->Lock();
IssueLog::Info('Backup Restore - LOCK acquired, executing...');
try
{
set_time_limit(0);
@@ -144,6 +145,7 @@ EOF
$sBackupFile = $sBackupDir.$sFile;
$sRes = $oDBRS->RestoreFromZip($sBackupFile, $sEnvironment);
IssueLog::Info('Backup Restore - Done, releasing the LOCK');
$oRestoreMutex->Unlock();
}
catch (Exception $e)

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2014 Combodo SARL
// Copyright (C) 2014-2016 Combodo SARL
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -26,35 +26,19 @@ class BackupHandler extends ModuleHandlerAPI
{
public static function OnMetaModelStarted()
{
try
{
$oBackupMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
if ($oBackupMutex->TryLock())
{
$oBackupMutex->Unlock();
}
else
{
// Not needed: the DB dump is done in a single transaction
//MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
//MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-backup-running'), 'itop-backup');
}
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->TryLock())
{
$oRestoreMutex->Unlock();
}
else
if ($oRestoreMutex->IsLocked())
{
IssueLog::Info(__class__.'::'.__function__.' A user is trying to use iTop while a restore is running. The requested page is in read-only mode.');
MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-restore-running'), 'itop-backup');
}
}
catch(Exception $e)
{
IssueLog::Error(__class__.'::'.__function__.' Failed to check if a backup/restore is running: '.$e->getMessage());
}
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2014 Combodo SARL
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -20,7 +20,7 @@
/**
* Monitor the backup
*
* @copyright Copyright (C) 2013 Combodo SARL
* @copyright Copyright (C) 2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -169,14 +169,13 @@ try
}
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->TryLock())
if ($oRestoreMutex->IsLocked())
{
$oRestoreMutex->Unlock();
$sDisableRestore = '';
$sDisableRestore = 'disabled="disabled"';
}
else
{
$sDisableRestore = 'disabled="disabled"';
$sDisableRestore = '';
}
// 1st table: list the backups made in the background
@@ -271,20 +270,12 @@ try
// Ongoing operation ?
//
$oBackupMutex = new iTopMutex('backup.'.utils::GetCurrentEnvironment());
if ($oBackupMutex->TryLock())
{
$oBackupMutex->Unlock();
}
else
if ($oBackupMutex->IsLocked())
{
$oP->p(Dict::S('bkp-backup-running'));
}
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
if ($oRestoreMutex->TryLock())
{
$oRestoreMutex->Unlock();
}
else
if ($oRestoreMutex->IsLocked())
{
$oP->p(Dict::S('bkp-restore-running'));
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2014 Combodo SARL
// Copyright (C) 2014-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -105,6 +105,10 @@ try
{
$oP->add("<div class=\"header_message message_info\">Sorry, iTop is in <b>demonstration mode</b>: the configuration file cannot be edited.</div>");
}
if (MetaModel::GetModuleSetting('itop-config', 'config_editor', '') == 'disabled')
{
$oP->add("<div class=\"header_message message_info\">iTop interactive edition of the configuration as been disabled. See <tt>'config_editor' => 'disabled'</tt> in the configuration file.</div>");
}
else
{
$oP->add_style(
@@ -129,27 +133,50 @@ EOF
if ($sOperation == 'save')
{
$sConfig = utils::ReadParam('new_config', '', false, 'raw_data');
$sTransactionId = utils::ReadParam('transaction_id', '');
$sOrginalConfig = utils::ReadParam('prev_config', '', false, 'raw_data');
if ($sConfig == $sOrginalConfig)
if (!utils::IsTransactionValid($sTransactionId, true))
{
$oP->add('<div id="save_result" class="header_message">'.Dict::S('config-no-change').'</div>');
$oP->add("<div class=\"header_message message_info\">Error: invalid Transaction ID. The configuration was <b>NOT</b> modified.</div>");
}
else
{
try
if ($sConfig == $sOrginalConfig)
{
TestConfig($sConfig, $oP); // throws exceptions
@chmod($sConfigFile, 0770); // Allow overwriting the file
file_put_contents($sConfigFile, $sConfig);
@chmod($sConfigFile, 0444); // Read-only
$oP->p('<div id="save_result" class="header_message message_ok">'.Dict::S('Successfully recorded.').'</div>');
$sOrginalConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
$oP->add('<div id="save_result" class="header_message">'.Dict::S('config-no-change').'</div>');
}
catch (Exception $e)
else
{
$oP->p('<div id="save_result" class="header_message message_error">'.$e->getMessage().'</div>');
try
{
TestConfig($sConfig, $oP); // throws exceptions
@chmod($sConfigFile, 0770); // Allow overwriting the file
$sTmpFile = tempnam(SetupUtils::GetTmpDir(), 'itop-cfg-');
// Don't write the file as-is since it would allow to inject any kind of PHP code.
// Instead write the interpreted version of the file
// Note:
// The actual raw PHP code will anyhow be interpreted exactly twice: once in TestConfig() above
// and a second time during the load of the Config object below.
// If you are really concerned about an iTop administrator crafting some malicious
// PHP code inside the config file, then turn off the interactive configuration
// editor by adding the configuration parameter:
// 'itop-config' => array(
// 'config_editor' => 'disabled',
// )
file_put_contents($sTmpFile, $sConfig);
$oTempConfig = new Config($sTmpFile, true);
$oTempConfig->WriteToFile($sConfigFile);
@unlink($sTmpFile);
@chmod($sConfigFile, 0444); // Read-only
$oP->p('<div id="save_result" class="header_message message_ok">'.Dict::S('Successfully recorded.').'</div>');
$sOrginalConfig = str_replace("\r\n", "\n", file_get_contents($sConfigFile));
}
catch (Exception $e)
{
$oP->p('<div id="save_result" class="header_message message_error">'.$e->getMessage().'</div>');
}
}
}
}
@@ -164,6 +191,7 @@ EOF
$oP->p(Dict::S('config-edit-intro'));
$oP->add("<form method=\"POST\">");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"save\">");
$oP->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">");
$oP->add("<input type=\"submit\" value=\"".Dict::S('config-apply')."\"><button onclick=\"ResetConfig(); return false;\">".Dict::S('config-cancel')."</button>");
$oP->add("<span class=\"current_line\">".Dict::Format('config-current-line', "<span class=\"line_number\"></span>")."</span>");
$oP->add("<input type=\"hidden\" id=\"prev_config\" name=\"prev_config\" value=\"$sOriginalConfigEscaped\">");

View File

@@ -230,25 +230,32 @@ function ValidateCKEditField(sFieldId, sPattern, bMandatory, sFormId, nullValue)
var bValid;
var sTextContent;
// Get the contents without the tags
var oFormattedContents = $("#cke_"+sFieldId+" iframe");
if (oFormattedContents.length == 0)
if ($('#'+sFieldId).attr('disabled'))
{
var oSourceContents = $("#cke_"+sFieldId+" textarea.cke_source");
sTextContent = oSourceContents.val();
bValid = true; // disabled fields are not checked
}
else
{
sTextContent = oFormattedContents.contents().find("body").text();
}
if (bMandatory && (sTextContent == ''))
{
bValid = false;
}
else
{
bValid = true;
// Get the contents without the tags
var oFormattedContents = $("#cke_"+sFieldId+" iframe");
if (oFormattedContents.length == 0)
{
var oSourceContents = $("#cke_"+sFieldId+" textarea.cke_source");
sTextContent = oSourceContents.val();
}
else
{
sTextContent = oFormattedContents.contents().find("body").text();
}
if (bMandatory && (sTextContent == ''))
{
bValid = false;
}
else
{
bValid = true;
}
}
ReportFieldValidationStatus(sFieldId, sFormId, bValid, '');

View File

@@ -238,8 +238,12 @@ $(function()
}
);
oParams.operation = 'searchObjectsToAdd2';
oParams['class'] = this.options.class_name;
oParams.real_class = '';
if ((oParams['class'] != undefined) && (oParams['class'] != ''))
{
oParams.real_class = oParams['class'];
}
oParams['class'] = this.options.class_name;
oParams.att_code = this.options.att_code;
oParams.iInputId = this.id;
if (this.options.oWizardHelper)

View File

@@ -200,13 +200,18 @@ function ReloadSearchForm(divId, sClassName, sBaseClass, sContext)
for(var index = 0; index < aSubmit.length; index++)
{
// Restore the previously bound submit handlers
var sEventName = 'submit';
if ((aSubmit[index].namespace != undefined) && (aSubmit[index].namespace != ''))
{
sEventName += '.'+aSubmit[index].namespace;
}
if (aSubmit[index].data != undefined)
{
oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].data, aSubmit[index].handler)
oForm.bind(sEventName, aSubmit[index].data, aSubmit[index].handler)
}
else
{
oForm.bind('submit.'+aSubmit[index].namespace, aSubmit[index].handler)
oForm.bind(sEventName, aSubmit[index].handler)
}
}
}

View File

@@ -601,6 +601,7 @@ try
if ($iErrors == 0)
{
$oP->set_title(Dict::S('UI:SearchResultsPageTitle'));
$oP->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/xlsx-export.js'); // Since the results are loaded asynchronously they don't do this on their own
$oP->add("<div style=\"padding: 10px;\">\n");
$oP->add("<div class=\"header_message\" id=\"full_text_progress\" style=\"position: fixed; background-color: #cccccc; opacity: 0.7; padding: 1.5em;\">\n");
$oP->add('<img id="full_text_indicator" src="../images/indicator.gif">&nbsp;<span style="padding: 1.5em;">'.Dict::Format('UI:Search:Ongoing', htmlentities($sFullText, ENT_QUOTES, 'UTF-8')).'</span>');
@@ -1654,4 +1655,4 @@ catch(Exception $e)
IssueLog::Error($e->getMessage());
}
}
?>
?>

View File

@@ -1732,8 +1732,7 @@ catch (Exception $e)
{
// note: transform to cope with XSS attacks
echo htmlentities($e->GetMessage(), ENT_QUOTES, 'utf-8');
echo "<p>Debug trace: <pre>".$e->getTraceAsString()."</pre></p>\n";
IssueLog::Error($e->getMessage());
IssueLog::Error($e->getMessage()."\nDebug trace:\n".$e->getTraceAsString()); // Do NOT display the call stack since it may contain sensitive information
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -224,6 +224,12 @@ class DBBackup
}
if ($iRetCode != 0)
{
// Cleanup residual output (Happens with Error 2020: Got packet bigger than 'maxallowedpacket' bytes...)
if (file_exists($sBackupFileName))
{
unlink($sBackupFileName);
}
$this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
foreach($aOutput as $sLine)
{

View File

@@ -50,7 +50,7 @@ class CheckResult
class SetupUtils
{
const PHP_MIN_VERSION = '5.2.0';
const PHP_MIN_VERSION = '5.3.0';
const MYSQL_MIN_VERSION = '5.0.0';
const MIN_MEMORY_LIMIT = 33554432; // = 32*1024*1024 Beware: Computations are not allowed in defining constants
const SUHOSIN_GET_MAX_VALUE_LENGTH = 2048;

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2012 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -18,7 +18,7 @@
/**
* All the steps of the iTop installation wizard
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -618,16 +618,12 @@ EOF
);
$oMutex = new iTopMutex(
'cron.'.$this->oWizard->GetParameter('db_name', '').'_'.$this->oWizard->GetParameter('db_prefix', ''),
'cron'.$this->oWizard->GetParameter('db_name', '').$this->oWizard->GetParameter('db_prefix', ''),
$this->oWizard->GetParameter('db_server', ''),
$this->oWizard->GetParameter('db_user', ''),
$this->oWizard->GetParameter('db_pwd', '')
);
if ($oMutex->TryLock())
{
$oMutex->Unlock();
}
else
if ($oMutex->IsLocked())
{
$oPage->p("<img src=\"../images/error.png\"/>&nbsp;An iTop CRON process is being executed on the target database. It is highly recommended to stop any iTop CRON process prior to running the setup program.");
}

View File

@@ -468,7 +468,7 @@ try
// Prepare insert columns
$sInsertColumns = '`'.implode('`, `', $aInputColumns).'`';
$oMutex = new iTopMutex('synchro_import_'.$oDataSource->GetKey().'_'.MetaModel::GetConfig()->GetDBName().'_'.MetaModel::GetConfig()->GetDBSubname());
$oMutex = new iTopMutex('synchro_import_'.$oDataSource->GetKey());
$oMutex->Lock();
foreach($aData as $iRow => $aRow)
{

View File

@@ -2415,7 +2415,7 @@ class SynchroExecution
self::$m_oCurrentTask = $this->m_oDataSource;
$oMutex = new iTopMutex('synchro_process_'.$this->m_oDataSource->GetKey().'_'.MetaModel::GetConfig()->GetDBName().'_'.MetaModel::GetConfig()->GetDBSubname());
$oMutex = new iTopMutex('synchro_process_'.$this->m_oDataSource->GetKey());
try
{
$oMutex->Lock();

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,7 +19,7 @@
/**
* Heart beat of the application (process asynchron tasks such as broadcasting email)
*
* @copyright Copyright (C) 2010-2013 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -71,49 +71,49 @@ function UsageAndExit($oP)
function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
{
$oNow = new DateTime();
$fStart = microtime(true);
try
{
$oNow = new DateTime();
$fStart = microtime(true);
$sMessage = $oProcess->Process($iTimeLimit);
$fDuration = microtime(true) - $fStart;
if ($oTask->Get('total_exec_count') == 0)
{
// First execution
$oTask->Set('first_run_date', $oNow->format('Y-m-d H:i:s'));
}
$oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics
$oTask->Set('latest_run_date', $oNow->format('Y-m-d H:i:s'));
$oRefClass = new ReflectionClass(get_class($oProcess));
if ($oRefClass->implementsInterface('iScheduledProcess'))
{
// Schedules process do repeat at specific moments
$oPlannedStart = $oProcess->GetNextOccurrence();
}
else
{
// Background processes do repeat periodically
$oPlannedStart = new DateTime($oTask->Get('latest_run_date'));
// Let's assume that the task was started exactly when planned so that the schedule does no shift each time
// this allows to schedule a task everyday "around" 11:30PM for example
$oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
$oEnd = new DateTime();
if ($oPlannedStart->format('U') < $oEnd->format('U'))
{
// Huh, next planned start is already in the past, shift it of the periodicity !
$oPlannedStart = $oEnd->modify('+'.$oProcess->GetPeriodicity().' seconds');
}
}
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
$oTask->DBUpdate();
}
catch(Exception $e)
{
$sMessage = 'Processing failed, the following exception occured: '.$e->getMessage();
$sMessage = 'Processing failed with message: '.$e->getMessage();
}
return $sMessage;
$fDuration = microtime(true) - $fStart;
if ($oTask->Get('total_exec_count') == 0)
{
// First execution
$oTask->Set('first_run_date', $oNow->format('Y-m-d H:i:s'));
}
$oTask->ComputeDurations($fDuration); // does increment the counter and compute statistics
$oTask->Set('latest_run_date', $oNow->format('Y-m-d H:i:s'));
$oRefClass = new ReflectionClass(get_class($oProcess));
if ($oRefClass->implementsInterface('iScheduledProcess'))
{
// Schedules process do repeat at specific moments
$oPlannedStart = $oProcess->GetNextOccurrence();
}
else
{
// Background processes do repeat periodically
$oPlannedStart = new DateTime($oTask->Get('latest_run_date'));
// Let's assume that the task was started exactly when planned so that the schedule does no shift each time
// this allows to schedule a task everyday "around" 11:30PM for example
$oPlannedStart->modify('+'.$oProcess->GetPeriodicity().' seconds');
$oEnd = new DateTime();
if ($oPlannedStart->format('U') < $oEnd->format('U'))
{
// Huh, next planned start is already in the past, shift it of the periodicity !
$oPlannedStart = $oEnd->modify('+'.$oProcess->GetPeriodicity().' seconds');
}
}
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
$oTask->DBUpdate();
return $sMessage;
}
function CronExec($oP, $aProcesses, $bVerbose)
@@ -354,7 +354,7 @@ $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')');
try
{
$oConfig = utils::GetConfig();
$oMutex = new iTopMutex('cron.'.$oConfig->GetDBName().'_'.$oConfig->GetDBSubname());
$oMutex = new iTopMutex('cron');
if ($oMutex->TryLock())
{
// Note: testing this now in case some of the background processes forces the read-only mode for a while

View File

@@ -66,7 +66,7 @@ else
$sRawFile = WebServicesBase::GetWSDLContents();
}
$sServerURI = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/soapserver.php';
$sServerURI = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php';
if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
{
$sServerURI .= "?service_category=".$_REQUEST['service_category'];

View File

@@ -32,10 +32,10 @@ require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/startup.inc.php');
// this file is generated dynamically with location = here
$sWsdlUri = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php';
$sWsdlUri = utils::GetAbsoluteUrlAppRoot().'webservices/itop.wsdl.php';
if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
{
$sWsdlUri .= "soapserver.php?service_category=".$_REQUEST['service_category'];
$sWsdlUri .= "?service_category=".$_REQUEST['service_category'];
}
@@ -99,7 +99,7 @@ else
if (is_subclass_of($sPHPClass, 'WebServicesBase'))
{
$sServiceCategory = $sPHPClass;
$sSoapServerUri = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/soapserver.php';
$sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php';
$sSoapServerUri .= "?service_category=$sServiceCategory";
echo "<li><a href=\"$sSoapServerUri\">$sServiceCategory</a></li>\n";
}