瀏覽代碼

Forms enhancements and XML format touch-up.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3359 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 年之前
父節點
當前提交
aab3215968
共有 5 個文件被更改,包括 340 次插入170 次删除
  1. 187 134
      application/forms.class.inc.php
  2. 59 2
      core/attributedef.class.inc.php
  3. 40 2
      js/property_field.js
  4. 2 2
      setup/compiler.class.inc.php
  5. 52 30
      setup/modelfactory.class.inc.php

+ 187 - 134
application/forms.class.inc.php

@@ -38,7 +38,10 @@ class DesignerForm
 	protected $bReadOnly;
 	protected $sHierarchyPath;   // Needed to manage the visibility of nested subform
 	protected $sHierarchyParent; // Needed to manage the visibility of nested subform
+	protected $sHierarchySelector; // Needed to manage the visibility of nested subform
 	protected $bDisplayed;
+	protected $aDefaultValues;
+	protected $sFieldsSuffix;
 	
 	public function __construct()
 	{
@@ -47,14 +50,17 @@ class DesignerForm
 		$this->sScript = '';
 		$this->sReadyScript = '';
 		$this->sFormPrefix = '';
+		$this->sFieldsSuffix = '';
 		$this->sParamsContainer = '';
 		$this->sFormId = 'form_'.rand();
 		$this->oParentForm = null;
 		$this->bReadOnly = false;
 		$this->sHierarchyPath = '';
 		$this->sHierarchyParent = '';
+		$this->sHierarchySelector = '';
 		$this->StartFieldSet($this->sCurrentFieldSet);
 		$this->bDisplayed = true;
+		$this->aDefaultvalues = array();
 	}
 	
 	public function AddField(DesignerFormField $oField)
@@ -140,6 +146,11 @@ class DesignerForm
 			$oP->add($sReturn);
 		}
 	}
+	
+	public function GetFieldSets()
+	{
+		return $this->aFieldSets;
+	}
 
 	public function SetSubmitParams($sSubmitToUrl, $aSubmitParams)
 	{
@@ -153,6 +164,11 @@ class DesignerForm
 		$this->aSubmitParams = $oParentForm->aSubmitParams;
 	}
 	
+	public function GetSubmitParams()
+	{
+		return array( 'url' => $this->sSubmitTo, 'params' => $this->aSubmitParams);
+	}
+	
 	/**
 	 * Helper to handle subforms hide/show	
 	 */	
@@ -168,7 +184,7 @@ class DesignerForm
 	{
 		return $this->sHierarchyPath;
 	}
-	
+		
 	/**
 	 * Helper to handle subforms hide/show	
 	 */	
@@ -208,7 +224,7 @@ class DesignerForm
 			$aDetails = array();
 			if ($sLabel != '')
 			{
-				$sReturn .= '<tr><th colspan="4">'.$sLabel.'</th></tr>';
+				$sReturn .= $this->StartRow().'<th colspan="4">'.$sLabel.'</th>'.$this->EndRow();
 			}
 
 
