瀏覽代碼

- First implementation of documents
- small cleanup to remove a global Config variable (and removed some deprecated utils:: functions)
- removed the 'DocumentVersion' object since previous versions are now tracked as normal change objects.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@220 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 15 年之前
父節點
當前提交
2158d3223e

+ 35 - 15
application/cmdbabstract.class.inc.php

@@ -225,6 +225,21 @@ abstract class cmdbAbstractObject extends CMDBObject
 			}
 		}
 		$sHtml .= $oPage->GetDetails($aDetails);
+		// Documents displayed inline (when possible: images, html...)
+		foreach($aList as $sAttCode)
+		{
+			$oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode);
+			if ( $oAttDef->GetEditClass() == 'Document')
+			{
+				$oDoc = $this->Get($sAttCode);
+				if (is_object($oDoc) && !$oDoc->IsEmpty())
+				{
+					$sHtml .= "<p>Open in New Window: ".$oDoc->GetDisplayLink($sClass, $this->GetKey(), $sAttCode).", \n";
+					$sHtml .= "Download: ".$oDoc->GetDownloadLink($sClass, $this->GetKey(), $sAttCode)."</p>\n";
+					$sHtml .= "<div>".$oDoc->GetDisplayInline($sClass, $this->GetKey(), $sAttCode)."</div>\n";
+				}
+			}
+		}
 		return $sHtml;		
 	}
 
@@ -269,8 +284,6 @@ abstract class cmdbAbstractObject extends CMDBObject
 	//public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true, $bSelectMode = false)
 	public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
 	{
-		global $g_oConfig;
-
 		static $iListId = 0;
 		$iListId++;
 		
@@ -347,9 +360,9 @@ abstract class cmdbAbstractObject extends CMDBObject
 		$iMaxObjects = -1;
 		if ($bDisplayLimit)
 		{
-			if ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit())
+			if ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit())
 			{
-				$iMaxObjects = $g_oConfig->GetMinDisplayLimit();
+				$iMaxObjects = utils::GetConfig()->GetMinDisplayLimit();
 			}
 		}
 		while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
@@ -379,14 +392,14 @@ abstract class cmdbAbstractObject extends CMDBObject
 				//$aMenuExtraParams['linkage'] = $sLinkageAttribute;
 				$aMenuExtraParams = $aExtraParams;
 			}
-			if ($bDisplayLimit && ($oSet->Count() > $g_oConfig->GetMaxDisplayLimit()))
+			if ($bDisplayLimit && ($oSet->Count() > utils::GetConfig()->GetMaxDisplayLimit()))
 			{
 				// list truncated
 				$divId = $aExtraParams['block_id'];
 				$sFilter = $oSet->GetFilter()->serialize();
 				$aExtraParams['display_limit'] = false; // To expand the full list
 				$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
-				$sHtml .= '<tr class="containerHeader"><td>'.$g_oConfig->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'&nbsp;&nbsp;<a href="Javascript:ReloadTruncatedList(\''.$divId.'\', \''.$sFilter.'\', \''.$sExtraParams.'\');">Display All</a></td><td>';
+				$sHtml .= '<tr class="containerHeader"><td>'.utils::GetConfig()->GetMinDisplayLimit().' object(s) displayed out of '.$oSet->Count().'&nbsp;&nbsp;<a href="Javascript:ReloadTruncatedList(\''.$divId.'\', \''.$sFilter.'\', \''.$sExtraParams.'\');">Display All</a></td><td>';
 				$oPage->add_ready_script("$('#{$divId} table.listResults').addClass('truncated');");
 				$oPage->add_ready_script("$('#{$divId} table.listResults tr:last td').addClass('truncated');");
 			}
@@ -434,14 +447,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 			$aRow[] = $oObj->GetKey();
 			foreach($aList as $sAttCode)
 			{
-				if (strstr($oObj->Get($sAttCode), $sSeparator)) // Escape the text only when it contains the separator
-				{
-					$aRow[] = $sTextQualifier.$oObj->Get($sAttCode).$sTextQualifier;
-				}
-				else
-				{
-					$aRow[] = $oObj->Get($sAttCode);
-				}
+				$aRow[] = $oObj->GetAsCSV($sAttCode, $sSeparator, '\\');
 			}
 			$sHtml .= implode($sSeparator, $aRow)."\n";
 		}
