Compare commits

..

42 Commits

Author SHA1 Message Date
Denis Flaven
d40c85a64b (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.1[4246]
2016-06-22 14:00:37 +00:00
Denis Flaven
bfed914893 (retrofit from trunk) Properly sanitize the "switch_env" parameter and take it into account only if it contains a valid value.
SVN:2.1.1[4240]
2016-06-22 12:12:04 +00:00
Denis Flaven
17e49dcc55 (retrofit from trunk) Optimization/bug (!): Never use the whole object as a placeholder in ApplyParams !!
SVN:2.1.1[3933]
2016-02-29 16:26:18 +00:00
Denis Flaven
6ceab2ab5e Use one-way encryption for storing the token used for the "Forgotten password" feature.
SVN:2.1.1[3922]
2016-02-19 18:20:28 +00:00
Denis Flaven
b621e746d9 (retrofit from trunk) #1202: Fix for a security vulnerability in the Configuration Editor.
SVN:2.1.1[3905]
2016-02-11 10:30:19 +00:00
Denis Flaven
41345e6636 #1150: Spurious message "A restore is running..." - FIXED !
SVN:2.1.1[3866]
2016-01-20 16:06:05 +00:00
Denis Flaven
7c3a3820b7 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.1[3824]
2015-11-20 14:26:05 +00:00
Denis Flaven
b4b25b3c5b 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.1[3818]
2015-11-09 10:50:38 +00:00
Denis Flaven
f45b9921a6 Retrofit of file based "transactions" as an alternative to session based ones.
SVN:2.1.1[3670]
2015-08-05 14:18:13 +00:00
Denis Flaven
fd21ae262b Fixed a potential XSS vulnerability.
SVN:2.1.1[3663]
2015-07-30 09:07:47 +00:00
Denis Flaven
0d14a20e1b Bug fix: typo causing the generation of invalid SQL queries (in some rare cases). - fix for the 2.1.1 branch.
SVN:2.1.1[3655]
2015-07-28 12:32:22 +00:00
Denis Flaven
ad36b978c5 Better error reporting (thanks to Stefan Goethals for suggesting it).
SVN:2.1.1[3625]
2015-07-06 17:08:34 +00:00
Denis Flaven
e3f550fb39 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.1[3623]
2015-07-06 14:37:42 +00:00
Denis Flaven
96e886199f #1107: Make sure that all settings are preserved upon update.
SVN:2.1.1[3615]
2015-07-01 08:45:05 +00:00
Denis Flaven
ff4ba2ddfe Bug fix: make Excel export work on results of the global search.
SVN:2.1.1[3605]
2015-06-22 10:12:03 +00:00
Denis Flaven
a4091ea771 Bug fix: make Excel export work on results of the global search.
SVN:2.1.1[3604]
2015-06-22 10:05:15 +00:00
Denis Flaven
c33bbd853d Added an alternate implementation for storing "transaction" identifiers on disk instead of inside the $_SESSION variable.
SVN:2.1.1[3600]
2015-06-20 13:41:13 +00:00
Denis Flaven
3345b07cc0 Mutex instrumentation for troubleshooting...
SVN:2.1.1[3597]
2015-06-19 14:49:15 +00:00
Romain Quetiez
5dd7141d54 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.1[3566]
2015-04-27 09:33:26 +00:00
Denis Flaven
55da100f9c Bug fix: prevent a crash of the web services when trying to log a non scalar paramater value...
SVN:2.1.1[3550]
2015-04-16 15:35:09 +00:00
Denis Flaven
88665da96e Enhancement: allow the API to create entries with a specified user_login.
SVN:2.1.1[3515]
2015-03-24 17:08:03 +00:00
Denis Flaven
4c25362b84 #594: properly display attachments inside "properties" by closing the span and the fieldset in non-edit mode.
SVN:2.1.1[3511]
2015-03-23 17:55:24 +00:00
Romain Quetiez
e520320736 Creating 2.1.1
SVN:2.1.1[3508]
2015-03-20 10:14:45 +00:00
Denis Flaven
95fc4d867d Fixed another regression of 3500: LongTextFields also support multiple forbidden lists...
SVN:trunk[3505]
2015-03-12 15:26:08 +00:00
Denis Flaven
6524a40eaa Enhancement: do not retrieve disabled fields.
SVN:trunk[3504]
2015-03-12 14:00:14 +00:00
Romain Quetiez
f53943e78c Meta information on lifecycle actions arguments: added type restrictions, and added the method ResetStopWatch
SVN:trunk[3503]
2015-03-12 10:42:51 +00:00
Denis Flaven
528a8901df Fixed a regression introduced by the revision 3500: the default value is NEVER "forbidden"
SVN:trunk[3502]
2015-03-11 15:46:53 +00:00
Denis Flaven
acd6d9679a Additional markup for JQuery scripts...
SVN:trunk[3501]
2015-03-11 15:33:01 +00:00
Denis Flaven
f7c7fc5dc4 Support several sets of forbidden values (with a specific "reason" message) per field.
SVN:trunk[3500]
2015-03-10 15:34:04 +00:00
Romain Quetiez
d575c48579 N°257 Aperçu des dashlets Badge partiellement hardcodé ("Search for objects of type Server")
SVN:trunk[3499]
2015-03-10 13:48:50 +00:00
Denis Flaven
930d833e1b #803: template placeholders are now built on demand. Yes !!
SVN:trunk[3498]
2015-02-27 10:02:44 +00:00
Denis Flaven
f7f77911be - Properly handle "suggested" attachments
- Properly pass the name of the uploaded file to the internal JS event

SVN:trunk[3496]
2015-02-12 17:59:08 +00:00
Romain Quetiez
508f82946f #1060 Internal: improved the symptoms when calling MetaModel::GetAttributeDef with an invalid attribute code (feedback on the class name and no more FATAL errors)
SVN:trunk[3492]
2015-02-09 13:11:49 +00:00
Romain Quetiez
6bb9754628 Internal: fixed the caching of DBObject::ToArgs()
1) Wasn't reset when the object was written the DB (thus having its ID set)
2) Wasn't taking the argument name into account (the list of placeholders was defined by the first caller)

