mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-19 02:14:10 +01:00
Compare commits
42 Commits
support/2.
...
support/2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d40c85a64b | ||
|
|
bfed914893 | ||
|
|
17e49dcc55 | ||
|
|
6ceab2ab5e | ||
|
|
b621e746d9 | ||
|
|
41345e6636 | ||
|
|
7c3a3820b7 | ||
|
|
b4b25b3c5b | ||
|
|
f45b9921a6 | ||
|
|
fd21ae262b | ||
|
|
0d14a20e1b | ||
|
|
ad36b978c5 | ||
|
|
e3f550fb39 | ||
|
|
96e886199f | ||
|
|
ff4ba2ddfe | ||
|
|
a4091ea771 | ||
|
|
c33bbd853d | ||
|
|
3345b07cc0 | ||
|
|
5dd7141d54 | ||
|
|
55da100f9c | ||
|
|
88665da96e | ||
|
|
4c25362b84 | ||
|
|
e520320736 | ||
|
|
95fc4d867d | ||
|
|
6524a40eaa | ||
|
|
f53943e78c | ||
|
|
528a8901df | ||
|
|
acd6d9679a | ||
|
|
f7c7fc5dc4 | ||
|
|
d575c48579 | ||
|
|
930d833e1b | ||
|
|
f7f77911be | ||
|
|
508f82946f | ||
|
|
6bb9754628 | ||
|
|
44fad50031 | ||
|
|
ed2cd2cea3 | ||
|
|
eaf74a3f23 | ||
|
|
1a99146b7a | ||
|
|
4a8e9e71f4 | ||
|
|
6d2d0ff701 | ||
|
|
af3c93051f | ||
|
|
f594190005 |
@@ -783,6 +783,10 @@ class RestUtils
|
||||
{
|
||||
$realValue = self::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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1570,7 +1571,7 @@ class DashletBadge extends Dashlet
|
||||
$oPage->add('<p>');
|
||||
$oPage->add(' <a>'.Dict::Format('UI:ClickToCreateNew', $sClassLabel).'</a>');
|
||||
$oPage->add(' <br/>');
|
||||
$oPage->add(' <a>Search for Server objects</a>');
|
||||
$oPage->add(' <a>'.Dict::Format('UI:SearchFor_Class', $sClassLabel).'</a>');
|
||||
$oPage->add('</p>');
|
||||
$oPage->add('</div>');
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -867,13 +867,11 @@ class DesignerTextField extends DesignerFormField
|
||||
{
|
||||
protected $sValidationPattern;
|
||||
protected $aForbiddenValues;
|
||||
protected $sExplainForbiddenValues;
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, $defaultValue);
|
||||
$this->sValidationPattern = '';
|
||||
$this->aForbiddenValues = null;
|
||||
$this->sExplainForbiddenValues = null;
|
||||
$this->aForbiddenValues = array();
|
||||
}
|
||||
|
||||
public function SetValidationPattern($sValidationPattern)
|
||||
@@ -883,17 +881,17 @@ class DesignerTextField extends DesignerFormField
|
||||
|
||||
public function SetForbiddenValues($aValues, $sExplain)
|
||||
{
|
||||
$this->aForbiddenValues = $aValues;
|
||||
$aForbiddenValues = $aValues;
|
||||
|
||||
$iDefaultKey = array_search($this->defaultValue, $this->aForbiddenValues);
|
||||
$iDefaultKey = array_search($this->defaultValue, $aForbiddenValues);
|
||||
if ($iDefaultKey !== false)
|
||||
{
|
||||
// The default (current) value is always allowed...
|
||||
unset($this->aForbiddenValues[$iDefaultKey]);
|
||||
unset($aForbiddenValues[$iDefaultKey]);
|
||||
|
||||
}
|
||||
|
||||
$this->sExplainForbiddenValues = $sExplain;
|
||||
$this->aForbiddenValues[] = array('values' => $aForbiddenValues, 'message' => $sExplain);
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
@@ -911,17 +909,15 @@ class DesignerTextField extends DesignerFormField
|
||||
if (is_array($this->aForbiddenValues))
|
||||
{
|
||||
$sForbiddenValues = json_encode($this->aForbiddenValues);
|
||||
$sExplainForbiddenValues = addslashes($this->sExplainForbiddenValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sForbiddenValues = 'null';
|
||||
$sExplainForbiddenValues = 'null';
|
||||
$sForbiddenValues = '[]'; //Empty JS array
|
||||
}
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
@@ -964,30 +960,35 @@ class DesignerLongTextField extends DesignerTextField
|
||||
if (is_array($this->aForbiddenValues))
|
||||
{
|
||||
$sForbiddenValues = json_encode($this->aForbiddenValues);
|
||||
$sExplainForbiddenValues = addslashes($this->sExplainForbiddenValues);
|
||||
}
|
||||
else
|
||||
{
|
||||
$sForbiddenValues = 'null';
|
||||
$sExplainForbiddenValues = 'null';
|
||||
$sForbiddenValues = '[]'; //Empty JS array
|
||||
}
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'readonly' : '';
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues, '$sExplainForbiddenValues'); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => "<textarea $sCSSClasses id=\"$sId\" $sReadOnly name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>");
|
||||
if (!$this->IsReadOnly())
|
||||
{
|
||||
$oP->add_ready_script(
|
||||
<<<EOF
|
||||
$('#$sId').bind('change keyup validate', function() { ValidateWithPattern('$sId', $sMandatory, '$sPattern', $(this).closest('form').attr('id'), $sForbiddenValues); } );
|
||||
{
|
||||
var myTimer = null;
|
||||
$('#$sId').bind('keyup', function() { clearTimeout(myTimer); myTimer = setTimeout(function() { $('#$sId').trigger('change', {} ); }, 100); });
|
||||
}
|
||||
EOF
|
||||
);
|
||||
$sValue = "<textarea $sCSSClasses id=\"$sId\" name=\"$sName\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</textarea>";
|
||||
}
|
||||
else
|
||||
{
|
||||
$sValue = "<div $sCSSClasses id=\"$sId\">".htmlentities($this->defaultValue, ENT_QUOTES, 'UTF-8')."</div>";
|
||||
}
|
||||
return array('label' => $this->sLabel, 'value' => $sValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1065,6 +1066,7 @@ class DesignerComboField extends DesignerFormField
|
||||
protected $bMultipleSelection;
|
||||
protected $bOtherChoices;
|
||||
protected $sNullLabel;
|
||||
protected $bSorted;
|
||||
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
@@ -1075,6 +1077,7 @@ class DesignerComboField extends DesignerFormField
|
||||
$this->sNullLabel = Dict::S('UI:SelectOne');
|
||||
|
||||
$this->bAutoApply = true;
|
||||
$this->bSorted = true; // Sorted by default
|
||||
}
|
||||
|
||||
public function SetAllowedValues($aAllowedValues)
|
||||
@@ -1100,6 +1103,16 @@ class DesignerComboField extends DesignerFormField
|
||||
$this->sNullLabel = $sLabel;
|
||||
}
|
||||
|
||||
public function IsSorted()
|
||||
{
|
||||
return $this->bSorted;
|
||||
}
|
||||
|
||||
public function SetSorted($bSorted)
|
||||
{
|
||||
$this->bSorted = $bSorted;
|
||||
}
|
||||
|
||||
public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
|
||||
{
|
||||
$sId = $this->oForm->GetFieldId($this->sCode);
|
||||
@@ -1107,6 +1120,10 @@ class DesignerComboField extends DesignerFormField
|
||||
$sChecked = $this->defaultValue ? 'checked' : '';
|
||||
$sMandatory = $this->bMandatory ? 'true' : 'false';
|
||||
$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' : '';
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
asort($this->aAllowedValues);
|
||||
}
|
||||
$sCSSClasses = '';
|
||||
if (count($this->aCSSClasses) > 0)
|
||||
{
|
||||
@@ -1428,13 +1445,37 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
{
|
||||
protected $aSubForms;
|
||||
protected $defaultRealValue; // What's stored as default value is actually the index
|
||||
protected $bSorted;
|
||||
|
||||
public function __construct($sCode, $sLabel = '', $defaultValue = '')
|
||||
{
|
||||
parent::__construct($sCode, $sLabel, 0);
|
||||
$this->defaultRealValue = $defaultValue;
|
||||
$this->aSubForms = array();
|
||||
$this->bSorted = true;
|
||||
}
|
||||
|
||||
public function IsSorted()
|
||||
{
|
||||
return $this->bSorted;
|
||||
}
|
||||
|
||||
public function SetSorted($bSorted)
|
||||
{
|
||||
$this->bSorted = $bSorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for sorting an array of $aFormData based ont he labels of the subforms
|
||||
* @param unknown $aItem1
|
||||
* @param unknown $aItem2
|
||||
* @return number
|
||||
*/
|
||||
static function SortOnFormLabel($aItem1, $aItem2)
|
||||
{
|
||||
return strcasecmp($aItem1['label'], $aItem2['label']);
|
||||
}
|
||||
|
||||
public function GetWidgetClass()
|
||||
{
|
||||
return 'selector_property_field';
|
||||
@@ -1465,6 +1506,10 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$sCSSClasses = 'class="'.implode(' ', $this->aCSSClasses).'"';
|
||||
}
|
||||
|
||||
if ($this->IsSorted())
|
||||
{
|
||||
uasort($this->aSubForms, array(get_class($this), 'SortOnFormLabel'));
|
||||
}
|
||||
|
||||
if ($this->IsReadOnly())
|
||||
{
|
||||
@@ -1490,9 +1535,10 @@ class DesignerFormSelectorField extends DesignerFormField
|
||||
$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
|
||||
foreach($this->aSubForms as $iKey => $aFormData)
|
||||
{
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');;
|
||||
$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
|
||||
$sValue = htmlentities($aFormData['value'], ENT_QUOTES, 'UTF-8');
|
||||
$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
|
||||
$sHtml .= "<option value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
$sHtml .= "<option data-value=\"$sValue\" value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
|
||||
}
|
||||
$sHtml .= "</select>";
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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']))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ class UILinksWidgetDirect
|
||||
$valuesDef = $oLinksetDef->GetValuesDef();
|
||||
if ($valuesDef === null)
|
||||
{
|
||||
$oFilter = new DBObjectSearch($this->sLinkedClass);
|
||||
$oFilter = new DBObjectSearch($sRemoteClass);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -487,6 +487,35 @@ abstract class AttributeDefinition
|
||||
{
|
||||
return $this->GetAsHTML($sValue, $oHostObject, $bLocalize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $value mixed The current value of the field
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $oHostObject DBObject The object
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
*/
|
||||
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
if ($this->IsScalar())
|
||||
{
|
||||
switch ($sVerb)
|
||||
{
|
||||
case '':
|
||||
return $value;
|
||||
|
||||
case 'html':
|
||||
return $this->GetAsHtml($value, $oHostObject, $bLocalize);
|
||||
|
||||
case 'label':
|
||||
return $this->GetEditValue($value);
|
||||
|
||||
default:
|
||||
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function GetAllowedValues($aArgs = array(), $sContains = '')
|
||||
{
|
||||
@@ -731,6 +760,46 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return $sRes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $value mixed The current value of the field
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $oHostObject DBObject The object
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
*/
|
||||
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
$sRemoteName = $this->IsIndirect() ? $this->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
|
||||
|
||||
$oLinkSet = clone $value; // Workaround/Safety net for Trac #887
|
||||
$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
|
||||
if ($iLimit > 0)
|
||||
{
|
||||
$oLinkSet->SetLimit($iLimit);
|
||||
}
|
||||
$aNames = $oLinkSet->GetColumnAsArray($sRemoteName);
|
||||
if ($iLimit > 0)
|
||||
{
|
||||
$iTotal = $oLinkSet->Count();
|
||||
if ($iTotal > count($aNames))
|
||||
{
|
||||
$aNames[] = '... '.Dict::Format('UI:TruncatedResults', count($aNames), $iTotal);
|
||||
}
|
||||
}
|
||||
|
||||
switch($sVerb)
|
||||
{
|
||||
case '':
|
||||
return implode("\n", $aNames);
|
||||
|
||||
case 'html':
|
||||
return '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
|
||||
|
||||
default:
|
||||
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
|
||||
}
|
||||
}
|
||||
|
||||
public function DuplicatesAllowed() {return false;} // No duplicates for 1:n links, never
|
||||
|
||||
public function GetImportColumns()
|
||||
@@ -2094,6 +2163,35 @@ class AttributeCaseLog extends AttributeLongText
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get various representations of the value, for insertion into a template (e.g. in Notifications)
|
||||
* @param $value mixed The current value of the field
|
||||
* @param $sVerb string The verb specifying the representation of the value
|
||||
* @param $oHostObject DBObject The object
|
||||
* @param $bLocalize bool Whether or not to localize the value
|
||||
*/
|
||||
public function GetForTemplate($value, $sVerb, $oHostObject = null, $bLocalize = true)
|
||||
{
|
||||
switch($sVerb)
|
||||
{
|
||||
case '':
|
||||
return $value->GetText();
|
||||
|
||||
case 'head':
|
||||
return $value->GetLatestEntry();
|
||||
|
||||
case 'head_html':
|
||||
return '<div class="caselog_entry">'.str_replace( array( "\r\n", "\n", "\r"), "<br/>", htmlentities($value->GetLatestEntry(), ENT_QUOTES, 'UTF-8')).'</div>';
|
||||
|
||||
case 'html':
|
||||
return $value->GetAsEmailHtml();
|
||||
|
||||
default:
|
||||
throw new Exception("Unknown verb '$sVerb' for attribute ".$this->GetCode().' in class '.get_class($oHostObj));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get a value that will be JSON encoded
|
||||
* The operation is the opposite to FromJSONToValue
|
||||
|
||||
@@ -809,6 +809,54 @@ 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,
|
||||
),
|
||||
'concurrent_lock_enabled' => array(
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not to activate the locking mechanism in order to prevent concurrent edition of the same object.',
|
||||
'default' => true,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => true,
|
||||
),
|
||||
'concurrent_lock_expiration_delay' => array(
|
||||
'type' => 'integer',
|
||||
'description' => 'Delay (in seconds) for a concurrent lock to expire',
|
||||
'default' => 120,
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
'concurrent_lock_override_profiles' => array(
|
||||
'type' => 'array',
|
||||
'description' => 'The list of profiles allowed to "kill" a lock',
|
||||
'default' => array('Administrator'),
|
||||
'value' => '',
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
),
|
||||
);
|
||||
|
||||
public function IsProperty($sPropCode)
|
||||
@@ -1462,6 +1510,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 +1519,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 +1585,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 +1625,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,
|
||||
);
|
||||
|
||||
@@ -89,8 +89,6 @@ abstract class DBObject implements iDisplay
|
||||
protected $m_aCheckIssues = null;
|
||||
protected $m_aDeleteIssues = null;
|
||||
|
||||
protected $m_aAsArgs = null; // The current object as a standard argument (cache)
|
||||
|
||||
private $m_bFullyLoaded = false; // Compound objects can be partially loaded
|
||||
private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
|
||||
protected $m_aModifiedAtt = array(); // list of (potentially) modified sAttCodes
|
||||
@@ -413,7 +411,6 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
// The object has changed, reset caches
|
||||
$this->m_bCheckStatus = null;
|
||||
$this->m_aAsArgs = null;
|
||||
|
||||
// Make sure we do not reload it anymore... before saving it
|
||||
$this->RegisterAsDirty();
|
||||
@@ -1570,9 +1567,6 @@ abstract class DBObject implements iDisplay
|
||||
$this->DBWriteLinks();
|
||||
$this->m_bIsInDB = true;
|
||||
$this->m_bDirty = false;
|
||||
|
||||
// Arg cache invalidated (in particular, it needs the object key -could be improved later)
|
||||
$this->m_aAsArgs = null;
|
||||
|
||||
$this->AfterInsert();
|
||||
|
||||
@@ -2270,84 +2264,94 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Create query parameters (SELECT ... WHERE service = :this->service_id)
|
||||
* to be used with the APIs DBObjectSearch/DBObjectSet
|
||||
*
|
||||
* Starting 2.0.2 the parameters are computed on demand, at the lowest level,
|
||||
* in VariableExpression::Render()
|
||||
*/
|
||||
/**
|
||||
* Create query parameters (SELECT ... WHERE service = :this->service_id)
|
||||
* to be used with the APIs DBObjectSearch/DBObjectSet
|
||||
*
|
||||
* Starting 2.0.2 the parameters are computed on demand, at the lowest level,
|
||||
* in VariableExpression::Render()
|
||||
*/
|
||||
public function ToArgsForQuery($sArgName = 'this')
|
||||
{
|
||||
return array($sArgName.'->object()' => $this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create template placeholders
|
||||
* An improvement could be to compute the values on demand
|
||||
* (i.e. interpret the template to determine the placeholders)
|
||||
*/
|
||||
/**
|
||||
* Create template placeholders: now equivalent to ToArgsForQuery since the actual
|
||||
* template placeholders are computed on demand.
|
||||
*/
|
||||
public function ToArgs($sArgName = 'this')
|
||||
{
|
||||
if (is_null($this->m_aAsArgs))
|
||||
return $this->ToArgsForQuery($sArgName);
|
||||
}
|
||||
|
||||
public function GetForTemplate($sPlaceholderAttCode)
|
||||
{
|
||||
$ret = null;
|
||||
if (($iPos = strpos($sPlaceholderAttCode, '->')) !== false)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
$aScalarArgs = $this->ToArgsForQuery($sArgName);
|
||||
$aScalarArgs[$sArgName] = $this->GetKey();
|
||||
$aScalarArgs[$sArgName.'->id'] = $this->GetKey();
|
||||
$aScalarArgs[$sArgName.'->hyperlink()'] = $this->GetHyperlink('iTopStandardURLMaker', false);
|
||||
$aScalarArgs[$sArgName.'->hyperlink(portal)'] = $this->GetHyperlink('PortalURLMaker', false);
|
||||
$aScalarArgs[$sArgName.'->name()'] = $this->GetName();
|
||||
|
||||
$sClass = get_class($this);
|
||||
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
|
||||
$sExtKeyAttCode = substr($sPlaceholderAttCode, 0, $iPos);
|
||||
$sRemoteAttCode = substr($sPlaceholderAttCode, $iPos + 2);
|
||||
if (!MetaModel::IsValidAttCode(get_class($this), $sExtKeyAttCode))
|
||||
{
|
||||
if ($oAttDef instanceof AttributeCaseLog)
|
||||
{
|
||||
$oCaseLog = $this->Get($sAttCode);
|
||||
$aScalarArgs[$sArgName.'->'.$sAttCode] = $oCaseLog->GetText();
|
||||
$sHead = $oCaseLog->GetLatestEntry();
|
||||
$aScalarArgs[$sArgName.'->head('.$sAttCode.')'] = $sHead;
|
||||
$aScalarArgs[$sArgName.'->head_html('.$sAttCode.')'] = '<div class="caselog_entry">'.str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sHead, ENT_QUOTES, 'UTF-8')).'</div>';
|
||||
$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $oCaseLog->GetAsEmailHtml();
|
||||
}
|
||||
elseif ($oAttDef->IsScalar())
|
||||
{
|
||||
$aScalarArgs[$sArgName.'->'.$sAttCode] = $this->Get($sAttCode);
|
||||
// #@# Note: This has been proven to be quite slow, this can slow down bulk load
|
||||
$sAsHtml = $this->GetAsHtml($sAttCode);
|
||||
$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = $sAsHtml;
|
||||
$aScalarArgs[$sArgName.'->label('.$sAttCode.')'] = $this->GetEditValue($sAttCode); // "Nice" display value, but without HTML tags and entities
|
||||
}
|
||||
elseif ($oAttDef->IsLinkSet())
|
||||
{
|
||||
$sRemoteName = $oAttDef->IsIndirect() ? $oAttDef->GetExtKeyToRemote().'_friendlyname' : 'friendlyname';
|
||||
throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
|
||||
}
|
||||
|
||||
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
|
||||
if (!$oKeyAttDef instanceof AttributeExternalKey)
|
||||
{
|
||||
throw new CoreException("'$sExtKeyAttCode' is not an external key of the class ".get_class($this));
|
||||
}
|
||||
$sRemoteClass = $oKeyAttDef->GetTargetClass();
|
||||
$oRemoteObj = MetaModel::GetObject($sRemoteClass, $this->GetStrict($sExtKeyAttCode), false);
|
||||
if (is_null($oRemoteObj))
|
||||
{
|
||||
$ret = Dict::S('UI:UndefinedObject');
|
||||
}
|
||||
else
|
||||
{
|
||||
// Recurse
|
||||
$ret = $oRemoteObj->GetForTemplate($sRemoteAttCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch($sPlaceholderAttCode)
|
||||
{
|
||||
case 'id':
|
||||
$ret = $this->GetKey();
|
||||
break;
|
||||
|
||||
case 'hyperlink()':
|
||||
$ret = $this->GetHyperlink('iTopStandardURLMaker', false);
|
||||
break;
|
||||
|
||||
$oLinkSet = clone $this->Get($sAttCode); // Workaround/Safety net for Trac #887
|
||||
$iLimit = MetaModel::GetConfig()->Get('max_linkset_output');
|
||||
if ($iLimit > 0)
|
||||
{
|
||||
$oLinkSet->SetLimit($iLimit);
|
||||
}
|
||||
$aNames = $oLinkSet->GetColumnAsArray($sRemoteName);
|
||||
if ($iLimit > 0)
|
||||
{
|
||||
$iTotal = $oLinkSet->Count();
|
||||
if ($iTotal > count($aNames))
|
||||
{
|
||||
$aNames[] = '... '.Dict::Format('UI:TruncatedResults', count($aNames), $iTotal);
|
||||
}
|
||||
}
|
||||
$sNames = implode("\n", $aNames);
|
||||
$aScalarArgs[$sArgName.'->'.$sAttCode] = $sNames;
|
||||
$aScalarArgs[$sArgName.'->html('.$sAttCode.')'] = '<ul><li>'.implode("</li><li>", $aNames).'</li></ul>';
|
||||
case 'hyperlink(portal)':
|
||||
$ret = $this->GetHyperlink('PortalURLMaker', false);
|
||||
break;
|
||||
|
||||
case 'name()':
|
||||
$ret = $this->GetName();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (preg_match('/^([^(]+)\\((.+)\\)$/', $sPlaceholderAttCode, $aMatches))
|
||||
{
|
||||
$sVerb = $aMatches[1];
|
||||
$sAttCode = $aMatches[2];
|
||||
}
|
||||
else
|
||||
{
|
||||
$sVerb = '';
|
||||
$sAttCode = $sPlaceholderAttCode;
|
||||
}
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
|
||||
$ret = $oAttDef->GetForTemplate($this->Get($sAttCode), $sVerb, $this);
|
||||
}
|
||||
|
||||
$this->m_aAsArgs = $aScalarArgs;
|
||||
$oKPI->ComputeStats('ToArgs', get_class($this));
|
||||
}
|
||||
return $this->m_aAsArgs;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// To be optionaly overloaded
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
@@ -5416,7 +5420,7 @@ abstract class MetaModel
|
||||
/**
|
||||
* Replaces all the parameters by the values passed in the hash array
|
||||
*/
|
||||
static public function ApplyParams($aInput, $aParams)
|
||||
static public function ApplyParams($sInput, $aParams)
|
||||
{
|
||||
// Declare magic parameters
|
||||
$aParams['APP_URL'] = utils::GetAbsoluteUrlAppRoot();
|
||||
@@ -5427,12 +5431,45 @@ abstract class MetaModel
|
||||
foreach($aParams as $sSearch => $replace)
|
||||
{
|
||||
// Some environment parameters are objects, we just need scalars
|
||||
if (is_object($replace)) continue;
|
||||
|
||||
$aSearches[] = '$'.$sSearch.'$';
|
||||
$aReplacements[] = (string) $replace;
|
||||
if (is_object($replace))
|
||||
{
|
||||
$iPos = strpos($sSearch, '->object()');
|
||||
if ($iPos !== false)
|
||||
{
|
||||
// Expand the parameters for the object
|
||||
$sName = substr($sSearch, 0, $iPos);
|
||||
if (preg_match_all('/\\$'.$sName.'->([^\\$]+)\\$/', $sInput, $aMatches))
|
||||
{
|
||||
foreach($aMatches[1] as $sPlaceholderAttCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
$sReplacement = $replace->GetForTemplate($sPlaceholderAttCode);
|
||||
if ($sReplacement !== null)
|
||||
{
|
||||
$aReplacements[] = $sReplacement;
|
||||
$aSearches[] = '$'.$sName.'->'.$sPlaceholderAttCode.'$';
|
||||
}
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
// No replacement will occur
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // Ignore this non-scalar value
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSearches[] = '$'.$sSearch.'$';
|
||||
$aReplacements[] = (string) $replace;
|
||||
}
|
||||
}
|
||||
return str_replace($aSearches, $aReplacements, $aInput);
|
||||
return str_replace($aSearches, $aReplacements, $sInput);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ abstract class QueryReflection
|
||||
/**
|
||||
* Throws an exception in case of an invalid syntax
|
||||
*/
|
||||
abstract public function __construct($sOQL);
|
||||
abstract public function __construct($sOQL, ModelReflection $oModelReflection);
|
||||
|
||||
abstract public function GetClass();
|
||||
abstract public function GetClassAlias();
|
||||
@@ -222,7 +222,7 @@ class ModelReflectionRuntime extends ModelReflection
|
||||
|
||||
public function GetQuery($sOQL)
|
||||
{
|
||||
return new QueryReflectionRuntime($sOQL);
|
||||
return new QueryReflectionRuntime($sOQL, $this);
|
||||
}
|
||||
|
||||
public function DictString($sStringCode, $sDefault = null, $bUserLanguageOnly = false)
|
||||
@@ -244,7 +244,7 @@ class QueryReflectionRuntime extends QueryReflection
|
||||
/**
|
||||
* throws an exception in case of a wrong syntax
|
||||
*/
|
||||
public function __construct($sOQL)
|
||||
public function __construct($sOQL, ModelReflection $oModelReflection)
|
||||
{
|
||||
$this->oFilter = DBObjectSearch::FromOQL($sOQL);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -121,7 +121,41 @@ class iTopMutex
|
||||
$this->bLocked = true;
|
||||
self::$aAcquiredLocks[$this->sName]++;
|
||||
}
|
||||
return ($res === '1');
|
||||
if (($res !== '1') && ($res !== '0'))
|
||||
{
|
||||
IssueLog::Error('GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null !!');
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
$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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/> <input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/> </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/> <input id="btn_remove_'.$iAttId.'" type="button" class="btn_hidden" value="Delete" onClick="RemoveAttachment('.$iAttId.');"/> </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/> </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'; }}});");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Copyright (C) 2014 Combodo SARL
|
||||
// Copyright (C) 2013-2016 Combodo SARL
|
||||
//
|
||||
// This file is part of iTop.
|
||||
//
|
||||
@@ -18,8 +18,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 +76,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';
|
||||
|
||||
@@ -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
|
||||
@@ -29,24 +29,8 @@ class BackupHandler extends ModuleHandlerAPI
|
||||
|
||||
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())
|
||||
{
|
||||
MetaModel::GetConfig()->Set('access_mode', ACCESS_READONLY, 'itop-backup');
|
||||
MetaModel::GetConfig()->Set('access_message', ' - '.dict::S('bkp-restore-running'), 'itop-backup');
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
@@ -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\">");
|
||||
|
||||
@@ -746,6 +746,16 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>deny</operation>
|
||||
<types>
|
||||
<type id="AttributeStopWatch"/>
|
||||
<type id="AttributeSubItem"/>
|
||||
<type id="AttributeExternalField"/>
|
||||
<type id="AttributeLinkedSetIndirect"/>
|
||||
<type id="AttributeLinkedSet"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="2">
|
||||
<type>string</type>
|
||||
@@ -758,6 +768,14 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
<type id="AttributeString"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
@@ -766,6 +784,14 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeExternalKey"/>
|
||||
<type id="AttributeInteger"/>
|
||||
<type id="AttributeString"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
@@ -774,10 +800,23 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDuration"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="2">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeDate"/>
|
||||
<type id="AttributeDateTime"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="3">
|
||||
<type>string</type>
|
||||
@@ -790,6 +829,28 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>deny</operation>
|
||||
<types>
|
||||
<type id="AttributeStopWatch"/>
|
||||
<type id="AttributeSubItem"/>
|
||||
<type id="AttributeExternalField"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
<method id="ResetStopWatch">
|
||||
<arguments>
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>allow</operation>
|
||||
<types>
|
||||
<type id="AttributeStopWatch"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
</arguments>
|
||||
</method>
|
||||
@@ -798,6 +859,14 @@
|
||||
<argument id="1">
|
||||
<type>attcode</type>
|
||||
<mandatory>true</mandatory>
|
||||
<type_restrictions>
|
||||
<operation>deny</operation>
|
||||
<types>
|
||||
<type id="AttributeStopWatch"/>
|
||||
<type id="AttributeSubItem"/>
|
||||
<type id="AttributeExternalField"/>
|
||||
</types>
|
||||
</type_restrictions>
|
||||
</argument>
|
||||
<argument id="2">
|
||||
<type>attcode</type>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -225,6 +225,7 @@ $(function()
|
||||
_do_submit: function()
|
||||
{
|
||||
var oData = {};
|
||||
var me = this;
|
||||
this.element.closest('form').find(':input[type=hidden]').each(function()
|
||||
{
|
||||
// Hidden form fields
|
||||
@@ -232,7 +233,7 @@ $(function()
|
||||
});
|
||||
this.element.closest('form').find('.itop-property-field').each(function()
|
||||
{
|
||||
var oWidget = $(this).data('itopProperty_field');
|
||||
var oWidget = me._get_widget($(this));
|
||||
if (oWidget && oWidget._is_visible())
|
||||
{
|
||||
var oVal = oWidget._get_committed_value();
|
||||
@@ -264,6 +265,15 @@ $(function()
|
||||
{
|
||||
var oField = $('#'+this.options.field_id, this.element);
|
||||
oField.trigger('validate');
|
||||
},
|
||||
_get_widget: function(element)
|
||||
{
|
||||
var oWidget = element.data('itopProperty_field');
|
||||
if (oWidget == undefined)
|
||||
{
|
||||
oWidget = element.data('itopSelector_property_field');
|
||||
}
|
||||
return oWidget;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -331,9 +341,19 @@ $(function()
|
||||
$('tr[data-path^="'+sSelector+'"]').each(function() {
|
||||
if($(this).is(':visible'))
|
||||
{
|
||||
var oPropField = $(this).closest('.itop-property-field');
|
||||
oPropField.property_field('option', {can_apply: !me.bModified, parent_selector: '#'+me.element.attr('id') });
|
||||
oPropField.property_field('validate');
|
||||
var oWidget = me._get_widget($(this).closest('.itop-property-field'));
|
||||
if (oWidget)
|
||||
{
|
||||
try
|
||||
{
|
||||
oWidget._setOptions({can_apply: !me.bModified, parent_selector: '#'+me.element.attr('id') });
|
||||
oWidget.validate();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
// Do nothing, form in read-only mode
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -385,21 +405,21 @@ $(function()
|
||||
$('tr[data-path^="'+sSelector+'"]').each(function() {
|
||||
if($(this).is(':visible'))
|
||||
{
|
||||
var sName = $(this).closest('.itop-property-field').property_field('mark_as_applied').property_field('get_field_name');
|
||||
if (typeof sName == 'string')
|
||||
var oWidget = me._get_widget($(this).closest('.itop-property-field'));
|
||||
if (oWidget)
|
||||
{
|
||||
aUpdated.push(sName);
|
||||
oWidget.mark_as_applied();
|
||||
sName = oWidget.get_field_name();
|
||||
if (typeof sName == 'string')
|
||||
{
|
||||
aUpdated.push(sName);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
this.element.closest('form').find('.itop-property-field').each(function()
|
||||
{
|
||||
var oWidget = $(this).data('itopProperty_field');
|
||||
if (!oWidget)
|
||||
{
|
||||
// try the form selector widget
|
||||
oWidget = $(this).data('itopSelector_property_field');
|
||||
}
|
||||
var oWidget = me._get_widget($(this));
|
||||
if (oWidget && oWidget._is_visible())
|
||||
{
|
||||
var oVal = oWidget._get_committed_value();
|
||||
@@ -426,12 +446,16 @@ $(function()
|
||||
sFormId = this.element.closest('form').attr('id');
|
||||
oFormValidation[sFormId] = [];
|
||||
this.options.can_apply = true;
|
||||
var sSelector = this.options.data_selector
|
||||
var sSelector = this.options.data_selector;
|
||||
var me = this;
|
||||
$('tr[data-path^="'+sSelector+'"]').each(function() {
|
||||
if($(this).is(':visible'))
|
||||
{
|
||||
var oPropField = $(this).closest('.itop-property-field');
|
||||
oPropField.property_field('validate');
|
||||
var oWidget = me._get_widget($(this).closest('.itop-property-field'));
|
||||
if (oWidget)
|
||||
{
|
||||
oWidget.validate();
|
||||
}
|
||||
}
|
||||
});
|
||||
this.options.can_apply = (oFormValidation[sFormId].length == 0); // apply allowed only if no error
|
||||
@@ -442,7 +466,7 @@ $(function()
|
||||
|
||||
var oFormValidation = {};
|
||||
|
||||
function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId, aForbiddenValues, sExplainForbiddenValues)
|
||||
function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId, aForbiddenValues)
|
||||
{
|
||||
var currentVal = $('#'+sFieldId).val();
|
||||
var bValid = true;
|
||||
@@ -461,11 +485,14 @@ function ValidateWithPattern(sFieldId, bMandatory, sPattern, sFormId, aForbidden
|
||||
{
|
||||
for(var i in aForbiddenValues)
|
||||
{
|
||||
if (aForbiddenValues[i] == currentVal)
|
||||
for(j in aForbiddenValues[i].values)
|
||||
{
|
||||
bValid = false;
|
||||
sMessage = sExplainForbiddenValues;
|
||||
break;
|
||||
if (aForbiddenValues[i].values[j] == currentVal)
|
||||
{
|
||||
bValid = false;
|
||||
sMessage = aForbiddenValues[i].message;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,7 +625,7 @@ function ReadFormParams(sFormId)
|
||||
{
|
||||
var oMap = { };
|
||||
$('#'+sFormId+' :input').each( function() {
|
||||
if ($(this).parent().is(':visible'))
|
||||
if ($(this).parent().is(':visible') && !$(this).prop('disabled'))
|
||||
{
|
||||
var sName = $(this).attr('name');
|
||||
if (sName && sName != '')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"> <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());
|
||||
}
|
||||
}
|
||||
?>
|
||||
?>
|
||||
|
||||
@@ -272,11 +272,10 @@ class ModelFactory
|
||||
|
||||
if (($oSourceNode->tagName == 'class') && ($oSourceNode->parentNode->tagName == 'classes') && ($oSourceNode->parentNode->parentNode->tagName == 'itop_design'))
|
||||
{
|
||||
$sParentId = $oSourceNode->GetChildText('parent');
|
||||
if ($oSourceNode->getAttribute('_delta') == 'define')
|
||||
{
|
||||
// This tag is organized in hierarchy: determine the real parent node (as a subnode of the current node)
|
||||
$sParentId = $oSourceNode->GetChildText('parent');
|
||||
|
||||
$oTargetParentNode = $oTarget->GetNodeById('/itop_design/classes//class', $sParentId)->item(0);
|
||||
|
||||
if (!$oTargetParentNode)
|
||||
@@ -298,6 +297,13 @@ class ModelFactory
|
||||
else
|
||||
{
|
||||
$oTargetParentNode = $oTargetNode->parentNode;
|
||||
if (($oSourceNode->getAttribute('_delta') == 'redefine') && ($oTargetParentNode->getAttribute('id') != $sParentId))
|
||||
{
|
||||
// A class that has moved <=> deletion and creation elsewhere
|
||||
$oTargetParentNode = $oTarget->GetNodeById('/itop_design/classes//class', $sParentId)->item(0);
|
||||
$oTargetNode->Delete();
|
||||
$oSourceNode->setAttribute('_delta', 'define');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -860,19 +866,56 @@ EOF
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Import the node into the delta
|
||||
*/
|
||||
protected function SetDeltaFlags($oNodeClone)
|
||||
{
|
||||
$sAlteration = $oNodeClone->getAttribute('_alteration');
|
||||
$oNodeClone->removeAttribute('_alteration');
|
||||
if ($oNodeClone->hasAttribute('_old_id'))
|
||||
{
|
||||
$oNodeClone->setAttribute('_rename_from', $oNodeClone->getAttribute('_old_id'));
|
||||
$oNodeClone->removeAttribute('_old_id');
|
||||
}
|
||||
switch ($sAlteration)
|
||||
{
|
||||
case '':
|
||||
if ($oNodeClone->hasAttribute('id'))
|
||||
{
|
||||
$oNodeClone->setAttribute('_delta', 'must_exist');
|
||||
}
|
||||
break;
|
||||
case 'added':
|
||||
$oNodeClone->setAttribute('_delta', 'define');
|
||||
break;
|
||||
case 'replaced':
|
||||
$oNodeClone->setAttribute('_delta', 'redefine');
|
||||
break;
|
||||
case 'removed':
|
||||
$oNodeClone->setAttribute('_delta', 'delete');
|
||||
break;
|
||||
case 'needed':
|
||||
$oNodeClone->setAttribute('_delta', 'define_if_not_exists');
|
||||
break;
|
||||
}
|
||||
return $oNodeClone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create path for the delta
|
||||
* @param Array aMovedClasses The classes that have been moved in the hierarchy (deleted + created elsewhere)
|
||||
* @param DOMDocument oTargetDoc Where to attach the top of the hierarchy
|
||||
* @param MFElement oNode The node to import with its path
|
||||
*/
|
||||
protected function ImportNodeAndPathDelta($oTargetDoc, $oNode)
|
||||
protected function ImportNodeAndPathDelta($aMovedClasses, $oTargetDoc, $oNode)
|
||||
{
|
||||
// Preliminary: skip the parent if this node is organized hierarchically into the DOM
|
||||
// Only class nodes are organized this way
|
||||
$oParent = $oNode->parentNode;
|
||||
if ($oNode->tagName == 'class')
|
||||
if ($oNode->IsClassNode())
|
||||
{
|
||||
while (($oParent instanceof DOMElement) && ($oParent->tagName == $oNode->tagName) && $oParent->hasAttribute('id'))
|
||||
while (($oParent instanceof DOMElement) && ($oParent->IsClassNode()))
|
||||
{
|
||||
$oParent = $oParent->parentNode;
|
||||
}
|
||||
@@ -880,64 +923,71 @@ EOF
|
||||
// Recursively create the path for the parent
|
||||
if ($oParent instanceof DOMElement)
|
||||
{
|
||||
$oParentClone = $this->ImportNodeAndPathDelta($oTargetDoc, $oParent);
|
||||
$oParentClone = $this->ImportNodeAndPathDelta($aMovedClasses, $oTargetDoc, $oParent);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We've reached the top let's add the node into the root recipient
|
||||
$oParentClone = $oTargetDoc;
|
||||
}
|
||||
// Look for the node into the parent node
|
||||
// Note: this is an identified weakness of the algorithm,
|
||||
// because for each node modified, and each node of its path
|
||||
// we will have to lookup for the existing entry
|
||||
// Anyhow, this loop is quite quick to execute because in the delta
|
||||
// the number of nodes is limited
|
||||
$oNodeClone = null;
|
||||
foreach ($oParentClone->childNodes as $oChild)
|
||||
|
||||
$sAlteration = $oNode->getAttribute('_alteration');
|
||||
if ($oNode->IsClassNode() && ($sAlteration != ''))
|
||||
{
|
||||
if (($oChild instanceof DOMElement) && ($oChild->tagName == $oNode->tagName))
|
||||
{
|
||||
if (!$oNode->hasAttribute('id') || ($oNode->getAttribute('id') == $oChild->getAttribute('id')))
|
||||
{
|
||||
$oNodeClone = $oChild;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$oNodeClone)
|
||||
{
|
||||
$sAlteration = $oNode->getAttribute('_alteration');
|
||||
$bCopyContents = ($sAlteration == 'replaced') || ($sAlteration == 'added') || ($sAlteration == 'needed');
|
||||
$oNodeClone = $oTargetDoc->importNode($oNode->cloneNode($bCopyContents), $bCopyContents);
|
||||
$oNodeClone->removeAttribute('_alteration');
|
||||
if ($oNodeClone->hasAttribute('_old_id'))
|
||||
{
|
||||
$oNodeClone->setAttribute('_rename_from', $oNodeClone->getAttribute('_old_id'));
|
||||
$oNodeClone->removeAttribute('_old_id');
|
||||
}
|
||||
switch ($sAlteration)
|
||||
{
|
||||
case '':
|
||||
if ($oNodeClone->hasAttribute('id'))
|
||||
{
|
||||
$oNodeClone->setAttribute('_delta', 'must_exist');
|
||||
}
|
||||
break;
|
||||
case 'added':
|
||||
$oNodeClone->setAttribute('_delta', 'define');
|
||||
break;
|
||||
case 'replaced':
|
||||
$oNodeClone->setAttribute('_delta', 'redefine');
|
||||
break;
|
||||
case 'removed':
|
||||
$oNodeClone->setAttribute('_delta', 'delete');
|
||||
break;
|
||||
case 'needed':
|
||||
$oNodeClone->setAttribute('_delta', 'define_if_not_exists');
|
||||
break;
|
||||
}
|
||||
// Handle the moved classes
|
||||
//
|
||||
// Import the whole root node
|
||||
$oNodeClone = $oTargetDoc->importNode($oNode->cloneNode(true), true);
|
||||
$oParentClone->appendChild($oNodeClone);
|
||||
$this->SetDeltaFlags($oNodeClone);
|
||||
|
||||
// Handle the moved classes found under the root node (or the root node itself)
|
||||
foreach($oNodeClone->GetNodes("descendant-or-self::class[@id]", false) as $oClassNode)
|
||||
{
|
||||
if (array_key_exists($oClassNode->getAttribute('id'), $aMovedClasses))
|
||||
{
|
||||
if ($sAlteration == 'removed')
|
||||
{
|
||||
// Remove that node: this specification will be overriden by the 'replaced' spec (see below)
|
||||
$oClassNode->parentNode->removeChild($oClassNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Move the class at the root, with the flag 'modified'
|
||||
$oParentClone->appendChild($oClassNode);
|
||||
$oClassNode->setAttribute('_alteration', 'replaced');
|
||||
$this->SetDeltaFlags($oClassNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Look for the node into the parent node
|
||||
// Note: this is an identified weakness of the algorithm,
|
||||
// because for each node modified, and each node of its path
|
||||
// we will have to lookup for the existing entry
|
||||
// Anyhow, this loop is quite quick to execute because in the delta
|
||||
// the number of nodes is limited
|
||||
$oNodeClone = null;
|
||||
foreach ($oParentClone->childNodes as $oChild)
|
||||
{
|
||||
if (($oChild instanceof DOMElement) && ($oChild->tagName == $oNode->tagName))
|
||||
{
|
||||
if (!$oNode->hasAttribute('id') || ($oNode->getAttribute('id') == $oChild->getAttribute('id')))
|
||||
{
|
||||
$oNodeClone = $oChild;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$oNodeClone)
|
||||
{
|
||||
$bCopyContents = ($sAlteration == 'replaced') || ($sAlteration == 'added') || ($sAlteration == 'needed');
|
||||
$oNodeClone = $oTargetDoc->importNode($oNode->cloneNode($bCopyContents), $bCopyContents);
|
||||
$this->SetDeltaFlags($oNodeClone);
|
||||
$oParentClone->appendChild($oNodeClone);
|
||||
}
|
||||
}
|
||||
return $oNodeClone;
|
||||
}
|
||||
@@ -962,9 +1012,24 @@ EOF
|
||||
public function GetDeltaDocument($aNodesToIgnore = array(), $aAttributes = null)
|
||||
{
|
||||
$oDelta = new MFDocument();
|
||||
|
||||
// Handle classes moved from one parent to another
|
||||
// This will be done in two steps:
|
||||
// 1) Identify the moved classes (marked as deleted under the original parent, and created under the new parent)
|
||||
// 2) When importing those "moved" classes into the delta (see ImportNodeAndPathDelta), extract them from the hierarchy (the alteration can be done at an upper level in the hierarchy) and mark them as "modified"
|
||||
$aMovedClasses = array();
|
||||
foreach($this->GetNodes("/itop_design/classes//class/class[@_alteration='removed']", null, false) as $oNode)
|
||||
{
|
||||
$sId = $oNode->getAttribute('id');
|
||||
if ($this->GetNodes("/itop_design/classes//class/class[@id='$sId']/properties", null, false)->length > 0)
|
||||
{
|
||||
$aMovedClasses[$sId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach($this->ListChanges() as $oAlteredNode)
|
||||
{
|
||||
$this->ImportNodeAndPathDelta($oDelta, $oAlteredNode);
|
||||
$this->ImportNodeAndPathDelta($aMovedClasses, $oDelta, $oAlteredNode);
|
||||
}
|
||||
foreach($aNodesToIgnore as $sXPath)
|
||||
{
|
||||
@@ -1173,9 +1238,9 @@ EOF;
|
||||
* @param string $sXPath A XPath expression
|
||||
* @return DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath, $oContextNode = null)
|
||||
public function GetNodes($sXPath, $oContextNode = null, $bSafe = true)
|
||||
{
|
||||
return $this->oDOMDocument->GetNodes($sXPath, $oContextNode);
|
||||
return $this->oDOMDocument->GetNodes($sXPath, $oContextNode, $bSafe);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1199,9 +1264,9 @@ class MFElement extends DOMElement
|
||||
* @param string $sXPath A XPath expression
|
||||
* @return DOMNodeList
|
||||
*/
|
||||
public function GetNodes($sXPath)
|
||||
public function GetNodes($sXPath, $bSafe = true)
|
||||
{
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this);
|
||||
return $this->ownerDocument->GetNodes($sXPath, $this, $bSafe);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1274,7 +1339,7 @@ class MFElement extends DOMElement
|
||||
$sText = null;
|
||||
foreach($this->childNodes as $oChildNode)
|
||||
{
|
||||
if ($oChildNode instanceof DOMCharacterData) // Base class of DOMText and DOMCdataSection
|
||||
if ($oChildNode instanceof DOMText)
|
||||
{
|
||||
if (is_null($sText)) $sText = '';
|
||||
$sText .= $oChildNode->wholeText;
|
||||
@@ -1614,16 +1679,16 @@ class MFElement extends DOMElement
|
||||
/**
|
||||
* Check that the current node is actually a class node, under classes
|
||||
*/
|
||||
protected function IsClassNode()
|
||||
public function IsClassNode()
|
||||
{
|
||||
if ($this->tagName == 'class')
|
||||
{
|
||||
if (($this->parentNode->tagName == 'classes') && ($this->parentNode->parentNode->tagName == 'itop_design') ) // Beware: classes/class also exists in the group definition
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return $this->parentNode->IsClassNode();
|
||||
}
|
||||
elseif (($this->tagName == 'classes') && ($this->parentNode->tagName == 'itop_design') ) // Beware: classes/class also exists in the group definition
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -623,11 +623,7 @@ EOF
|
||||
$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\"/> 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.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user