@@ -674,6 +680,20 @@ abstract class cmdbAbstractObject extends CMDBObject
 					$sHTMLValue = $oWidget->Display($oPage, $value);
 				break;
 							
+				case 'Document':
+					$oDocument = $value; // Value is an ormDocument object
+					$sFileName = '';
+					if (is_object($oDocument))
+					{
+						$sFileName = $oDocument->GetFileName();
+					}
+					$iMaxFileSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
+					$sHTMLValue = "<input type=\"hidden\" name=\"MAX_FILE_SIZE\" value=\"$iMaxFileSize\" />\n";
+				    $sHTMLValue .= "<input name=\"attr_{$sAttCode}{$sNameSuffix}\" type=\"hidden\" id=\"$iInputId\" \" value=\"$sFileName\"/>\n";
+				    $sHTMLValue .= "<span id=\"name_$iInputId\">$sFileName</span><br/>\n";
+				    $sHTMLValue .= "<input name=\"file_{$sAttCode}{$sNameSuffix}\" type=\"file\" id=\"file_$iInputId\" onChange=\"UpdateFileName('$iInputId', this.value);\"/>\n";
+				break;
+				
 				case 'String':
 				default:
 					// #@# todo - add context information (depending on dimensions)
@@ -728,7 +748,7 @@ abstract class cmdbAbstractObject extends CMDBObject
 		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
 		$iKey = $this->GetKey();
 		$aDetails = array();