@@ -219,16 +235,19 @@ class DesignerForm
 				{
 					$sFieldId = $this->GetFieldId($oField->GetCode());
 					$sValidation = $this->GetValidationArea($oField->GetCode(), '<span title="Apply" class="ui-icon ui-icon-circle-check"/>');
-					$sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td  class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td></tr>';
+					$sValidationFields = '</td><td class="prop_icon prop_apply">'.$sValidation.'</td><td  class="prop_icon prop_cancel"><span title="Revert" class="ui-icon ui-icon-circle-close"/></td>'.$this->EndRow();
+					
+					$sPath = $this->GetHierarchyPath().'/'.$oField->GetCode();
+					
 					if (is_null($aRow['label']))
 					{
-						$sReturn .= '<tr id="row_'.$sFieldId.'"><td class="prop_value" colspan="2">'.$aRow['value'];
+						$sReturn .= $this->StartRow($sFieldId).'<td class="prop_value" colspan="2">'.$aRow['value'];
 					}
 					else
 					{
-						$sReturn .= '<tr id="row_'.$sFieldId.'"><td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
+						$sReturn .= $this->StartRow($sFieldId).'<td class="prop_label">'.$aRow['label'].'</td><td class="prop_value">'.$aRow['value'];
 					}
-					if (!($oField instanceof DesignerFormSelectorField))
+					if (!($oField instanceof DesignerFormSelectorField) && !($oField instanceof DesignerMultipleSubFormField))
 					{
 						$sReturn .= $sValidationFields;
 					}
@@ -296,7 +315,22 @@ EOF
 		{
 			$oP->add($sReturn);
 		}
-	}	
+	}
+	
+	public function StartRow($sFieldId = null)
+	{
+		if ($sFieldId != null)
+		{
+			return '<tr id="row_'.$sFieldId.'" data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
+		}
+		return '<tr data-path="'.$this->GetHierarchyPath().'" data-selector="'.$this->GetHierarchyParent().'">';
+	}
+	
+	public function EndRow()
+	{
+		return '</tr>';
+	}
+	
 	public function RenderAsDialog($oPage, $sDialogId, $sDialogTitle, $iDialogWidth, $sOkButtonLabel, $sIntroduction = null)
 	{
 		$sDialogTitle = addslashes($sDialogTitle);
@@ -353,9 +387,29 @@ EOF
 	
 	public function GetPrefix()
 	{
-		return $this->sFormPrefix;
+		$sPrefix = '';	
+		if ($this->oParentForm != null)
+		{
+			$sPrefix = $this->oParentForm->GetPrefix();
+		}
+		return $sPrefix.$this->sFormPrefix;
+	}
+
+	public function SetSuffix($sSuffix)
+	{
+		$this->sFieldsSuffix = $sSuffix;
 	}
 	
+	public function GetSuffix()
+	{
+		$sSuffix = '';
+		if ($this->oParentForm != null)
+		{
+			$sSuffix = $this->oParentForm->GetSuffix();
+		}
+		return $sSuffix.$this->sFieldsSuffix;
+	}
+		
 	public function SetReadOnly($bReadOnly = true)
 	{
 		$this->bReadOnly = $bReadOnly;
@@ -395,6 +449,25 @@ EOF
 		$this->oParentForm = $oParentForm;
 	}
 	
+	public function SetDefaultValues($aDefaultValues)
+	{
+		if (!is_array($aDefaultValues)) return;
+		
+		foreach($this->aFieldSets as $sLabel => $aFields)
+		{
+			foreach($aFields as $oField)
+			{
+				$oField->SetDefaultValueFrom($aDefaultValues);
+			}
+		}
+	}
+	
+	public function GetDefaultValues()
+	{
+		return $this->aDefaultValues;
+	}
+	
+	
 	public function GetParentForm()
 	{
 		return $this->oParentForm;
@@ -429,17 +502,17 @@ EOF
 	
 	public function GetFieldId($sCode)
 	{
-		return $this->sFormPrefix.'attr_'.$sCode;
+		return $this->GetPrefix().'attr_'.$sCode;
 	}
 	
 	public function GetFieldName($sCode)
 	{
-		return 'attr_'.$sCode;
+		return 'attr_'.$sCode.$this->GetSuffix();
 	}
 	
 	public function GetParamName($sCode)
 	{
-		return 'attr_'.$sCode;
+		return 'attr_'.$sCode.$this->GetSuffix();
 	}
 	
 	public function GetValidationArea($sCode, $sContent = '')
@@ -450,6 +523,21 @@ EOF
 	{
 		return $this->sAsyncActionClass;
 	}
+	
+	public function FindField($sFieldCode)
+	{
+		$oFoundField = false;
+		foreach($this->aFieldSets as $sLabel => $aFields)
+		{
+			foreach($aFields as $oField)
+			{
+				$oFoundField = $oField->FindField($sFieldCode);
+				if ($oFoundField !== false) break;
+			}
+			if ($oFoundField !== false) break;
+		}
+		return $oFoundField;		
+	}
 }
 
 class DesignerTabularForm extends DesignerForm
@@ -629,6 +717,11 @@ class DesignerFormField
 		return $this->bDisplayed;
 	}
 
