'; // No validation span for this one: it does handle its own validation!
+ $sHTMLValue .= '
';
+ $sHTMLValue .= '
';
+ $sHTMLValue .= "\n";
+
+ $oForm = $value->GetForm($sFormPrefix);
+ $oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oForm);
+ $aRenderRes = $oRenderer->Render();
+
+ $aFormHandlerOptions = array(
+ 'wizard_helper_var_name' => 'oWizardHelper'.$sFormPrefix,
+ 'custom_field_attcode' => $sAttCode
+ );
+ $sFormHandlerOptions = json_encode($aFormHandlerOptions);
+ $aFieldSetOptions = array(
+ 'field_identifier_attr' => 'data-field-id', // convention: fields are rendered into a div and are identified by this attribute
+ 'fields_list' => $aRenderRes,
+ 'fields_impacts' => $oForm->GetFieldsImpacts(),
+ 'form_path' => $oForm->GetId()
+ );
+ $sFieldSetOptions = json_encode($aFieldSetOptions);
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_handler.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/console_form_handler.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/field_set.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/form_field.js');
+ $oPage->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/subform_field.js');
+ $oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler($sFormHandlerOptions);");
+ $oPage->add_ready_script("$('#{$iId}_field_set').field_set($sFieldSetOptions);");
+ $oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler('alignColumns');");
+ $oPage->add_ready_script("$('#{$iId}_console_form').console_form_handler('option', 'field_set', $('#{$iId}_field_set'));");
+ // field_change must be processed to refresh the hidden value at anytime
+ $oPage->add_ready_script("$('#{$iId}_console_form .field_set').bind('field_change', function() { $('#{$iId}').val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); });");
+ // update_value is triggered when preparing the wizard helper object for ajax calls
+ $oPage->add_ready_script("$('#{$iId}').bind('update_value', function() { $(this).val(JSON.stringify($('#{$iId}_field_set').triggerHandler('get_current_values'))); });");
+ // validate is triggered by CheckFields, on all the input fields, once at page init and once before submitting the form
+ $oPage->add_ready_script("$('#{$iId}').bind('validate', function(evt, sFormId) { return ValidateCustomFields('$iId', sFormId) } );"); // Custom validation function
+ break;
+
case 'String':
default:
$aEventsList[] ='validate';
@@ -1925,7 +1974,7 @@ EOF
case 'radio_vertical':
$sHTMLValue = '';
$bVertical = ($sDisplayStyle != 'radio_horizontal');
- $sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, $sValidationField);
+ $sHTMLValue = $oPage->GetRadioButtons($aAllowedValues, $value, $iId, "attr_{$sFieldPrefix}{$sAttCode}{$sNameSuffix}", $bMandatory, $bVertical, $sValidationSpan.$sReloadSpan);
$aEventsList[] ='change';
break;
@@ -1946,13 +1995,13 @@ EOF
}
$sHTMLValue .= "\n";
}
- $sHTMLValue .= " {$sValidationField}\n";
+ $sHTMLValue .= " {$sValidationSpan}{$sReloadSpan}\n";
$aEventsList[] ='change';
}
}
else
{
- $sHTMLValue = " {$sValidationField}";
+ $sHTMLValue = " {$sValidationSpan}{$sReloadSpan}";
$aEventsList[] ='keyup';
$aEventsList[] ='change';
}
@@ -2977,6 +3026,10 @@ EOF
$this->Set($sAttCode, $iValue);
}
}
+ elseif ($oAttDef->GetEditClass() == 'CustomFields')
+ {
+ $this->Set($sAttCode, $value);
+ }
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() &&
(($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE)))
{
@@ -3089,6 +3142,10 @@ EOF
{
$value = $oAttDef->ReadValueFromPostedForm($sFormPrefix);
}
+ elseif ($oAttDef->GetEditClass() == 'CustomFields')
+ {
+ $value = $oAttDef->ReadValueFromPostedForm($this, $sFormPrefix);
+ }
else if (($oAttDef->GetEditClass() == 'LinkedSet') && !$oAttDef->IsIndirect() &&
(($oAttDef->GetEditMode() == LINKSET_EDITMODE_INPLACE) || ($oAttDef->GetEditMode() == LINKSET_EDITMODE_ADDREMOVE)) )
{
diff --git a/application/ui.extkeywidget.class.inc.php b/application/ui.extkeywidget.class.inc.php
index b7993579a..561f1b4b6 100644
--- a/application/ui.extkeywidget.class.inc.php
+++ b/application/ui.extkeywidget.class.inc.php
@@ -1,5 +1,5 @@
iId}\">";
+ $sValidationField = "iId}\">iId}\">";
$sHTMLValue = '';
$bVertical = ($sDisplayStyle != 'radio_horizontal');
$bExtensions = false;
@@ -305,7 +305,7 @@ EOF
}
if (($sDisplayStyle == 'select') || ($sDisplayStyle == 'list'))
{
- $sHTMLValue .= "iId}\">";
+ $sHTMLValue .= "iId}\">iId}\">";
}
$sHTMLValue .= ""; // end of no wrap
return $sHTMLValue;
diff --git a/application/ui.passwordwidget.class.inc.php b/application/ui.passwordwidget.class.inc.php
index d348649fa..0950dd08a 100644
--- a/application/ui.passwordwidget.class.inc.php
+++ b/application/ui.passwordwidget.class.inc.php
@@ -58,7 +58,7 @@ class UIPasswordWidget
$sConfirmPasswordValue = $aPasswordValues ? $aPasswordValues['confirm'] : '*****';
$sChangedValue = (($sPasswordValue != '*****') || ($sConfirmPasswordValue != '*****')) ? 1 : 0;
$sHtmlValue = '';
- $sHtmlValue = ' ';
+ $sHtmlValue = ' ';
$sHtmlValue .= ' '.Dict::S('UI:PasswordConfirm').' ';
$sHtmlValue .= '';
diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php
index f9b99f46b..d4c8eb20a 100644
--- a/core/attributedef.class.inc.php
+++ b/core/attributedef.class.inc.php
@@ -31,6 +31,9 @@ require_once('ormstopwatch.class.inc.php');
require_once('ormpassword.class.inc.php');
require_once('ormcaselog.class.inc.php');
require_once('htmlsanitizer.class.inc.php');
+require_once(APPROOT.'sources/autoload.php');
+require_once('customfieldshandler.class.inc.php');
+require_once('ormcustomfieldsvalue.class.inc.php');
/**
* MissingColumnException - sent if an attribute is being created but the column is missing in the row
@@ -178,7 +181,6 @@ abstract class AttributeDefinition
private function ConsistencyCheck()
{
-
// Check that any mandatory param has been specified
//
$aExpectedParams = $this->ListExpectedParams();
@@ -192,7 +194,18 @@ abstract class AttributeDefinition
throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
}
}
- }
+ }
+
+ /**
+ * Check the validity of the given value
+ * @param DBObject $oHostObject
+ * @param string An error if any, null otherwise
+ */
+ public function CheckValue(DBObject $oHostObject, $value)
+ {
+ // todo: factorize here the cases implemented into DBObject
+ return true;
+ }
// table, key field, name field
public function ListDBJoins()
@@ -212,8 +225,9 @@ abstract class AttributeDefinition
public function IsExternalField() {return false;}
public function IsWritable() {return false;}
public function LoadInObject() {return true;}
+ public function LoadFromDB() {return true;}
public function AlwaysLoadInTables() {return $this->GetOptional('always_load_in_tables', false);}
- public function GetValue($oHostObject){return null;} // must return the value if LoadInObject returns false
+ public function GetValue($oHostObject, $bOriginal = false){return null;} // must return the value if LoadInObject returns false
public function IsNullAllowed() {return true;}
public function GetCode() {return $this->m_sCode;}
public function GetMirrorLinkAttribute() {return null;}
@@ -417,7 +431,7 @@ abstract class AttributeDefinition
return call_user_func($sComputeFunc);
}
- abstract public function GetDefaultValue();
+ abstract public function GetDefaultValue(DBObject $oHostObject = null);
//
// To be overloaded in subclasses
@@ -503,7 +517,7 @@ abstract class AttributeDefinition
/**
* List the available verbs for 'GetForTemplate'
*/
- public static function EnumTemplateVerbs()
+ public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text (unlocalized) representation',
@@ -692,21 +706,9 @@ class AttributeLinkedSet extends AttributeDefinition
public function GetValuesDef() {return $this->Get("allowed_values");}
public function GetPrerequisiteAttributes($sClass = null) {return $this->Get("depends_on");}
- public function GetDefaultValue($aArgs = array())
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
- // Note: so far, this feature is a prototype,
- // later, the argument 'this' should always be present in the arguments
- //
- if (($this->IsParam('default_value')) && array_key_exists('this', $aArgs))
- {
- $aValues = $this->Get('default_value')->GetValues($aArgs);
- $oSet = DBObjectSet::FromArray($this->Get('linked_class'), $aValues);
- return $oSet;
- }
- else
- {
- return DBObjectSet::FromScratch($this->Get('linked_class'));
- }
+ return DBObjectSet::FromScratch($this->Get('linked_class'));
}
public function GetTrackingLevel()
@@ -849,7 +851,7 @@ class AttributeLinkedSet extends AttributeDefinition
/**
* List the available verbs for 'GetForTemplate'
*/
- public static function EnumTemplateVerbs()
+ public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text (unlocalized) representation',
@@ -1295,7 +1297,7 @@ class AttributeDBFieldVoid extends AttributeDefinition
public function IsScalar() {return true;}
public function IsWritable() {return true;}
public function GetSQLExpr() {return $this->Get("sql");}
- public function GetDefaultValue() {return $this->MakeRealValue("", null);}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);}
public function IsNullAllowed() {return false;}
//
@@ -1368,7 +1370,7 @@ class AttributeDBField extends AttributeDBFieldVoid
{
return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
}
- public function GetDefaultValue() {return $this->MakeRealValue($this->Get("default_value"), null);}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue($this->Get("default_value"), $oHostObject);}
public function IsNullAllowed() {return $this->Get("is_null_allowed");}
}
@@ -1480,7 +1482,7 @@ class AttributeObjectKey extends AttributeDBFieldVoid
public function GetEditClass() {return "String";}
protected function GetSQLCol($bFullSpec = false) {return "INT(11)".($bFullSpec ? " DEFAULT 0" : "");}
- public function GetDefaultValue() {return 0;}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return 0;}
public function IsNullAllowed()
{
return $this->Get("is_null_allowed");
@@ -1854,9 +1856,9 @@ class AttributeClass extends AttributeString
parent::__construct($sCode, $aParams);
}
- public function GetDefaultValue()
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
- $sDefault = parent::GetDefaultValue();
+ $sDefault = parent::GetDefaultValue($oHostObject);
if (!$this->IsNullAllowed() && $this->IsNull($sDefault))
{
// For this kind of attribute specifying null as default value
@@ -1953,7 +1955,7 @@ class AttributeFinalClass extends AttributeString
{
$this->m_sValue = $sValue;
}
- public function GetDefaultValue()
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->m_sValue;
}
@@ -2312,7 +2314,7 @@ class AttributeText extends AttributeString
{
$sClass = $aMatches[1];
$sName = $aMatches[2];
-
+
if (MetaModel::IsValidClass($sClass))
{
$sClassLabel = MetaModel::GetName($sClass);
@@ -2357,7 +2359,7 @@ class AttributeText extends AttributeString
{
$sClassLabel = $aMatches[1];
$sName = $aMatches[2];
-
+
if (!MetaModel::IsValidClass($sClassLabel))
{
$sClass = MetaModel::GetClassFromLabel($sClassLabel);
@@ -2548,8 +2550,8 @@ class AttributeCaseLog extends AttributeLongText
return (string) $value;
}
}
-
- public function GetDefaultValue() {return new ormCaseLog();}
+
+ public function GetDefaultValue(DBObject $oHostObject = null) {return new ormCaseLog();}
public function Equals($val1, $val2) {return ($val1->GetText() == $val2->GetText());}
@@ -2719,7 +2721,7 @@ class AttributeCaseLog extends AttributeLongText
/**
* List the available verbs for 'GetForTemplate'
*/
- public static function EnumTemplateVerbs()
+ public function EnumTemplateVerbs()
{
return array(
'' => 'Plain text representation of all the log entries',
@@ -3372,9 +3374,9 @@ class AttributeDateTime extends AttributeDBField
// This has been done at the time when itop was using TIMESTAMP columns,
// now that iTop is using DATETIME columns, it seems possible to have IsNullAllowed returning false... later when this is needed
public function IsNullAllowed() {return true;}
- public function GetDefaultValue()
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
- $default = parent::GetDefaultValue();
+ $default = parent::GetDefaultValue($oHostObject);
if (!parent::IsNullAllowed())
{
@@ -3769,7 +3771,7 @@ class AttributeExternalKey extends AttributeDBFieldVoid
public function GetDisplayStyle() { return $this->GetOptional('display_style', 'select'); }
- public function GetDefaultValue() {return 0;}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return 0;}
public function IsNullAllowed()
{
if (MetaModel::GetConfig()->Get('disable_mandatory_ext_keys'))
@@ -4163,10 +4165,10 @@ class AttributeExternalField extends AttributeDefinition
return $oExtAttDef->GetSQLExpr();
}
- public function GetDefaultValue()
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
$oExtAttDef = $this->GetExtAttDef();
- return $oExtAttDef->GetDefaultValue();
+ return $oExtAttDef->GetDefaultValue();
}
public function IsNullAllowed()
{
@@ -4308,8 +4310,8 @@ class AttributeBlob extends AttributeDefinition
public function IsDirectField() {return true;}
public function IsScalar() {return true;}
public function IsWritable() {return true;}
- public function GetDefaultValue() {return "";}
- public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return "";}
+ public function IsNullAllowed(DBObject $oHostObject = null) {return $this->GetOptional("is_null_allowed", false);}
public function GetEditValue($sValue, $oHostObj = null)
{
@@ -4569,7 +4571,7 @@ class AttributeStopWatch extends AttributeDefinition
public function IsDirectField() {return true;}
public function IsScalar() {return true;}
public function IsWritable() {return false;}
- public function GetDefaultValue() {return $this->NewStopWatch();}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return $this->NewStopWatch();}
public function GetEditValue($value, $oHostObj = null)
{
@@ -5186,9 +5188,21 @@ class AttributeSubItem extends AttributeDefinition
public function IsDirectField() {return true;}
public function IsScalar() {return true;}
public function IsWritable() {return false;}
- public function GetDefaultValue() {return null;}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return null;}
// public function IsNullAllowed() {return false;}
- public function LoadInObject() {return false;} // if this verb returns true, then GetValue must be implemented
+
+ public function LoadInObject() {return false;} // if this verb returns false, then GetValue must be implemented
+
+ /**
+ * Used by DBOBject::Get()
+ */
+ public function GetValue($oHostObject, $bOriginal = false)
+ {
+ $oParent = $this->GetTargetAttDef();
+ $parentValue = $oHostObject->GetStrict($oParent->GetCode());
+ $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject);
+ return $res;
+ }
//
// protected function ScalarToSQL($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
@@ -5237,16 +5251,6 @@ class AttributeSubItem extends AttributeDefinition
return $res;
}
- /**
- * Used by DBOBject::Get()
- */
- public function GetValue($parentValue, $oHostObject = null)
- {
- $oParent = $this->GetTargetAttDef();
- $res = $oParent->GetSubItemValue($this->Get('item_code'), $parentValue, $oHostObject);
- return $res;
- }
-
public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
{
$oParent = $this->GetTargetAttDef();
@@ -5303,7 +5307,7 @@ class AttributeOneWayPassword extends AttributeDefinition
public function IsDirectField() {return true;}
public function IsScalar() {return true;}
public function IsWritable() {return true;}
- public function GetDefaultValue() {return "";}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return "";}
public function IsNullAllowed() {return $this->GetOptional("is_null_allowed", false);}
// Facilitate things: allow the user to Set the value from a string or from an ormPassword (already encrypted)
@@ -5676,7 +5680,7 @@ class AttributeComputedFieldVoid extends AttributeDefinition
public function IsScalar() {return true;}
public function IsWritable() {return false;}
public function GetSQLExpr() {return null;}
- public function GetDefaultValue() {return $this->MakeRealValue("", null);}
+ public function GetDefaultValue(DBObject $oHostObject = null) {return $this->MakeRealValue("", $oHostObject);}
public function IsNullAllowed() {return false;}
//
@@ -5827,7 +5831,7 @@ class AttributeFriendlyName extends AttributeComputedFieldVoid
{
$this->m_sValue = $sValue;
}
- public function GetDefaultValue()
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
return $this->m_sValue;
}
@@ -5913,7 +5917,7 @@ class AttributeRedundancySettings extends AttributeDBField
return 20;
}
- public function GetDefaultValue($aArgs = array())
+ public function GetDefaultValue(DBObject $oHostObject = null)
{
$sRet = 'disabled';
if ($this->Get('enabled'))
@@ -6268,3 +6272,251 @@ class AttributeRedundancySettings extends AttributeDBField
return $sRet;
}
}
+
+/**
+ * Custom fields managed by an external implementation
+ *
+ * @package iTopORM
+ */
+class AttributeCustomFields extends AttributeDefinition
+{
+ static public function ListExpectedParams()
+ {
+ return array_merge(parent::ListExpectedParams(), array("handler_class"));
+ }
+
+ public function GetEditClass() {return "CustomFields";}
+ public function IsWritable() {return true;}
+ public function LoadFromDB() {return false;} // See ReadValue...
+
+ public function GetDefaultValue(DBObject $oHostObject = null)
+ {
+ return new ormCustomFieldsValue($oHostObject, $this->GetCode());
+ }
+
+ public function GetBasicFilterOperators() {return array();}
+ public function GetBasicFilterLooseOperator() {return '';}
+ public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';}
+
+ /**
+ * @param DBObject $oHostObject
+ * @param ormCustomFieldsValue|null $oValue
+ * @return CustomFieldsHandler
+ */
+ public function GetHandler(DBObject $oHostObject, ormCustomFieldsValue $oValue = null)
+ {
+ $sHandlerClass = $this->Get('handler_class');
+ $oHandler = new $sHandlerClass($oHostObject, $this->GetCode());
+ if (!is_null($oValue))
+ {
+ $oHandler->SetCurrentValues($oValue->GetValues());
+ }
+ return $oHandler;
+ }
+
+ public function GetPrerequisiteAttributes($sClass = null)
+ {
+ $sHandlerClass = $this->Get('handler_class');
+ return $sHandlerClass::GetPrerequisiteAttributes($sClass);
+ }
+
+ public function GetEditValue($sValue, $oHostObj = null)
+ {
+ return 'GetEditValueNotImplemented for '.get_class($this);
+ }
+
+ /**
+ * Makes the string representation out of the values given by the form defined in GetDisplayForm
+ */
+ public function ReadValueFromPostedForm($oHostObject, $sFormPrefix)
+ {
+ $aRawData = json_decode(utils::ReadPostedParam("attr_{$sFormPrefix}{$this->GetCode()}", '{}', 'raw_data'), true);
+ return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aRawData);
+ }
+
+ public function MakeRealValue($proposedValue, $oHostObject)
+ {
+ if (is_object($proposedValue) && ($proposedValue instanceof ormCustomFieldsValue))
+ {
+ return $proposedValue;
+ }
+ elseif (is_string($proposedValue))
+ {
+ $aValues = json_decode($proposedValue, true);
+ return new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
+ }
+ throw new Exception('Unexpected type for the value of a custom fields attribute: '.gettype($proposedValue));
+ }
+
+ /**
+ * Override to build the relevant form field
+ *
+ * When called first, $oFormField is null and will be created (eg. Make). Then when the ::parent is called and the $oFormField is passed, MakeFormField behaves more like a Prepare.
+ */
+ public function MakeFormField(DBObject $oObject, $oFormField = null)
+ {
+ if ($oFormField === null)
+ {
+ $oField = new Combodo\iTop\Form\Field\SubFormField($this->GetCode(), $sParentFormId);
+ $oField->SetForm($this->GetForm($oObject));
+ }
+ parent::MakeFormField($oObject, $oFormField);
+
+ return $oFormField;
+ }
+
+ /**
+ * @param DBObject $oHostObject
+ * @return Combodo\iTop\Form\Form
+ */
+ public function GetForm(DBObject $oHostObject, $sFormPrefix = null)
+ {
+ $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
+ $sFormId = is_null($sFormPrefix) ? 'cf_'.$this->GetCode() : $sFormPrefix.'_cf_'.$this->GetCode();
+ $oHandler->BuildForm($sFormId);
+ return $oHandler->GetForm();
+ }
+
+ /**
+ * Read the data from where it has been stored. This verb must be implemented as soon as LoadFromDB returns false and LoadInObject returns true
+ * @param $oHostObject
+ * @return ormCustomFieldsValue
+ */
+ public function ReadValue($oHostObject)
+ {
+ $oHandler = $this->GetHandler($oHostObject);
+ $aValues = $oHandler->ReadValues();
+ $oRet = new ormCustomFieldsValue($oHostObject, $this->GetCode(), $aValues);
+ return $oRet;
+ }
+
+ /**
+ * Record the data (currently in the processing of recording the host object)
+ * It is assumed that the data has been checked prior to calling Write()
+ * @param DBObject $oHostObject
+ * @param ormCustomFieldsValue|null $oValue (null is the default value)
+ */
+ public function WriteValue(DBObject $oHostObject, ormCustomFieldsValue $oValue = null)
+ {
+ $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
+ if (is_null($oValue))
+ {
+ $aValues = array();
+ }
+ else
+ {
+ $aValues = $oValue->GetValues();
+ }
+ return $oHandler->WriteValues($aValues);
+ }
+
+ /**
+ * Check the validity of the data
+ * @param DBObject $oHostObject
+ * @param $value
+ * @return bool|string true or error message
+ */
+ public function CheckValue(DBObject $oHostObject, $value)
+ {
+ try
+ {
+ $oHandler = $this->GetHandler($oHostObject, $value);
+ $oHandler->BuildForm();
+ $oForm = $oHandler->GetForm();
+ $oForm->Validate();
+ if ($oForm->GetValid())
+ {
+ $ret = true;
+ }
+ else
+ {
+ $aMessages = array();
+ foreach ($oForm->GetErrorMessages() as $sFieldId => $aFieldMessages)
+ {
+ $aMessages[] = $sFieldId.': '.implode(', ', $aFieldMessages);
+ }
+ $ret = 'Invalid value: '.implode(', ', $aMessages);
+ }
+ }
+ catch (Exception $e)
+ {
+ $ret = $e->getMessage();
+ }
+ return $ret;
+ }
+
+ /**
+ * Cleanup data upon object deletion (object id still available here)
+ * @param DBObject $oHostObject
+ */
+ public function DeleteValue(DBObject $oHostObject)
+ {
+ $oHandler = $this->GetHandler($oHostObject, $oHostObject->Get($this->GetCode()));
+ return $oHandler->DeleteValues();
+ }
+
+ public function GetAsHTML($value, $oHostObject = null, $bLocalize = true)
+ {
+ return $value->GetAsHTML($bLocalize);
+ }
+
+ public function GetAsXML($value, $oHostObject = null, $bLocalize = true)
+ {
+ return $value->GetAsXML($bLocalize);
+ }
+
+ public function GetAsCSV($value, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true, $bConvertToPlainText = false)
+ {
+ return $value->GetAsCSV($sSeparator, $sTextQualifier, $bLocalize, $bConvertToPlainText);
+ }
+
+ /**
+ * List the available verbs for 'GetForTemplate'
+ */
+ public function EnumTemplateVerbs()
+ {
+ $sHandlerClass = $this->Get('handler_class');
+ return $sHandlerClass::EnumTemplateVerbs();
+ }
+
+ /**
+ * 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)
+ {
+ return $value->GetForTemplate($sVerb, $bLocalize);
+ }
+
+ public function MakeValueFromString($sProposedValue, $bLocalizedValue = false, $sSepItem = null, $sSepAttribute = null, $sSepValue = null, $sAttributeQualifier = null)
+ {
+ return null;
+ }
+
+ /**
+ * Helper to get a value that will be JSON encoded
+ * The operation is the opposite to FromJSONToValue
+ */
+ public function GetForJSON($value)
+ {
+ return null;
+ }
+
+ /**
+ * Helper to form a value, given JSON decoded data
+ * The operation is the opposite to GetForJSON
+ */
+ public function FromJSONToValue($json)
+ {
+ return null;
+ }
+
+ public function Equals($val1, $val2)
+ {
+ return $val1->Equals($val2);
+ }
+}
+
diff --git a/core/cmdbobject.class.inc.php b/core/cmdbobject.class.inc.php
index bdae0c013..c9e424d65 100644
--- a/core/cmdbobject.class.inc.php
+++ b/core/cmdbobject.class.inc.php
@@ -302,14 +302,10 @@ abstract class CMDBObject extends DBObject
{
// Stop watches - record changes for sub items only (they are visible, the rest is not visible)
//
- if (is_null($original))
- {
- $original = new OrmStopWatch();
- }
foreach ($oAttDef->ListSubItems() as $sSubItemAttCode => $oSubItemAttDef)
{
- $item_value = $oSubItemAttDef->GetValue($value);
- $item_original = $oSubItemAttDef->GetValue($original);
+ $item_value = $oSubItemAttDef->GetValue($this);
+ $item_original = $oSubItemAttDef->GetValue($this, true);
if ($item_value != $item_original)
{
diff --git a/core/customfieldshandler.class.inc.php b/core/customfieldshandler.class.inc.php
new file mode 100644
index 000000000..19d21f1fe
--- /dev/null
+++ b/core/customfieldshandler.class.inc.php
@@ -0,0 +1,127 @@
+
+
+use Combodo\iTop\Form\Form;
+use Combodo\iTop\Form\FormManager;
+
+/**
+ * Base class to implement a handler for AttributeCustomFields
+ *
+ * @copyright Copyright (C) 2016 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+abstract class CustomFieldsHandler
+{
+ protected $oHostObject;
+ protected $sAttCode;
+ protected $aValues;
+ protected $oForm;
+
+ /**
+ * This constructor's prototype must be frozen.
+ * Any specific behavior must be implemented in BuildForm()
+ *
+ * @param DBObject $oHostObject
+ * @param $sAttCode
+ */
+ final public function __construct(DBObject $oHostObject, $sAttCode)
+ {
+ $this->oHostObject = $oHostObject;
+ $this->sAttCode = $sAttCode;
+ $this->aValues = null;
+ }
+
+ abstract public function BuildForm($sFormId);
+
+ /**
+ *
+ * @return \Combodo\iTop\Form\Form
+ */
+ public function GetForm()
+ {
+ return $this->oForm;
+ }
+
+ public function SetCurrentValues($aValues)
+ {
+ $this->aValues = $aValues;
+ }
+
+ static public function GetPrerequisiteAttributes($sClass = null)
+ {
+ return array();
+ }
+
+ /**
+ * List the available verbs for 'GetForTemplate'
+ */
+ static public function EnumTemplateVerbs()
+ {
+ return array();
+ }
+
+ /**
+ * Get various representations of the value, for insertion into a template (e.g. in Notifications)
+ * @param $aValues array The current values
+ * @param $sVerb string The verb specifying the representation of the value
+ * @param $bLocalize bool Whether or not to localize the value
+ * @return string
+ */
+ abstract public function GetForTemplate($aValues, $sVerb, $bLocalize = true);
+
+ /**
+ * @param $aValues
+ * @param bool|true $bLocalize
+ * @return mixed
+ */
+ abstract public function GetAsHTML($aValues, $bLocalize = true);
+
+ /**
+ * @param $aValues
+ * @param bool|true $bLocalize
+ * @return mixed
+ */
+ abstract public function GetAsXML($aValues, $bLocalize = true);
+
+ /**
+ * @param $aValues
+ * @param string $sSeparator
+ * @param string $sTextQualifier
+ * @param bool|true $bLocalize
+ * @return mixed
+ */
+ abstract public function GetAsCSV($aValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true);
+
+ /**
+ * @return array Associative array id => value
+ */
+ abstract public function ReadValues();
+
+ /**
+ * Record the data (currently in the processing of recording the host object)
+ * It is assumed that the data has been checked prior to calling Write()
+ * @param array Associative array id => value
+ */
+ abstract public function WriteValues($aValues);
+
+ /**
+ * Cleanup data upon object deletion (object id still available here)
+ */
+ abstract public function DeleteValues();
+}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index 469eb697b..c0ed8e287 100644
--- a/core/dbobject.class.php
+++ b/core/dbobject.class.php
@@ -116,7 +116,7 @@ abstract class DBObject implements iDisplay
// set default values
foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
{
- $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+ $this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue($this);
$this->m_aOrigValues[$sAttCode] = null;
if ($oAttDef->IsExternalField() || ($oAttDef instanceof AttributeFriendlyName))
{
@@ -291,16 +291,30 @@ abstract class DBObject implements iDisplay
if (!$oAttDef->LoadInObject()) continue;
- // Note: we assume that, for a given attribute, if it can be loaded,
- // then one column will be found with an empty suffix, the others have a suffix
- // Take care: the function isset will return false in case the value is null,
- // which is something that could happen on open joins
- $sAttRef = $sClassAlias.$sAttCode;
-
- if (array_key_exists($sAttRef, $aRow))
+ unset($value);
+ $bIsDefined = false;
+ if ($oAttDef->LoadFromDB())
{
- $value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
+ // Note: we assume that, for a given attribute, if it can be loaded,
+ // then one column will be found with an empty suffix, the others have a suffix
+ // Take care: the function isset will return false in case the value is null,
+ // which is something that could happen on open joins
+ $sAttRef = $sClassAlias.$sAttCode;
+ if (array_key_exists($sAttRef, $aRow))
+ {
+ $value = $oAttDef->FromSQLToValue($aRow, $sAttRef);
+ $bIsDefined = true;
+ }
+ }
+ else
+ {
+ $value = $oAttDef->ReadValue($this);
+ $bIsDefined = true;
+ }
+
+ if ($bIsDefined)
+ {
$this->m_aCurrValues[$sAttCode] = $value;
if (is_object($value))
{
@@ -380,7 +394,7 @@ abstract class DBObject implements iDisplay
{
if (($oDef->IsExternalField() || ($oDef instanceof AttributeFriendlyName)) && ($oDef->GetKeyAttCode() == $sAttCode))
{
- $this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
+ $this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
unset($this->m_aLoadedAtt[$sCode]);
}
}
@@ -492,9 +506,7 @@ abstract class DBObject implements iDisplay
if (!$oAttDef->LoadInObject())
{
- $sParentAttCode = $oAttDef->GetParentAttCode();
- $parentValue = $this->GetStrict($sParentAttCode);
- $value = $oAttDef->GetValue($parentValue, $this);
+ $value = $oAttDef->GetValue($this);
}
else
{
@@ -550,7 +562,7 @@ abstract class DBObject implements iDisplay
}
else
{
- $this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue();
+ $this->m_aCurrValues[$sCode] = $oDef->GetDefaultValue($this);
}
$this->m_aLoadedAtt[$sCode] = true;
}
@@ -1109,6 +1121,10 @@ abstract class DBObject implements iDisplay
return "Wrong format [$toCheck]";
}
}
+ else
+ {
+ return $oAtt->CheckValue($this, $toCheck);
+ }
return true;
}
@@ -1405,6 +1421,17 @@ abstract class DBObject implements iDisplay
}
}
+ // used both by insert/update
+ private function WriteExternalAttributes()
+ {
+ foreach (MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode => $oAttDef)
+ {
+ if (!$oAttDef->LoadInObject()) continue;
+ if ($oAttDef->LoadFromDB()) continue;
+ $oAttDef->WriteValue($this, $this->m_aCurrValues[$sAttCode]);
+ }
+ }
+
// Note: this is experimental - it was designed to speed up the setup of iTop
// Known limitations:
// - does not work with multi-table classes (issue with the unique id to maintain in several tables)
@@ -1591,6 +1618,8 @@ abstract class DBObject implements iDisplay
}
$this->DBWriteLinks();
+ $this->WriteExternalAttributes();
+
$this->m_bIsInDB = true;
$this->m_bDirty = false;
@@ -1878,6 +1907,7 @@ abstract class DBObject implements iDisplay
}
$this->DBWriteLinks();
+ $this->WriteExternalAttributes();
$this->m_bDirty = false;
$this->AfterUpdate();
@@ -1981,6 +2011,10 @@ abstract class DBObject implements iDisplay
}
MetaModel::HKReplugBranch($iNewLeft, $iNewLeft + $iDelta - 1, $oAttDef, $sTable);
}
+ elseif (!$oAttDef->LoadFromDB())
+ {
+ $oAttDef->DeleteValue($this);
+ }
}
foreach(MetaModel::EnumParentClasses(get_class($this), ENUM_PARENT_CLASSES_ALL) as $sParentClass)
@@ -2222,7 +2256,7 @@ abstract class DBObject implements iDisplay
public function Reset($sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
- $this->Set($sAttCode, $oAttDef->GetDefaultValue());
+ $this->Set($sAttCode, $oAttDef->GetDefaultValue($this));
return true;
}
diff --git a/core/ormcustomfieldsvalue.class.inc.php b/core/ormcustomfieldsvalue.class.inc.php
new file mode 100644
index 000000000..5720560f1
--- /dev/null
+++ b/core/ormcustomfieldsvalue.class.inc.php
@@ -0,0 +1,101 @@
+
+
+
+/**
+ * Base class to hold the value managed by CustomFieldsHandler
+ *
+ * @copyright Copyright (C) 2016 Combodo SARL
+ * @license http://opensource.org/licenses/AGPL-3.0
+ */
+
+class ormCustomFieldsValue
+{
+ protected $oHostObject;
+ protected $sAttCode;
+ protected $aCurrentValues;
+
+ /**
+ * @param DBObject $oHostObject
+ * @param $sAttCode
+ */
+ public function __construct(DBObject $oHostObject, $sAttCode, $aCurrentValues = null)
+ {
+ $this->oHostObject = $oHostObject;
+ $this->sAttCode = $sAttCode;
+ $this->aCurrentValues = $aCurrentValues;
+ }
+
+ public function GetValues()
+ {
+ return $this->aCurrentValues;
+ }
+
+ /**
+ * Wrapper used when the only thing you have is the value...
+ * @return \Combodo\iTop\Form\Form
+ */
+ public function GetForm()
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
+ return $oAttDef->GetForm($this->oHostObject);
+ }
+
+ public function GetAsHTML($bLocalize = true)
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
+ $oHandler = $oAttDef->GetHandler($this->oHostObject, $this);
+ return $oHandler->GetAsHTML($this->aCurrentValues, $bLocalize);
+ }
+
+ public function GetAsXML($bLocalize = true)
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
+ $oHandler = $oAttDef->GetHandler($this->oHostObject, $this);
+ return $oHandler->GetAsXML($this->aCurrentValues, $bLocalize);
+ }
+
+ public function GetAsCSV($sSeparator = ',', $sTextQualifier = '"', $bLocalize = true)
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
+ $oHandler = $oAttDef->GetHandler($this->oHostObject, $this);
+ return $oHandler->GetAsCSV($this->aCurrentValues, $sSeparator = ',', $sTextQualifier = '"', $bLocalize = true);
+ }
+
+ /**
+ * 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 $bLocalize bool Whether or not to localize the value
+ */
+ public function GetForTemplate($sVerb, $bLocalize = true)
+ {
+ $oAttDef = MetaModel::GetAttributeDef(get_class($this->oHostObject), $this->sAttCode);
+ $oHandler = $oAttDef->GetHandler($this->oHostObject, $this);
+ return 'template...verb='.$sVerb.' sur "'.json_encode($this->aCurrentValues).'"';
+ }
+
+ /**
+ * @param ormCustomFieldsValue $fellow
+ * @return bool
+ */
+ public function Equals(ormCustomFieldsValue $oReference)
+ {
+ return (json_encode($this->aCurrentValues) === json_encode($oReference->aCurrentValues));
+ }
+}
diff --git a/js/console_form_handler.js b/js/console_form_handler.js
new file mode 100644
index 000000000..7432157a9
--- /dev/null
+++ b/js/console_form_handler.js
@@ -0,0 +1,115 @@
+// Copyright (C) 2010-2016 Combodo SARL
+//
+// This file is part of iTop.
+//
+// iTop is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// iTop is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with iTop. If not, see
+
+//iTop Form handler
+;
+$(function()
+{
+ // the widget definition, where 'itop' is the namespace,
+ // 'consoleform_handler' the widget name
+ $.widget( 'itop.console_form_handler', $.itop.form_handler,
+ {
+ // default options
+ options:
+ {
+ wizard_helper_var_name: '', // Name of the global variable pointing to the wizard helper
+ custom_field_attcode: ''
+ },
+
+ // the constructor
+ _create: function()
+ {
+ var me = this;
+
+ this.element
+ .addClass('console_form_handler');
+
+ this.options.oWizardHelper = window[this.options.wizard_helper_var_name];
+
+ this._super();
+ },
+
+ // events bound via _bind are removed automatically
+ // revert other modifications here
+ _destroy: function()
+ {
+ this.element
+ .removeClass('console_form_handler');
+ this._super();
+ },
+ _onUpdateFields: function(event, data)
+ {
+ var me = this;
+ var sFormPath = data.form_path;
+ var sUpdateUrl = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php';
+
+ $(this.element.find('[data-form-path="' + sFormPath + '"]')).block({message:''});
+ $.post(
+ sUpdateUrl,
+ {
+ operation: 'custom_fields_update',
+ attcode: this.options.custom_field_attcode,
+ //current_values: this.getCurrentValues(),
+ requested_fields: data.requested_fields,
+ form_path: sFormPath,
+ json_obj: this.options.oWizardHelper.UpdateWizardToJSON()
+ },
+ function(data){
+ me._onUpdateSuccess(data, sFormPath);
+ }
+ )
+ .fail(function(data){ me._onUpdateFailure(data, sFormPath); })
+ .always(function(data){
+ me.alignColumns();
+ $(me.element.find('[data-form-path="' + sFormPath + '"]')).unblock();
+ me._onUpdateAlways(data, sFormPath);
+ });
+ },
+ // On initialization or update
+ alignColumns: function()
+ {
+ var iMaxWidth = 0;
+ var oLabels = $(this.element.find('td.form-field-label'));
+ // Reset the width to the automatic (original) value
+ oLabels.width('');
+ oLabels.each(function() {
+ iMaxWidth = Math.max(iMaxWidth, $(this).width());
+ });
+ oLabels.width(iMaxWidth);
+ },
+ // Intended for overloading in derived classes
+ _onSubmitClick: function()
+ {
+ },
+ // Intended for overloading in derived classes
+ _onCancelClick: function()
+ {
+ },
+ // Intended for overloading in derived classes
+ _onUpdateFailure: function(data)
+ {
+ },
+ // Intended for overloading in derived classes
+ _disableFormBeforeLoading: function()
+ {
+ },
+ // Intended for overloading in derived classes
+ _enableFormAfterLoading: function()
+ {
+ },
+ });
+});
diff --git a/js/extkeywidget.js b/js/extkeywidget.js
index 6cfe63dc5..4e179a1c8 100644
--- a/js/extkeywidget.js
+++ b/js/extkeywidget.js
@@ -1,4 +1,4 @@
-// Copyright (C) 2010-2012 Combodo SARL
+// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -28,7 +28,6 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
this.ajax_request = null;
this.bSelectMode = bSelectMode; // true if the edited field is a SELECT, false if it's an autocomplete
this.bSearchMode = bSearchMode; // true if selecting a value in the context of a search form
- this.v_html = '';
var me = this;
this.Init = function()
@@ -55,8 +54,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Query the server to get the form to search for target objects
if (me.bSelectMode)
{
- me.v_html = $('#v_'+me.id).html();
- $('#v_'+me.id).html('');
+ $('#fstatus_'+me.id).html('');
}
else
{
@@ -284,8 +282,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
// Query the server to get the form to create a target object
if (me.bSelectMode)
{
- me.v_html = $('#v_'+me.id).html();
- $('#v_'+me.id).html('');
+ $('#fstatus_'+me.id).html('');
}
else
{
@@ -336,7 +333,6 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
{
if (me.bSelectMode)
{
- $('#v_'+me.id).html(me.v_html);
}
else
{
@@ -446,8 +442,7 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
if (me.bSelectMode)
{
- me.v_html = $('#v_'+me.id).html();
- $('#v_'+me.id).html('');
+ $('#fstatus_'+me.id).html('');
}
else
{
@@ -501,7 +496,6 @@ function ExtKeyWidget(id, sTargetClass, sFilter, sTitle, bSelectMode, oWizHelper
{
if (me.bSelectMode)
{
- $('#v_'+me.id).html(me.v_html);
}
else
{
diff --git a/js/field_set.js b/js/field_set.js
index e3d808d7f..22fbe7258 100644
--- a/js/field_set.js
+++ b/js/field_set.js
@@ -1,3 +1,20 @@
+// Copyright (C) 2010-2016 Combodo SARL
+//
+// This file is part of iTop.
+//
+// iTop is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// iTop is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with iTop. If not, see
+
//iTop Field set
//Used by itop.form_handler and itop.subform_field to list their fields
;
@@ -36,15 +53,15 @@ $(function()
this.element
.bind('field_change', function(oEvent, oData){
- console.log('field_set: field_change');
+ //console.log('field_set: field_change');
me._onFieldChange(oEvent, oData);
})
.bind('update_form', function(oEvent, oData){
- console.log('field_set: update_form');
+ //console.log('field_set: update_form');
me._onUpdateForm(oEvent, oData);
})
.bind('get_current_values', function(oEvent, oData){
- console.log('field_set: get_current_values');
+ //console.log('field_set: get_current_values');
return me._onGetCurrentValues(oEvent, oData);
})
.bind('validate', function(oEvent, oData){
@@ -52,7 +69,7 @@ $(function()
{
oData = {};
}
- console.log('field_set: validate');
+ //console.log('field_set: validate');
return me._onValidate(oEvent, oData);
});
@@ -175,8 +192,9 @@ $(function()
}
// Adding code to the dom
- this.options.script_element.append('\n\n// Appended by update on ' + Date() + '\n' + this.buildData.script_code);
- this.options.style_element.append('\n\n// Appended by update on ' + Date() + '\n' + this.buildData.style_code);
+ // Note : We use text() instead of append(), otherwise the code will be interpreted as DOM tags (text + + ...) and can break some browsers
+ this.options.script_element.text( this.options.script_element.text() + '\n\n// Appended by update on ' + Date() + '\n' + this.buildData.script_code);
+ this.options.style_element.text( this.options.style_element.text() + '\n\n// Appended by update on ' + Date() + '\n' + this.buildData.style_code);
// Evaluating script code as adding it to dom did not executed it (only script from update !)
eval(this.buildData.script_code);
@@ -291,7 +309,7 @@ $(function()
var oField = this.options.fields_list[i];
if(oField.id === undefined)
{
- console.log('Field set : An field must have at least an id property.');
+ console.log('Field set : A field must have at least an id property.');
return false;
}
diff --git a/js/forms-json-utils.js b/js/forms-json-utils.js
index d87f4ae9b..e6a549715 100644
--- a/js/forms-json-utils.js
+++ b/js/forms-json-utils.js
@@ -1,3 +1,20 @@
+// Copyright (C) 2010-2016 Combodo SARL
+//
+// This file is part of iTop.
+//
+// iTop is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// iTop is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with iTop. If not, see
+
// ID of the (hidden) form field used to store the JSON representation of the
// object being edited in this page
var sJsonFieldId = 'json_object';
@@ -417,6 +434,15 @@ function ValidateRedundancySettings(sFieldId, sFormId)
return bValid;
}
+//Special validation function for custom fields
+function ValidateCustomFields(sFieldId, sFormId)
+{
+ var oFieldSet = $('#'+sFieldId+'_console_form').console_form_handler('option', 'field_set');
+ bValid = oFieldSet.triggerHandler('validate');
+ ReportFieldValidationStatus(sFieldId, sFormId, bValid, '');
+ return bValid;
+}
+
// Manage a 'duration' field
function UpdateDuration(iId)
{
diff --git a/js/wizardhelper.js b/js/wizardhelper.js
index 8dae04f13..2b3b1f1a8 100644
--- a/js/wizardhelper.js
+++ b/js/wizardhelper.js
@@ -1,3 +1,20 @@
+// Copyright (C) 2010-2016 Combodo SARL
+//
+// This file is part of iTop.
+//
+// iTop is free software; you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// iTop is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with iTop. If not, see
+
// Wizard Helper JavaScript class to communicate with the WizardHelper PHP class
if (!Array.prototype.indexOf) // Emulation of the indexOf function for IE and old browsers
@@ -151,6 +168,7 @@ function WizardHelper(sClass, sFormPrefix, sState)
{ operation: 'wizard_helper', json_obj: this.ToJSON() },
function(html){
$('#ajax_content').html(html);
+ $('.blockUI').parent().unblock();
//console.log('data received:', oWizardHelper);
//oWizardHelper.FromJSON(json_data);
//oWizardHelper.UpdateFields(); // Is done directly in the html provided by ajax.render.php
@@ -191,7 +209,8 @@ function WizardHelper(sClass, sFormPrefix, sState)
{
sAttCode = aFieldNames[index];
sFieldId = this.GetFieldId(sAttCode);
- $('#v_'+sFieldId).html('');
+ $('#fstatus_'+sFieldId).html('');
+ $('#field_'+sFieldId).closest('td').block({message:''});
this.RequestAllowedValues(sAttCode);
index++;
}
diff --git a/pages/ajax.render.php b/pages/ajax.render.php
index b582cd26a..5495d868d 100644
--- a/pages/ajax.render.php
+++ b/pages/ajax.render.php
@@ -1,5 +1,5 @@
GetFieldsForDefaultValue() as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
- $defaultValue = $oAttDef->GetDefaultValue();
+ $defaultValue = $oAttDef->GetDefaultValue($oObj);
$oWizardHelper->SetDefaultValue($sAttCode, $defaultValue);
$oObj->Set($sAttCode, $defaultValue);
}
@@ -2448,6 +2448,28 @@ EOF
}
}
break;
+
+ case 'custom_fields_update':
+ $oPage->SetContentType('application/json');
+ $sAttCode = utils::ReadParam('attcode', '');
+ $aRequestedFields = utils::ReadParam('requested_fields', array());
+ $sRequestedFieldsFormPath = utils::ReadParam('form_path', '');
+ $sJson = utils::ReadParam('json_obj', '', false, 'raw_data');
+
+ $oWizardHelper = WizardHelper::FromJSON($sJson);
+ $oObj = $oWizardHelper->GetTargetObject();
+
+ $oOrmCustomFieldValue = $oObj->Get($sAttCode);
+ $oForm = $oOrmCustomFieldValue->GetForm();
+ $oSubForm = $oForm->FindSubForm($sRequestedFieldsFormPath);
+ $oRenderer = new \Combodo\iTop\Renderer\Console\ConsoleFormRenderer($oSubForm);
+ $aRenderRes = $oRenderer->Render($aRequestedFields);
+
+ $aResult = array();
+ $aResult['form']['updated_fields'] = $aRenderRes;
+ $oPage->add(json_encode($aResult));
+ break;
+
default:
$oPage->p("Invalid query.");
}
diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php
index 460c58d47..543bfae4c 100644
--- a/setup/compiler.class.inc.php
+++ b/setup/compiler.class.inc.php
@@ -1206,6 +1206,10 @@ EOF;
$aParameters['min_up_mode'] = $this->GetMandatoryPropString($oField, 'min_up_mode');
$aParameters['min_up_type'] = $this->GetMandatoryPropString($oField, 'min_up_type');
}
+ elseif ($sAttType == 'AttributeCustomFields')
+ {
+ $aParameters['handler_class'] = $this->GetMandatoryPropString($oField, 'handler_class');
+ }
else
{
$aParameters['allowed_values'] = 'null'; // or "new ValueSetEnum('SELECT xxxx')"
diff --git a/sources/form/field/subformfield.class.inc.php b/sources/form/field/subformfield.class.inc.php
index 9f313fd3c..1048a13e7 100644
--- a/sources/form/field/subformfield.class.inc.php
+++ b/sources/form/field/subformfield.class.inc.php
@@ -61,7 +61,7 @@ class SubFormField extends Field
*/
public function Validate()
{
- $this->oForm->Validate();
+ return $this->oForm->Validate();
}
/**
@@ -106,4 +106,13 @@ class SubFormField extends Field
$this->oForm->SetCurrentValues($value);
return $this;
}
+
+ /**
+ * @param $sFormPath
+ * @return Form|null
+ */
+ public function FindSubForm($sFormPath)
+ {
+ return $this->oForm->FindSubForm($sFormPath);
+ }
}
diff --git a/sources/form/form.class.inc.php b/sources/form/form.class.inc.php
index 0b1d88a65..2490eb49d 100644
--- a/sources/form/form.class.inc.php
+++ b/sources/form/form.class.inc.php
@@ -19,6 +19,7 @@
namespace Combodo\iTop\Form;
+use Combodo\iTop\Form\Field\SubFormField;
use \Exception;
use \Dict;
use \Combodo\iTop\Form\Field\Field;
@@ -312,83 +313,108 @@ class Form
* @param string $sDependsOnId
* @return \Combodo\iTop\Form\Form
*/
- public function AddFieldDependency($sFieldId, $sDependsOnId)
- {
- if (!array_key_exists($sFieldId, $this->aDependencies))
- {
- $this->aDependencies[$sFieldId] = array();
- }
- $this->aDependencies[$sFieldId][] = $sDependsOnId;
- return $this;
- }
+ public function AddFieldDependency($sFieldId, $sDependsOnId)
+ {
+ if (!array_key_exists($sFieldId, $this->aDependencies))
+ {
+ $this->aDependencies[$sFieldId] = array();
+ }
+ $this->aDependencies[$sFieldId][] = $sDependsOnId;
+ return $this;
+ }
+
+ /**
+ * Returns a hash array of the fields impacts on other fields. Key being the field that impacts the fields stored in the value as a regular array
+ * (It kind of reversed the dependencies array)
+ *
+ * eg :
+ * - 'service' => array('subservice', 'template')
+ * - 'subservice' => array()
+ * - ...
+ *
+ * @return array
+ */
+ public function GetFieldsImpacts()
+ {
+ $aRes = array();
+
+ foreach ($this->aDependencies as $sImpactedFieldId => $aDependentFieldsIds)
+ {
+ foreach ($aDependentFieldsIds as $sDependentFieldId)
+ {
+ if (!array_key_exists($sDependentFieldId, $aRes))
+ {
+ $aRes[$sDependentFieldId] = array();
+ }
+ $aRes[$sDependentFieldId][] = $sImpactedFieldId;
+ }
+ }
+
+ return $aRes;
+ }
/**
- * Returns a hash array of the fields impacts on other fields. Key being the field that impacts the fields stored in the value as a regular array
- * (It kind of reversed the dependencies array)
- *
- * eg :
- * - 'service' => array('subservice', 'template')
- * - 'subservice' => array()
- * - ...
- *
- * @return array
+ * @param $sFormPath
+ * @return Form|null
*/
- public function GetFieldsImpacts()
+ public function FindSubForm($sFormPath)
{
- $aRes = array();
-
- foreach ($this->aDependencies as $sImpactedFieldId => $aDependentFieldsIds)
+ $ret = null;
+ if ($sFormPath == $this->sId)
{
- foreach ($aDependentFieldsIds as $sDependentFieldId)
+ $ret = $this;
+ }
+ else
+ {
+ foreach ($this->aFields as $oField)
{
- if (!array_key_exists($sDependentFieldId, $aRes))
+ if ($oField instanceof SubFormField)
{
- $aRes[$sDependentFieldId] = array();
+ $ret = $oField->FindSubForm($sFormPath);
+ if ($ret !== null) break;
}
- $aRes[$sDependentFieldId][] = $sImpactedFieldId;
}
}
-
- return $aRes;
+ return $ret;
}
/**
*
*/
- public function Finalize()
- {
- //TODO : Call GetOrderedFields
- // Must call OnFinalize on each fields, regarding the dependencies order
- // On a SubFormField, will call its own Finalize
- foreach ($this->aFields as $sId => $oField)
- {
- $oField->OnFinalize();
- }
- }
+ public function Finalize()
+ {
+ //TODO : Call GetOrderedFields
+ // Must call OnFinalize on each fields, regarding the dependencies order
+ // On a SubFormField, will call its own Finalize
+ foreach ($this->aFields as $sId => $oField)
+ {
+ $oField->OnFinalize();
+ }
+ }
/**
* Validate the form and return if it's valid or not
*
* @return boolean
*/
- public function Validate()
- {
- $this->SetValid(true);
- $this->EmptyErrorMessages();
+ public function Validate()
+ {
+ $this->SetValid(true);
+ $this->EmptyErrorMessages();
- foreach ($this->aFields as $oField)
- {
- if (!$oField->Validate())
- {
- $this->SetValid(false);
- foreach ($oField->GetErrorMessages() as $sErrorMessage)
- {
- $this->AddErrorMessage(Dict::S($sErrorMessage), $oField->Getid());
- }
- }
- }
+ foreach ($this->aFields as $oField)
+ {
+ if (!$oField->Validate())
+ {
+ $this->SetValid(false);
+ foreach ($oField->GetErrorMessages() as $sErrorMessage)
+ {
+ $this->AddErrorMessage(Dict::S($sErrorMessage), $oField->Getid());
+ }
+ }
+ }
- return $this->GetValid();
- }
+ return $this->GetValid();
+ }
}
diff --git a/sources/renderer/console/consoleformrenderer.class.inc.php b/sources/renderer/console/consoleformrenderer.class.inc.php
index 79da01cae..66d228a0e 100644
--- a/sources/renderer/console/consoleformrenderer.class.inc.php
+++ b/sources/renderer/console/consoleformrenderer.class.inc.php
@@ -33,7 +33,9 @@ class ConsoleFormRenderer extends FormRenderer
public function __construct(Form $oForm)
{
parent::__construct($oForm);
+ $this->AddSupportedField('HiddenField', 'ConsoleSimpleFieldRenderer');
$this->AddSupportedField('StringField', 'ConsoleSimpleFieldRenderer');
+ $this->AddSupportedField('SelectField', 'ConsoleSimpleFieldRenderer');
$this->AddSupportedField('SubFormField', 'ConsoleSubFormFieldRenderer');
}
}
\ No newline at end of file
diff --git a/sources/renderer/console/fieldrenderer/consolesimplefieldrenderer.class.inc.php b/sources/renderer/console/fieldrenderer/consolesimplefieldrenderer.class.inc.php
index 3dc862aba..536c24830 100644
--- a/sources/renderer/console/fieldrenderer/consolesimplefieldrenderer.class.inc.php
+++ b/sources/renderer/console/fieldrenderer/consolesimplefieldrenderer.class.inc.php
@@ -18,6 +18,7 @@
namespace Combodo\iTop\Renderer\Console\FieldRenderer;
+use Combodo\iTop\Form\Field\StringField;
use \Dict;
use Combodo\iTop\Renderer\FieldRenderer;
use Combodo\iTop\Renderer\RenderingOutput;
@@ -29,35 +30,60 @@ class ConsoleSimpleFieldRenderer extends FieldRenderer
$oOutput = new RenderingOutput();
$sFieldClass = get_class($this->oField);
- // TODO : Shouldn't we have a field type so we don't have to maintain FQN classname ?
- // Rendering field in edition mode
- if (!$this->oField->GetReadOnly())
+ if ($sFieldClass == 'Combodo\\iTop\\Form\\Field\\HiddenField')
{
- switch ($sFieldClass)
- {
- case 'Combodo\\iTop\\Form\\Field\\StringField':
- if ($this->oField->GetLabel() !== '')
- {
- $oOutput->AddHtml('');
- }
- $oOutput->AddHtml('');
- $oOutput->AddHtml('');
- break;
- }
+ $oOutput->AddHtml('');
}
- // ... and in read-only mode
else
{
+ $oOutput->AddHtml('
');
+ $oOutput->AddHtml('
');
+ if ($this->oField->GetLabel() != '')
+ {
+ $oOutput->AddHtml('
');
+ }
switch ($sFieldClass)
{
case 'Combodo\\iTop\\Form\\Field\\StringField':
- if ($this->oField->GetLabel() !== '')
+ $oOutput->AddHtml('