-		$oPage->add("<form id=\"form_{$iFormId}\" method=\"post\" onSubmit=\"return CheckMandatoryFields('form_{$iFormId}')\">\n");
+		$oPage->add("<form id=\"form_{$iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return CheckMandatoryFields('form_{$iFormId}')\">\n");
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		{
 			if ('finalclass' == $sAttCode) // finalclass is a reserved word, hardcoded !

+ 1 - 3
application/startup.inc.php

@@ -2,8 +2,6 @@
 
 require_once('../application/utils.inc.php');
 
-$g_oConfig = new Config('../config-itop.php');
-
-MetaModel::Startup('../config-itop.php');
+MetaModel::Startup(ITOP_CONFIG_FILE);
 
 ?>

+ 6 - 3
application/uiwizard.class.inc.php

@@ -27,6 +27,10 @@ class UIWizard
 	 */	 
 	public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false)
 	{
+		if ($iStepIndex == 1) // one big form that contains everything, to make sure that the uploaded files are posted too
+		{
+			$this->m_oPage->add("<form method=\"post\" enctype=\"multipart/form-data\" action=\"../pages/UI.php\">\n");
+		}
 		$this->m_oPage->add("<div class=\"wizContainer\" id=\"wizStep$iStepIndex\" style=\"display:none;\">\n");
 		$this->m_oPage->add("<a name=\"step$iStepIndex\" />\n");
 		$aStates = MetaModel::EnumStates($this->m_sClass);
@@ -34,7 +38,7 @@ class UIWizard
 		$sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered
 		foreach($aStep as $sAttCode)
 		{
-			if ($sAttCode != 'finalclass') // Do not displa the attribute that stores the actual class name
+			if ($sAttCode != 'finalclass') // Do not display the attribute that stores the actual class name
 			{
 				$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
 				$sAttLabel = $oAttDef->GetLabel();
@@ -107,7 +111,6 @@ $sJSHandlerCode
 		$this->m_oPage->add("<div class=\"wizContainer\" id=\"wizStep$iStepIndex\" style=\"display:none;\">\n");
 		$this->m_oPage->add("<a name=\"step$iStepIndex\" />\n");
 		$this->m_oPage->P("Final step: confirmation");
-		$this->m_oPage->add("<form method=\"post\" action=\"../pages/UI.php\">\n");
 		$this->m_oPage->add("<input type=\"hidden\" name=\"operation\" value=\"wizard_apply_new\" />\n");
 		$this->m_oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\" />\n");
 		$this->m_oPage->add("<input type=\"hidden\" id=\"wizard_json_obj\" name=\"json_obj\" value=\"\" />\n");
@@ -124,8 +127,8 @@ $sJSHandlerCode
 		$this->m_oPage->add("<div id=\"object_preview\">\n");
 		$this->m_oPage->add("</div>\n");
 		$this->m_oPage->add("<input type=\"submit\" value=\"Create ".MetaModel::GetName($this->m_sClass)."\" />\n");
-		$this->m_oPage->add("</form>\n");
 		$this->m_oPage->add("</div>\n");
+		$this->m_oPage->add("</form>\n");
 	}	
 	/**
 	 * Compute the order of the fields & pages in the wizard

+ 109 - 38
application/utils.inc.php

@@ -1,10 +1,15 @@
 <?php
+require_once('../core/config.class.inc.php');
 
-define('CONFIGFILE', '../config.txt');
+define('ITOP_CONFIG_FILE', '../config-itop.php');
+
+class FileUploadException extends Exception
+{
+}
 
 class utils
 {
-	private static $m_aConfig = null;
+	private static $m_oConfig = null;
 
 	public static function ReadParam($sName, $defaultValue = "")
 	{
@@ -16,6 +21,75 @@ class utils
 		return isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
 	}
 	
+	/**
+	 * Reads an uploaded file and turns it into an ormDocument object - Triggers an exception in case of error
+	 * @param string $sName Name of the input used from uploading the file	 
+	 * @return ormDocument The uploaded file (can be 'empty' if nothing was uploaded)
+	 */	 	 
+	public static function  ReadPostedDocument($sName)
+	{
+		$oDocument = new ormDocument(); // an empty document
+		if(isset($_FILES[$sName]))
+		{
+			switch($_FILES[$sName]['error'])
+			{
+				case UPLOAD_ERR_OK:
+				$doc_content = file_get_contents($_FILES[$sName]['tmp_name']);
+				$sMimeType = $_FILES[$sName]['type'];
+				if (function_exists('finfo_file'))
+				{
+					// as of PHP 5.3 the fileinfo extension is bundled within PHP
+					// in which case we don't trust the mime type provided by the browser
+					$rInfo = @finfo_open(FILEINFO_MIME_TYPE); // return mime type ala mimetype extension
+					if ($rInfo !== false)
+					{
+					   $sType = @finfo_file($rInfo, $file);
+					   if ( ($sType !== false)
+					        && is_string($sType)
+					        && (strlen($sType)>0))
+					   {
+					        $sMimeType = $sType;
+					   }
+					}
+					@finfo_close($finfo);
+				}
+				$oDocument = new ormDocument($doc_content, $sMimeType, $_FILES[$sName]['name']);
+				break;
+				
+				case UPLOAD_ERR_NO_FILE:
+				// no file to load, it's a normal case, just return an empty document
+				break;
+				
+				case UPLOAD_ERR_FORM_SIZE:
+				case UPLOAD_ERR_INI_SIZE:
+				throw new FileUploadException("Uploaded file is too big. (Max allowed size is ".ini_get('upload_max_filesize').". Check you PHP configuration for upload_max_filesize.");
+				break;
+
+				case UPLOAD_ERR_PARTIAL:
+				throw new FileUploadException("File upload failed, file has been truncated.");
+				break;
+				
+				case UPLOAD_ERR_NO_TMP_DIR:
+				throw new FileUploadException("Missing a temporary folder.");
+				break;
+
+				case UPLOAD_ERR_CANT_WRITE:
+				throw new FileUploadException("Unable to write the temporary file to the disk (upload_tmp_dir = ".ini_get('upload_tmp_dir').")");
+				break;
+
+				case UPLOAD_ERR_EXTENSION:
+				throw new FileUploadException("File upload stopped by extension. (Original file name: ".$_FILES[$sName]['name'].")");
+				break;
+				
+				default:
+				throw new FileUploadException("File upload failed, unknown cause (Error code = ".$_FILES[$sName]['error'].")");
+				break;
+
+			}
+		}
+		return $oDocument;
+	}
+	
 	public static function GetNewTransactionId()
 	{
 		// TO DO implement the real mechanism here
@@ -34,47 +108,44 @@ class utils
 		return file_get_contents($sFileName);
 	}
 
-	public static function ReadConfig()
+	/**
+	 * Get access to the application config file
+	 * @param none
+	 * @return Config The Config object initialized from the application config file
+	 */	 	 	 	
+	public static function GetConfig()
 	{
-		self::$m_aConfig = array();
-
-		$sConfigContents = self::ReadFromFile(CONFIGFILE);
-		if (!$sConfigContents) throw new Exception("Could not load file ".CONFIGFILE);
-
-		foreach (explode("\n", $sConfigContents) as $sLine)
+		if (self::$m_oConfig == null)
 		{
-			$sLine = trim($sLine);
-			if (($iPos = strpos($sLine, '#')) !== false)
-			{
-				// strip out the end of the line right after the #
-				$sLine = substr($sLine, 0, $iPos);
-			}
-
-			$aMatches = array();
-			if (preg_match("@(\\S+.*)=\s*(\S+.*)@", $sLine, $aMatches))
-			{
-				$sParamName = trim($aMatches[1]);
-				$sParamValue = trim($aMatches[2]);
-				self::$m_aConfig[$sParamName] = $sParamValue; 
-			}
+			self::$m_oConfig = new Config(ITOP_CONFIG_FILE);
 		}
+		return self::$m_oConfig;
 	}
-
-	public static function GetConfig($sParamName, $defaultValue = "")
+	/**
+	 * Helper function to convert a value expressed in a 'user friendly format'
+	 * as in php.ini, e.g. 256k, 2M, 1G etc. Into a number of bytes
+	 * @param mixed $value The value as read from php.ini
+	 * @return number
+	 */	 	  
+	public static function ConvertToBytes( $value )
 	{
-		if (is_null(self::$m_aConfig))
+		$iReturn = $value;
+	    if ( !is_numeric( $value ) )
 		{
-			self::ReadConfig();
-		}
-
-		if (array_key_exists($sParamName, self::$m_aConfig))
-		{
-			return self::$m_aConfig[$sParamName];
-		}
-		else
-		{
-			return $defaultValue;
-		}
-	}
+	        $iLength = strlen( $value );
+	        $iReturn = substr( $value, 0, $iLength - 1 );
+	        $sUnit = strtoupper( substr( $value, $iLength - 1 ) );
+	        switch ( $sUnit )
+			{
+	            case 'G':
+	                $iReturn *= 1024;
+	            case 'M':
+	                $iReturn *= 1024;
+	            case 'K':
+	                $iReturn *= 1024;
+	        }
+	    }
+        return $iReturn;
+    }
 }
 ?>