+	public function GetFieldId()
+	{
+		return $this->oForm->GetFieldId($this->sCode);
+	}
+	
 	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
 	{
 		$sId = $this->oForm->GetFieldId($this->sCode);
@@ -672,6 +765,26 @@ class DesignerFormField
 	{
 		$this->aCSSClasses[] = $sCSSClass;
 	}
+	
+	/**
+	 * A way to set/change the default value after constructing the field
+	 */
+	public function SetDefaultValueFrom($aAllDefaultValue)
+	{
+		if (array_key_exists($this->GetCode(), $aAllDefaultValue))
+		{
+			$this->defaultValue = $aAllDefaultValue[$this->GetCode()];
+		}
+	}
+	
+	public function FindField($sFieldCode)
+	{
+		if ($this->sCode == $sFieldCode)
+		{
+			return $this;
+		}
+		return false;
+	}
 
 	public function GetHandlerEquals()
 	{
@@ -747,6 +860,7 @@ class DesignerTextField extends DesignerFormField
 	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
 	{
 		$sId = $this->oForm->GetFieldId($this->sCode);
+		
 		$sName = $this->oForm->GetFieldName($this->sCode);
 		if ($this->IsReadOnly())
 		{
@@ -872,7 +986,6 @@ class DesignerComboField extends DesignerFormField
 	
 	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
 	{
-
 		$sId = $this->oForm->GetFieldId($this->sCode);
 		$sName = $this->oForm->GetFieldName($this->sCode);
 		$sChecked = $this->defaultValue ? 'checked' : '';
@@ -1215,6 +1328,8 @@ class DesignerFormSelectorField extends DesignerFormField
 		$sName = $this->oForm->GetFieldName($this->sCode);
 		$sReadOnly = $this->IsReadOnly() ? 'disabled="disabled"' :  '';
 		
+		$this->aCSSClasses[] = 'formSelector';
+		
 		$sCSSClasses = '';
 		if (count($this->aCSSClasses) > 0)
 		{
@@ -1228,9 +1343,9 @@ class DesignerFormSelectorField extends DesignerFormField
 			$aHiddenValues = array();
 			$sDisplayValue = '';
 			$sHiddenValue = '';
-			foreach($this->aSubForms as $sKey => $aFormData)
+			foreach($this->aSubForms as $iKey => $aFormData)
 			{
-				if ($sKey == $this->defaultValue)
+				if ($iKey == $this->defaultValue) // Default value is actually the index
 				{
 					$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');
 					$sHiddenValue = "<input type=\"hidden\" id=\"$sId\" name=\"$sName\" value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\"/>";
@@ -1244,11 +1359,11 @@ class DesignerFormSelectorField extends DesignerFormField
 			
 			
 			$sHtml = "<select $sCSSClasses id=\"$sId\" name=\"$sName\" $sReadOnly>";
-			foreach($this->aSubForms as $sKey => $aFormData)
+			foreach($this->aSubForms as $iKey => $aFormData)
 			{
 				$sDisplayValue = htmlentities($aFormData['label'], ENT_QUOTES, 'UTF-8');;
-				$sSelected = ($sKey == $this->defaultValue) ? 'selected' : '';
-				$sHtml .= "<option value=\"".htmlentities($sKey, ENT_QUOTES, 'UTF-8')."\" $sSelected>".$sDisplayValue."</option>";
+				$sSelected = ($iKey == $this->defaultValue) ? 'selected' : '';
+				$sHtml .= "<option value=\"$iKey\" $sSelected>".$sDisplayValue."</option>";
 			}
 			$sHtml .= "</select>";
 		}
@@ -1277,14 +1392,14 @@ class DesignerFormSelectorField extends DesignerFormField
 				// - data-path : uniquely identifies the combination of users choices that must be made to show the node
 				// - data-state : records the state, depending on the user choice on the FormSelectorField just above the node, but indepentantly from the visibility in the page (can be visible in the form itself being in a hidden form)
 				// Then a series of actions are performed to hide and show the relevant nodes, depending on the user choice
-				$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode;
+				$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
 				$oSubForm->SetHierarchyParent($sSelector);
-				$sPath = $this->oForm->GetHierarchyPath().'/'.$this->sCode.':'.$sKey;
+				$sPath = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix().'-'.$sKey;
 				$oSubForm->SetHierarchyPath($sPath);
 
 				$oSubForm->SetDisplayed($sKey == $this->defaultValue);
 				$sState = ($sKey == $this->defaultValue) ? 'visible' : 'hidden';
-				$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
+				//$sHtml .= "</tbody><tbody data-selector=\"$sSelector\" data-path=\"$sPath\" data-state=\"$sState\" $sStyle>";
 				$sHtml .= $oSubForm->RenderAsPropertySheet($oP, true);
 
 				$sState = $this->oForm->IsDisplayed() ? 'visible' : 'hidden';
@@ -1301,7 +1416,7 @@ class DesignerFormSelectorField extends DesignerFormField
 					$sParentPath = '';
 				}
 				
-				$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sParentStyle>";
+				//$sHtml .= "</tbody><tbody data-selector=\"$sParentSelector\" data-path=\"$sParentPath\" data-state=\"$sState\" $sParentStyle>";
 			}
 			else
 			{
@@ -1313,27 +1428,8 @@ class DesignerFormSelectorField extends DesignerFormField
 
 		if ($sRenderMode == 'property')
 		{
-			$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode;
-			$oP->add_ready_script(
-<<<EOF
-$('#$sId').bind('change reverted', function() {
-	// Mark all the direct children as hidden
-	$('tbody[data-selector="$sSelector"]').attr('data-state', 'hidden');
-	// Mark the selected one as visible
-	var sSelectedHierarchy = '$sSelector:'+this.value; 
-	$('tbody[data-path="'+sSelectedHierarchy+'"]').attr('data-state', 'visible');
-	
-	// Show all items behind the current one
-	$('tbody[data-path^="$sSelector"]').show();
-	// Hide items behind the current one as soon as it is behind a hidden node (or itself is marked as hidden) 
-	$('tbody[data-path^="$sSelector"][data-state="hidden"]').each(function(){
-		$(this).hide();
-		var sPath = $(this).attr('data-path');
-		$('tbody[data-path^="'+sPath+'/"]').hide();
-	});
-});
-EOF
-			);
+			$sSelector = $this->oForm->GetHierarchyPath().'/'.$this->sCode.$this->oForm->GetSuffix();
+			$oP->add_ready_script("InitFormSelectorField('$sId', '$sSelector');");
 		}
 		else
 		{
@@ -1356,6 +1452,44 @@ EOF
 		$this->aSubForms[$sKey]['form']->SetParentForm($this->oForm);
 		$this->aSubForms[$sKey]['form']->ReadParams($aValues);
 	}
+	
+	public function SetDefaultValueFrom($aAllDefaultValues)
+	{
+		if (array_key_exists($this->GetCode(), $aAllDefaultValues))
+		{
+			$selectedValue = $aAllDefaultValues[$this->GetCode()];
+			foreach($this->aSubForms as $iKey => $aFormData)
+			{
+				$sId = $this->oForm->GetFieldId($this->sCode);
+				if ($selectedValue == $aFormData['value'])
+				{
+					$this->defaultValue =$iKey;
+					$aDefaultValues = $this->oForm->GetDefaultValues();
+					$oSubForm = $aFormData['form'];
+					$oSubForm->SetDefaultValues($aAllDefaultValues);
+				}
+			}		
+		}
+	}
+	
+	public function FindField($sFieldCode)
+	{
+		$oField = parent::FindField($sFieldCode);
+		if ($oField === false)
+		{
+			// Look in the subforms
+			foreach($this->aSubForms as $sKey => $aFormData)
+			{
+				$oSubForm = $aFormData['form'];
+				$oField = $oSubForm->FindField($sFieldCode);
+				if ($oField !== false)
+				{
+					break;
+				}
+			}
+		}
+		return $oField;
+	}
 }
 
 class DesignerSubFormField extends DesignerFormField
@@ -1388,112 +1522,31 @@ class DesignerSubFormField extends DesignerFormField
 		$this->oSubForm->SetParentForm($this->oForm);
 		$this->oSubForm->ReadParams($aValues);
 	}
-}
-
-class DesignerStaticTextField extends DesignerFormField
-{
-	public function __construct($sCode, $sLabel = '', $defaultValue = '')
-	{
-		parent::__construct($sCode, $sLabel, $defaultValue);
-	}
-
-	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
-	{
-		return array('label' => $this->sLabel, 'value' => $this->defaultValue);
-	}
-}
-
-class DesignerButtonsField extends DesignerFormField
-{
-	protected $aButtons;
 	
-	public function __construct($sCode, $sLabel = null)
+	public function FindField($sFieldCode)
 	{
-		parent::__construct($sCode, $sLabel, null);
-		$this->aButtons = array('left' => array(), 'center' => array(), 'right' => array());
-	}
-
-	public function AddButton($sPos, DesignerButtonsFieldCtrl $oButton)
-	{
-		$this->aButtons[$sPos][] = $oButton;
-	}
-	
-	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
-	{
-		$sHtml = '<table style="width:100%"><tbody><tr>';
-		foreach($this->aButtons as $sPos => $aButtons)
+		$oField = parent::FindField($sFieldCode);
+		if ($oField === false)
 		{
-			$aBtns = array();
-			foreach($aButtons as $oButton)
-			{
-				$aBtns[] = $oButton->Render($oP, $sFormId, $sRenderMode);
-			}
-			$sHtml .= '<td style="text-align:'.$sPos.'; ">'.implode(' ', $aBtns)."</td>";
+			// Look in the subform
+			$oField = $this->oSubForm->FindField($sFieldCode);
 		}
-		$sHtml .= '</tr></tbody></table>';
-		return array('label' => $this->sLabel, 'value' => $sHtml);
+		return $oField;
 	}
 }
 
-/**
- * A button to add to a DesignerButtonsField
- *
- */
-abstract class DesignerButtonsFieldCtrl
-{
-	protected $sId;
-	public function __construct($sId)
-	{
-		$this->sId = $sId;
-	}
-	abstract public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog');
-}
 
-class DesignerButtonCtrl extends DesignerButtonsFieldCtrl
+
+class DesignerStaticTextField extends DesignerFormField
 {
-	protected $sLabel;
-	protected $sJSAction;
-	
-	public function __construct($sId, $sLabel, $sJSAction)
+	public function __construct($sCode, $sLabel = '', $defaultValue = '')
 	{
-		parent::__construct($sId);
-		$this->sLabel = $sLabel;
-		$this->sJSAction = $sJSAction;
+		parent::__construct($sCode, $sLabel, $defaultValue);
 	}
 
 	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
 	{
-		$sAction = htmlentities($this->sJSAction, ENT_QUOTES, 'UTF-8');
-		return '<button id="'.$this->sId.'" type="button" onclick="'.$sAction.'">'.$this->sLabel.'</button>';
+		return array('label' => $this->sLabel, 'value' => $this->defaultValue);
 	}
 }
 
-class DesignerSelectCtrl extends DesignerButtonsFieldCtrl
-{
-	protected $aValues;
-	protected $defaultValue;
-	protected $sJSAction;
-	
-	public function __construct($sId, $sLabel, $aValues, $defaultValue, $sJSAction)
-	{
-		parent::__construct($sId);
-		$this->sLabel = $sLabel;
-		$this->aValues = $aValues;
-		$this->defaultValue = $defaultValue;
-		$this->sJSAction = $sJSAction;
-		
-	}
-	
-	public function Render(WebPage $oP, $sFormId, $sRenderMode='dialog')
-	{
-		$sAction = htmlentities($this->sJSAction, ENT_QUOTES, 'UTF-8');
-		$sHtml = '<span>'.$this->sLabel.'&nbsp;<select id="'.$this->sId.'" onchange="'.$sAction.'">';
-		foreach($this->aValues as $value => $sLabel)
-		{
-			$sSelected = ($value == $this->defaultValue) ?  'selected' : '';
-			$sHtml .= '<option value="'.htmlentities($value).'"'.$sSelected.'>'.$sLabel.'</option>';
-		}
-		$sHtml .= '</select></span>';
-		return $sHtml;
-	}	
-}

+ 59 - 2
core/attributedef.class.inc.php

@@ -3426,12 +3426,69 @@ class AttributeBlob extends AttributeDefinition
 	}
 
 
-	// Facilitate things: allow the user to Set the value from a string
+	// Facilitate things: allow administrators to upload a document
+	// from a CSV by specifying its path/URL
 	public function MakeRealValue($proposedValue, $oHostObj)
 	{
 		if (!is_object($proposedValue))
 		{
-			return new ormDocument($proposedValue, 'text/plain');
+			if (file_exists($proposedValue) && UserRights::IsAdministrator())
+			{
+				$sContent = file_get_contents($proposedValue);
+				$sExtension = strtolower(pathinfo($proposedValue, PATHINFO_EXTENSION));
+				$sMimeType = "application/x-octoet-stream";
+				$aKnownExtensions = array(
+						'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+						'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+						'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+						'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+						'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+						'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+						'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+						'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+						'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+						'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12.xlsx',
+						'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+						'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+						'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+						'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+						'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+						'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+						'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+						'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+						'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+						'jpg' => 'image/jpeg',
+						'jpeg' => 'image/jpeg',
+						'gif' => 'image/gif',
+						'png' => 'image/png',
+						'pdf' => 'application/pdf',
+						'doc' => 'application/msword',
+						'dot' => 'application/msword',
+						'xls' => 'application/vnd.ms-excel',
+						'ppt' => 'application/vnd.ms-powerpoint',
+						'vsd' => 'application/x-visio',
+						'vdx' => 'application/visio.drawing', 
+						'odt' => 'application/vnd.oasis.opendocument.text',
+						'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+						'odp' => 'application/vnd.oasis.opendocument.presentation',
+						'zip' => 'application/zip',
+						'txt' => 'text/plain',
+						'htm' => 'text/html',
+						'html' => 'text/html', 
+						'exe' => 'application/octet-stream'
+					);
+
+				if (!array_key_exists($sExtension, $aKnownExtensions) && extension_loaded('fileinfo'))
+				{
+					$finfo = new finfo(FILEINFO_MIME);
+					$sMimeType = $finfo->file($proposedValue);
+				}
+				return new ormDocument($sContent, $sMimeType);			
+			}
+			else
+			{
+				return new ormDocument($proposedValue, 'text/plain');
+			}
 		}
 		return $proposedValue;
 	}

+ 40 - 2
js/property_field.js

@@ -225,7 +225,7 @@ $(function()
 		},
 		_is_visible: function()
 		{
-			return this.element.parent().is(':visible');
+			return this.element.is(':visible');
 		}
 	});
 });