SVN:trunk[3491]
2015-01-30 10:04:42 +00:00
Romain Quetiez
44fad50031 #1053 XML comments breaking the setup with message "Notice: Undefined property: DOMComment::$wholeText in ...modelfactory.class.inc.php on line 1280"
SVN:trunk[3490]
2015-01-14 13:51:37 +00:00
Denis Flaven
ed2cd2cea3 Change of the QueryReflection API to support DesignTime.
SVN:trunk[3489]
2015-01-12 14:20:20 +00:00
Romain Quetiez
eaf74a3f23 ModelFactory: Re-creating a class into another location in the class hierarchy it equivalent to moving that class => the delta must be a "redefine" for the class (improved the comment from the previous commit)
SVN:trunk[3487]
2015-01-08 11:05:18 +00:00
Romain Quetiez
1a99146b7a ModelFactory: Re-creating a class into another location in the class hierarchy it equivalent to moving that class => the delta must be a "redefine" for the class
SVN:trunk[3486]
2015-01-08 10:39:34 +00:00
Denis Flaven
4a8e9e71f4 Regression: the instance method is only available since jquery UI 1.11
SVN:trunk[3484]
2014-12-26 09:08:14 +00:00
Denis Flaven
6d2d0ff701 - Read-only "long text" fields no longer appear as editable
- Combo and FormSelector fields are now sorted by default (but sorting can be disabled if needed)

SVN:trunk[3483]
2014-12-23 15:23:12 +00:00
Denis Flaven
af3c93051f Protect against JS errors when the form is in read-only mode.
SVN:trunk[3482]
2014-12-23 14:49:06 +00:00
Denis Flaven
f594190005 Properly handle property_sheets with nested selector fields...
SVN:trunk[3481]
2014-12-23 13:42:25 +00:00
29 changed files with 643 additions and 429 deletions

View File

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

View File

@@ -1571,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>');

View File

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

View File

@@ -90,8 +90,6 @@ 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
@@ -233,20 +231,6 @@ 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

@@ -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')); $(this).data('ckeditorInstance').setReadOnly($(this).prop('disabled')); } );\n");
$oPage->add_ready_script("$('#$iId').bind('update', function() { BlockField('cke_$iId', $('#$iId').attr('disabled')); } );\n");
return $sHtmlValue;
}

View File

@@ -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()
@@ -892,106 +961,6 @@ 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;
@@ -2194,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

View File

@@ -833,6 +833,30 @@ class Config
'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)

View File

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

View File