+ 20 - 2
application/wizardhelper.class.inc.php

@@ -8,8 +8,12 @@ class WizardHelper
 	public function __construct()
 	{
 	}
-	
-	public function GetTargetObject()
+	/**
+	 * Constructs the PHP target object from the parameters sent to the web page by the wizard
+	 * @param boolean $bReadUploadedFiles True to also ready any uploaded file (for blob/document fields)
+	 * @return object
+	 */	 	 	 	
+	public function GetTargetObject($bReadUploadedFiles = false)
 	{
 		$oObj = MetaModel::NewObject($this->m_aData['m_sClass']);
 		foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value)
@@ -60,6 +64,20 @@ class WizardHelper
 					$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
 					$oObj->Set($sAttCode, $oSet);
 				}
+				else if ( $oAttDef->GetEditClass() == 'Document' )
+				{
+					if ($bReadUploadedFiles)
+					{
+						$oDocument = utils::ReadPostedDocument('file_'.$sAttCode);
+						$oObj->Set($sAttCode, $oDocument);
+					}
+					else
+					{
+						// Create a new empty document, just for displaying the file name
+						$oDocument = new ormDocument(null, '', $value);
+						$oObj->Set($sAttCode, $oDocument);
+					}
+				}
 				else if (($oAttDef->IsExternalKey()) && (!empty($value)) )
 				{
 					// For external keys: load the target object so that external fields

+ 1 - 50
business/itop.business.class.inc.php

@@ -406,7 +406,7 @@ class bizDocument extends logRealObject
 		MetaModel::Init_AddFilterFromAttribute("description");
 
 		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope', 'description', 'contents')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'scope', 'contents')); // Attributes to be displayed for a list
 		// Search criteria
 		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'scope')); // Criteria of the std search form
 		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'scope')); // Criteria of the advanced search form