@@ -326,7 +326,6 @@ function ReadFormParams(sFormId)
 				{
 					oMap[sName] = $(this).val();
 				}
-				
 			}
 		}
 	});
@@ -369,3 +368,42 @@ function SubmitForm(sFormId, onSubmitResult)
 		alert('Please fill all the fields before continuing...');
 	}
 }
+
+function RemoveSubForm(sId, sUrl, oParams)
+{
+	$.post(sUrl, oParams, function(data) {
+		$('body').append(data);
+	});
+}
+
+function AddSubForm(sId, sUrl, oParams)
+{
+	var aIndexes = JSON.parse($('#'+sId).val());
+	var iLast = aIndexes[aIndexes.length - 1];
+	var iNewIdx = 1 + iLast;
+	oParams.new_index = iNewIdx;
+	
+	$.post(sUrl, oParams, function(data) {
+		$('body').append(data);
+	});
+}
+
+function InitFormSelectorField(sId, sSelector)
+{
+	$('#'+sId).bind('change reverted init', function() {
+		// Mark all the direct children as hidden
+		$('tr[data-selector="'+sSelector+'"]').attr('data-state', 'hidden');
+		// Mark the selected one as visible
+		var sSelectedHierarchy = sSelector+'-'+this.value; 
+		$('tr[data-path="'+sSelectedHierarchy+'"]').attr('data-state', 'visible');
+					
+		// Show all items behind the current one
+		$('tr[data-path^="'+sSelector+'"]').show();
+		// Hide items behind the current one as soon as it is behind a hidden node (or itself is marked as hidden) 
+		$('tr[data-path^="'+sSelector+'"][data-state="hidden"]').each(function() {
+			$(this).hide();
+			var sPath = $(this).attr('data-path');
+			$('tr[data-path^="'+sPath+'/"]').hide();
+		});			
+	}).trigger('init'); // initial refresh
+}