@@ -5420,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();
@@ -5431,14 +5431,45 @@ abstract class MetaModel
foreach($aParams as $sSearch => $replace)
{
// Some environment parameters are objects, we just need scalars
if (is_object($replace)) continue;
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);
}
/**

View File

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

View File

@@ -37,19 +37,8 @@ class iTopMutex
public function __construct($sName, $sDBHost = null, $sDBUser = null, $sDBPwd = null)
{
// Compute the name of a lock for mysql
// 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();
// Note: the name is server-wide!!!
$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))
@@ -59,6 +48,7 @@ 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;
@@ -133,9 +123,7 @@ class iTopMutex
}
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);
IssueLog::Error('GET_LOCK('.$this->sName.', 0) returned: '.var_export($res, true).'. Expected values are: 0, 1 or null !!');
}
return ($res !== '0');
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2015 Combodo SARL
// Copyright (C) 2010-2012 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-2015 Combodo SARL
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
class ormCaseLog {
@@ -394,7 +394,7 @@ class ormCaseLog {
{
throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
}
if ($bCheckUserId && ($oJson->user_id != 0))
if ($bCheckUserId)
{
try
{

View File

@@ -1303,9 +1303,8 @@ class CAS_SelfRegister implements iSelfRegister
}
else
{
// 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;
// No membership required, anybody will pass
$bFound = true;
}
if (!$bFound)

View File

@@ -206,11 +206,6 @@ 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

@@ -18,7 +18,6 @@
/**
* Backup from an interactive session
*
* @copyright Copyright (C) 2013-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
@@ -118,9 +117,7 @@ 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);
@@ -145,7 +142,6 @@ EOF
$sBackupFile = $sBackupDir.$sFile;
$sRes = $oDBRS->RestoreFromZip($sBackupFile, $sEnvironment);
IssueLog::Info('Backup Restore - Done, releasing the LOCK');
$oRestoreMutex->Unlock();
}
catch (Exception $e)

View File

@@ -26,19 +26,19 @@ class BackupHandler extends ModuleHandlerAPI
{
public static function OnMetaModelStarted()
{
try
{
$oRestoreMutex = new iTopMutex('restore.'.utils::GetCurrentEnvironment());
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

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

View File

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

View File

@@ -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 != '')

View File

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

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2012 Combodo SARL
//
// This file is part of iTop.
//
@@ -224,12 +224,6 @@ 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

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

View File

@@ -50,7 +50,7 @@ class CheckResult
class SetupUtils
{
const PHP_MIN_VERSION = '5.3.0';
const PHP_MIN_VERSION = '5.2.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

@@ -618,7 +618,7 @@ 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', '')

View File

@@ -468,7 +468,7 @@ try
// Prepare insert columns
$sInsertColumns = '`'.implode('`, `', $aInputColumns).'`';
$oMutex = new iTopMutex('synchro_import_'.$oDataSource->GetKey());
$oMutex = new iTopMutex('synchro_import_'.$oDataSource->GetKey().'_'.MetaModel::GetConfig()->GetDBName().'_'.MetaModel::GetConfig()->GetDBSubname());
$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());
$oMutex = new iTopMutex('synchro_process_'.$this->m_oDataSource->GetKey().'_'.MetaModel::GetConfig()->GetDBName().'_'.MetaModel::GetConfig()->GetDBSubname());
try
{
$oMutex->Lock();

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2016 Combodo SARL
// Copyright (C) 2010-2013 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-2016 Combodo SARL
* @copyright Copyright (C) 2010-2013 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 with message: '.$e->getMessage();
$sMessage = 'Processing failed, the following exception occured: '.$e->getMessage();
}
$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;
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');
$oMutex = new iTopMutex('cron.'.$oConfig->GetDBName().'_'.$oConfig->GetDBSubname());
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 = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php';
$sServerURI = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/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 = utils::GetAbsoluteUrlAppRoot().'webservices/itop.wsdl.php';
$sWsdlUri = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/itop.wsdl.php';
if (isset($_REQUEST['service_category']) && (!empty($_REQUEST['service_category'])))
{
$sWsdlUri .= "?service_category=".$_REQUEST['service_category'];
$sWsdlUri .= "soapserver.php?service_category=".$_REQUEST['service_category'];
}
@@ -99,7 +99,7 @@ else
if (is_subclass_of($sPHPClass, 'WebServicesBase'))
{
$sServiceCategory = $sPHPClass;
$sSoapServerUri = utils::GetAbsoluteUrlAppRoot().'webservices/soapserver.php';
$sSoapServerUri = 'http'.(utils::IsConnectionSecure() ? 's' : '').'://'.$_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].dirname($_SERVER['SCRIPT_NAME']).'/../webservices/soapserver.php';
$sSoapServerUri .= "?service_category=$sServiceCategory";
echo "<li><a href=\"$sSoapServerUri\">$sServiceCategory</a></li>\n";
}