@@ -417,55 +417,6 @@ class bizDocument extends logRealObject
 
 ////////////////////////////////////////////////////////////////////////////////////
 /**
-* A version of an electronic document
-*/
-////////////////////////////////////////////////////////////////////////////////////
-class bizDocVersion extends cmdbAbstractObject
-{
-	public static function Init()
-	{
-		global $oAllowedStatuses;
-		$aParams = array
-		(
-			"category" => "bizmodel,searchable",
-			"name" => "DocumentVersion",
-			"description" => "A version of a document",
-			"key_type" => "autoincrement",
-			"key_label" => "id",
-			"name_attcode" => "version_number",
-			"state_attcode" => "",
-			"reconc_keys" => array("document", "version_number"),
-			"db_table" => "document_versions",
-			"db_key_field" => "id",
-			"db_finalclass_field" => "",
-			"display_template" => "../business/templates/default.html",
-		);
-		MetaModel::Init_Params($aParams);
-		MetaModel::Init_AddAttribute(new AttributeExternalKey("document", array("targetclass"=>"bizDocument", "label"=>"Document", "description"=>"The main document", "allowed_values"=>null, "sql"=>"document_id", "is_null_allowed"=>false, "on_target_delete"=>DEL_MANUAL, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeExternalField("docname", array("label"=>"Document Name", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'document', "target_attcode"=>"name")));
-		MetaModel::Init_AddAttribute(new AttributeString("version_number", array("label"=>"Version", "description"=>"Version identifier", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type", "allowed_values"=>new ValueSetEnum("local,draft"), "sql"=>"type", "default_value"=>"local", "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeURL("url", array("label"=>"URL", "description"=>"Hyperlink to the version", "allowed_values"=>null, "target"=>"_blank", "sql"=>"url", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"Service Description", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
-
-		MetaModel::Init_AddFilterFromAttribute("document");
-		MetaModel::Init_AddFilterFromAttribute("docname");
-		MetaModel::Init_AddFilterFromAttribute("version_number");
-		MetaModel::Init_AddFilterFromAttribute("status");
-		MetaModel::Init_AddFilterFromAttribute("type");
-		MetaModel::Init_AddFilterFromAttribute("description");
-
-		MetaModel::Init_SetZListItems('details', array('document', 'status', 'version_number', 'type','url','description')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('version_number', 'status', 'type', 'url')); // Attributes to be displayed for a list
-		// Search criteria
-		MetaModel::Init_SetZListItems('standard_search', array('docname', 'type')); // Criteria of the std search form
-		MetaModel::Init_SetZListItems('advanced_search', array('docname', 'type')); // Criteria o
-	}
-}
-
-////////////////////////////////////////////////////////////////////////////////////
-/**
 * n-n link between any Object and a Document
 */
 ////////////////////////////////////////////////////////////////////////////////////

+ 0 - 6
business/templates/document.html

@@ -5,9 +5,3 @@
 </div>
 <img src="../images/folder_documents.png" style="margin-top:-10px; margin-right:10px; float:right">
 <itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/oql">SELECT $class$ WHERE id = $pkey$</itopblock>
-<itoptabs>
-	<itoptab name="Versions">
-		<itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT bizDocVersion WHERE document = $pkey$</itopblock>
-	</itoptab>
-
-</itoptabs>

+ 14 - 1
core/attributedef.class.inc.php

@@ -1233,7 +1233,20 @@ class AttributeBlob extends AttributeDefinition
 
 	public function GetAsHTML($value)
 	{
-		return $value->GetAsHTML();
+		if (is_object($value))
+		{
+			return $value->GetAsHTML();
+		}
+	}
+
+	public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',')
+	{
+		return ''; // Not exportable in CSV !
+	}
+	
+	public function GetAsXML($value)
+	{
+		return ''; // Not exportable in XML, or as CDATA + some subtags ??
 	}
 }
 

+ 5 - 1
core/cmdbchangeop.class.inc.php

@@ -347,7 +347,11 @@ class CMDBChangeOpSetAttributeBlob extends CMDBChangeOpSetAttribute
 			$sAttName = $oAttDef->GetLabel();
 			$oPrevDoc = $this->Get('prevdata');
 			$sDocView = $oPrevDoc->GetAsHtml();
-			$sResult = "$sAttName changed (previous value: $sDocView)";
+			$sDocView .= "<br/>Open in New Window: ".$oPrevDoc->GetDisplayLink(get_class($this), $this->GetKey(), 'prevdata').", \n";
+			$sDocView .= "Download: ".$oPrevDoc->GetDownloadLink(get_class($this), $this->GetKey(), 'prevdata')."\n";
+
+			//$sDocView = $oPrevDoc->GetDisplayInline(get_class($this), $this->GetKey(), 'prevdata');
+			$sResult = "$sAttName changed, previous value: $sDocView";
 		}
 		return $sResult;
 	}

+ 17 - 0
core/cmdbsource.class.inc.php

@@ -428,6 +428,23 @@ class CMDBSource
 		mysql_free_result($result);
 		return $aRows;
 	}
+	
+	/**
+	 * Returns the value of the specified server variable
+	 * @param string $sVarName Name of the server variable
+	 * @return mixed Current value of the variable
+	 */	   	
+	public static function GetServerVariable($sVarName)
+	{
+		$result = '';
+		$sSql = "SELECT @@$sVarName as theVar";
+		$aRows = self::QueryToArray($sSql);
+		if (count($aRows) > 0)
+		{
+			$result = $aRows[0]['theVar'];
+		}
+		return $result;
+	}
 }
 
 

+ 67 - 10
core/ormdocument.class.inc.php

@@ -33,6 +33,11 @@ class ormDocument
 		return MyHelpers::beautifulstr($this->m_data, 100, true);
 	}
 