+ 2 - 2
setup/compiler.class.inc.php

@@ -920,7 +920,7 @@ EOF;
 					$aThresholds = array();
 					foreach($oThresholdNodes as $oThreshold)
 					{
-						$iPercent = $this->GetPropNumber($oThreshold, 'percent');
+						$iPercent = (int)$oThreshold->getAttribute('id');
 	
 						$oHighlight = $oThreshold->GetUniqueElement('highlight', false);
 						$sHighlight = '';
@@ -1130,7 +1130,7 @@ EOF;
 				$oTransitions = $oState->GetUniqueElement('transitions');
 				foreach ($oTransitions->getElementsByTagName('transition') as $oTransition)
 				{
-					$sStimulus = $oTransition->GetChildText('stimulus');
+					$sStimulus = $oTransition->getAttribute('id');
 					$sTargetState = $oTransition->GetChildText('target');
 	
 					$oActions = $oTransition->GetUniqueElement('actions');

+ 52 - 30
setup/modelfactory.class.inc.php

@@ -431,38 +431,9 @@ class ModelFactory
 						$oUserRightsNode->SetAttribute('_created_in', $sModuleName);
 					}
 				}
-				// Adjust the XML to transparently add an id (=stimulus) on all life-cycle transitions
-				// which don't already have one
-				$oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition/stimulus');	
-				foreach($oNodeList as $oNode)
-				{
-					if ($oNode->parentNode->getAttribute('id') == '')
-					{
-						$oNode->parentNode->SetAttribute('id', $oNode->textContent);
-					}
-				}
 				