+	public function IsEmpty()
+	{
+		return ($this->m_data == null);
+	}
+	
 	public function GetMimeType()
 	{
 		return $this->m_sMimeType;
@@ -59,20 +64,72 @@ class ormDocument
 
 	public function GetAsHTML()
 	{
-		$data = $this->GetData();
-
+		$sResult = '';
+		if ($this->IsEmpty())
+		{
+			// If the filename is not empty, display it, this is used
+			// by the creation wizard while the file has not yet been uploaded
+			$sResult = $this->GetFileName();
+		}
+		else
+		{
+			$data = $this->GetData();
+			$sResult = $this->GetFileName().' [ '.$this->GetMimeType().', size: '.strlen($data).' byte(s) ]';
+		}
+		return $sResult;
+	}
+	
+	/**
+	 * Returns an HTML fragment that will display the document *inline* (if possible)
+	 * @return string
+	 */	 	 	
+	public function GetDisplayInline($sClass, $Id, $sAttCode)
+	{
 		switch ($this->GetMainMimeType())
 		{
-		case 'text':
-			return "<pre>".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."</pre>\n";
-
-		case 'application':
-			return "binary data for ".$this->GetMimeType().', size: '.strlen($data).' byte(s).';
+			case 'text':
+			case 'html':
+			$data = $this->GetData();
+			switch($this->GetMimeType())
+			{
+				case 'text/html':
+				case 'text/xml':
+				return "<iframe src=\"../pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n";
+				
+				default:
+				return "<pre>".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."</pre>\n";			
+			}
+			break; // Not really needed, but...
 
-		case 'html':
-		default:
-			return "<div>".htmlentities(MyHelpers::beautifulstr($data, 1000, true))."</div>\n";
+			case 'application':
+			switch($this->GetMimeType())
+			{
+				case 'application/pdf':
+				return "<iframe src=\"../pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" width=\"100%\" height=\"400\">Loading...</iframe>\n";
+			}
+			break;
+			
+			case 'image':
+			return "<img src=\"../pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" />\n";
 		}
 	}
+	
+	/**
+	 * Returns an hyperlink to display the document *inline*
+	 * @return string
+	 */	 	 	
+	public function GetDisplayLink($sClass, $Id, $sAttCode)
+	{
+		return "<a href=\"../pages/ajax.render.php?operation=display_document&class=$sClass&id=$Id&field=$sAttCode\" target=\"_blank\" >".$this->GetFileName()."</a>\n";
+	}
+	
+	/**
+	 * Returns an hyperlink to download the document (content-disposition: attachment)
+	 * @return string
+	 */	 	 	
+	public function GetDownloadLink($sClass, $Id, $sAttCode)
+	{
+		return "<a href=\"../pages/ajax.render.php?operation=download_document&class=$sClass&id=$Id&field=$sAttCode\">".$this->GetFileName()."</a>\n";
+	}
 }
 ?>

+ 12 - 0
js/utils.js

@@ -17,3 +17,15 @@ function ReloadTruncatedList(divId, sSerializedFilter, sExtraParams)
 		}
 	 );
 }
+
+/**
+ * Update the display and value of a file input widget when the user picks a new file
+ */ 
+function UpdateFileName(id, sNewFileName)
+{
+	var aPath = sNewFileName.split('\\');
+	var sNewFileName = aPath[aPath.length-1];
+
+	$('#'+id).val(sNewFileName);
+	$('#name_'+id).text(sNewFileName);
+}

+ 1 - 0
js/wizardhelper.js

@@ -18,6 +18,7 @@ function WizardHelper(sClass)
 		this.m_oData.m_oFieldsMap = oFieldsMap;
 		
 	}
+	
 	this.SetFieldsCount = function (count)
 	{
 		this.m_oData.m_iFieldsCount = count;

+ 12 - 1
pages/UI.php

@@ -459,6 +459,17 @@ switch($operation)
 							$bObjectModified = true;
 						}
 					}
+					else if ($oAttDef->GetEditClass() == 'Document')
+					{
+						// There should be an uploaded file with the named attr_<attCode>
+						$oDocument = utils::ReadPostedDocument('file_'.$sAttCode);
+						if (!$oDocument->IsEmpty())
+						{
+							// A new file has been uploaded
+							$oObj->Set($sAttCode, $oDocument);
+							$bObjectModified = true;
+						}
+					}
 					else if (!$oAttDef->IsExternalField())
 					{
 						$rawValue = utils::ReadPostedParam("attr_$sAttCode", null);
@@ -826,7 +837,7 @@ switch($operation)
 	}
 	else
 	{
-		$oObj = $oWizardHelper->GetTargetObject();
+		$oObj = $oWizardHelper->GetTargetObject(true /* read uploaded files */);
 		if (is_object($oObj))
 		{
 			$sClass = get_class($oObj);

+ 51 - 0
pages/ajax.render.php

@@ -269,9 +269,60 @@ switch($operation)
 		$oPage->add('<option title="Here is more information..." value="'.$oObj->GetKey().'">'.$oObj->GetDisplayName().'</option>');
 	}
 	break;
+	
+	case 'display_document':
+	$id = utils::ReadParam('id', '');
+	$sField = utils::ReadParam('field', '');
+	if (!empty($sClass) && !empty($id) && !empty($sField))
+	{
+		DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'inline');
+	}
+	break;
+	
+	case 'download_document':
+	$id = utils::ReadParam('id', '');
+	$sField = utils::ReadParam('field', '');
+	if (!empty($sClass) && !empty($id) && !empty($sField))
+	{
+		DownloadDocument($oPage, $oContext, $sClass, $id, $sField, 'attachement');
+	}
+	break;
 
 	default:
 	$oPage->p("Invalid query.");
 }
 $oPage->output();