-				// Adjust the XML to transparently add an id (=type:<type>) on all allowed actions (profiles)
-				// which don't already have one
-				$oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action');	
-				foreach($oNodeList as $oNode)
-				{
-					if ($oNode->getAttribute('id') == '')
-					{
-						$oNode->SetAttribute('id', 'type:'.$oNode->getAttribute('xsi:type'));
-					}
-				}
+				self::UpgradeDocument($oDocument);
 				
-				// Adjust the XML to transparently add an id (=value) on all values of an enum which don't already have one.
-				// This enables altering an enum for just adding/removing one value, intead of redefining the whole list of values.
-				$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value");
-				foreach($oNodeList as $oNode)
-				{
-					if ($oNode->getAttribute('id') == '')
-					{
-						$oNode->SetAttribute('id', $oNode->textContent);
-					}
-				}				
 				$oDeltaRoot = $oDocument->childNodes->item(0);
 				$this->LoadDelta($oDeltaRoot, $this->oDOMDocument);
 			}
@@ -542,6 +513,57 @@ class ModelFactory
 			throw new Exception('Error loading module "'.$oModule->GetName().'": '.$e->getMessage().' - Loaded modules: '.implode(',', $aLoadedModuleNames));
 		}
 	}
+	
+	/**
+	 * Make adjustements to the DOM to migrate it to the latest version
+	 * @param DOMDocument $oDocument
+	 */
+	public static function UpgradeDocument(DOMDocument $oDocument)
+	{
+		// Adjust the XML to transparently add an id (=stimulus) on all life-cycle transitions
+		// which don't already have one
+		$oXPath = new DOMXPath($oDocument);
+		$oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition/stimulus');
+		foreach ($oNodeList as $oNode)
+		{
+			if ($oNode->parentNode->getAttribute('id') == '')
+			{
+				$oNode->parentNode->SetAttribute('id', $oNode->textContent);
+				$oNode->parentNode->removeChild($oNode);
+			}
+		}
+		
+		// Adjust the XML to transparently add an id (=percent) on all thresholds of stopwatches
+		// which don't already have one
+		$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeStopWatch']/thresholds/threshold/percent");
+		foreach ($oNodeList as $oNode)
+		{
+			$oNode->parentNode->SetAttribute('id', $oNode->textContent);
+			$oNode->parentNode->removeChild($oNode);
+		}
+		
+		// Adjust the XML to transparently add an id (=type:<type>) on all allowed actions (profiles)
+		// which don't already have one
+		$oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action');
+		foreach ($oNodeList as $oNode)
+		{
+			if ($oNode->getAttribute('id') == '')
+			{
+				$oNode->SetAttribute('id', 'type:' . $oNode->getAttribute('xsi:type'));
+			}
+		}
+		
+		// Adjust the XML to transparently add an id (=value) on all values of an enum which don't already have one.
+		// This enables altering an enum for just adding/removing one value, intead of redefining the whole list of values.
+		$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value");
+		foreach ($oNodeList as $oNode)
+		{
+			if ($oNode->getAttribute('id') == '')
+			{
+				$oNode->SetAttribute('id', $oNode->textContent);
+			}
+		}
+	}
 
 	/**
 	 * Collects the PHP Dict entries into the ModelFactory for transforming the dictionary into an XML structure