+
+/**
+ * Downloads a document to the browser, either as 'inline' or 'attachment'
+ *  
+ * @param web_page $oPage The web page for the output
+ * @param UserContext $oContext The current User/security context to retreive the objects
+ * @param string $sClass Class name of the object
+ * @param mixed $id Identifier of the object
+ * @param string $sAttCode Name of the attribute containing the document to download
+ * @param string $sContentDisposition Either 'inline' or 'attachment'
+ * @return none
+ */   
+function DownloadDocument(web_page $oPage, UserContext $oContext, $sClass, $id, $sAttCode, $sContentDisposition = 'attachement')
+{
+	try
+	{
+		$oObj = $oContext->GetObject($sClass, $id);
+		if (is_object($oObj))
+		{
+			$oDocument = $oObj->Get($sAttCode);
+			if (is_object($oDocument))
+			{
+				$oPage->add_header('Content-type: '.$oDocument->GetMimeType());
+				$oPage->add_header('Content-Disposition: '.$sContentDisposition.'; filename="'.$oDocument->GetFileName().'"');
+				$oPage->add($oDocument->GetData());
+			}
+		}
+	}
+	catch(Exception $e)
+	{
+		$oPage->p($e->getMessage());
+	}
+}
 ?>

+ 52 - 2
setup/index.php

@@ -23,6 +23,7 @@ $oP = new setup_web_page('iTop configuration wizard');
  */
 function CheckPHPVersion(setup_web_page $oP)
 {
+	$bResult = true;
 	$oP->log('Info - CheckPHPVersion');
 	if (version_compare(phpversion(), PHP_MIN_VERSION, '>='))
 	{
@@ -57,9 +58,38 @@ function CheckPHPVersion(setup_web_page $oP)
 	if (count($aMissingExtensions) > 0)
 	{
 		$oP->error("Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks).".");
-		return false;
+		$bResult = false;
 	}
-	return true;
+	// Check some ini settings here
+  	if (!ini_get('file_uploads'))
+  	{
+		$oP->error("Files upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').").");
+		$bResult = false;
+	}
+
+	$sUploadTmpDir = ini_get('upload_tmp_dir');
+  	if (empty($sUploadTmpDir))
+  	{
+		$oP->error("Temporary directory for files upload is not defined (upload_tmp_dir)");
+		$bResult = false;
+	}
+
+  	if (!ini_get('upload_max_filesize'))
+  	{
+		$oP->error("File upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').").");
+	}
+
+	$iMaxFileUploads = ini_get('max_file_uploads');
+  	if (!empty($iMaxFileUploads) && ($iMaxFileUploads < 1))
+  	{
+		$oP->error("File upload is not allowed on this server (max_file_uploads = ".ini_get('max_file_uploads').").");
+		$bResult = false;
+	}
+	$oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize'));
+	$oP->log("Info - upload_tmp_dir: $sUploadTmpDir");
+	$oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads'));
+
+	return $bResult;
 }
   
 /**
@@ -80,6 +110,26 @@ function CheckServerConnection(setup_web_page $oP, $sDBServer, $sDBUser, $sDBPwd
 		if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>='))
 		{
 			$oP->ok("Current MySQL version ($sDBVersion), greater than minimum required version (".MYSQL_MIN_VERSION.")");
+			// Check some server variables
+			$iMaxAllowedPacket = $oDBSource->GetServerVariable('max_allowed_packet');
+			$iMaxUploadSize = utils::ConvertToBytes(ini_get('upload_max_filesize'));
+			if ($iMaxAllowedPacket >= (500 + $iMaxUploadSize)) // Allow some space for the query + the file to upload
+			{
+				$oP->ok("MySQL server's max_allowed_packet is big enough.");
+			}
+			else if($iMaxAllowedPacket < $iMaxUploadSize)
+			{
+				$oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough.");
+				$oP->warning("Consider setting it to at least ".(500 + $iMaxUploadSize).".");
+			}
+			$oP->log("Info - MySQL max_allowed_packet: $iMaxAllowedPacket");
+			$iMaxConnections = $oDBSource->GetServerVariable('max_connections');
+			if ($iMaxConnections < 5)
+			{
+				$oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough.");
+				$oP->warning("Consider setting it to at least 5.");
+			}
+			$oP->log("Info - MySQL max_connections: ".($oDBSource->GetServerVariable('max_connections')));
 		}
 		else
 		{