Browse Source

Moved under "trunk" to be able to track releases under "tags"

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@55 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 16 years ago
parent
commit
4801d921f6
100 changed files with 29256 additions and 0 deletions
  1. 385 0
      addons/userrights/userrightsmatrix.class.inc.php
  2. 64 0
      addons/userrights/userrightsnull.class.inc.php
  3. 145 0
      application/ajaxwebpage.class.inc.php
  4. 12 0
      application/application.inc.php
  5. 81 0
      application/applicationcontext.class.inc.php
  6. 45 0
      application/audit.category.class.inc.php
  7. 52 0
      application/audit.rule.class.inc.php
  8. 647 0
      application/cmdbabstract.class.inc.php
  9. 35 0
      application/csvpage.class.inc.php
  10. 252 0
      application/dialogstack.class.inc.php
  11. 686 0
      application/displayblock.class.inc.php
  12. 54 0
      application/iotask.class.inc.php
  13. 414 0
      application/itopwebpage.class.inc.php
  14. 32 0
      application/itopwizardwebpage.class.inc.php
  15. 117 0
      application/loginwebpage.class.inc.php
  16. 219 0
      application/menunode.class.inc.php
  17. 76 0
      application/nicewebpage.class.inc.php
  18. 7 0
      application/startup.inc.php
  19. 224 0
      application/template.class.inc.php
  20. 12 0
      application/templates/audit_category.html
  21. 301 0
      application/ui.linkswidget.class.inc.php
  22. 257 0
      application/uiwizard.class.inc.php
  23. 106 0
      application/usercontext.class.inc.php
  24. 80 0
      application/utils.inc.php
  25. 289 0
      application/webpage.class.inc.php
  26. 204 0
      application/wizardhelper.class.inc.php
  27. 36 0
      application/xmlpage.class.inc.php
  28. 284 0
      business/ChangeMgmt.php
  29. 32 0
      business/Changes-04-Sep-2007.php
  30. 165 0
      business/KEDB.php
  31. 261 0
      business/ServiceMgmt.business.php
  32. 261 0
      business/business_itopbegins.class.inc.php
  33. 366 0
      business/business_test.class.inc.php
  34. 4845 0
      business/data.samples.inc.php
  35. 367 0
      business/incident.business.php
  36. 1629 0
      business/itop.business.class.inc.php
  37. 12 0
      business/templates/Circuits.html
  38. 19 0
      business/templates/application.html
  39. 17 0
      business/templates/change.html
  40. 18 0
      business/templates/contract.html
  41. 8 0
      business/templates/default.html
  42. 13 0
      business/templates/document.html
  43. 16 0
      business/templates/group.html
  44. 14 0
      business/templates/interface.html
  45. 11 0
      business/templates/knownError.html
  46. 25 0
      business/templates/location.html
  47. 25 0
      business/templates/network.device.html
  48. 27 0
      business/templates/pc.html
  49. 18 0
      business/templates/person.html
  50. 30 0
      business/templates/server.html
  51. 13 0
      business/templates/service.html
  52. 13 0
      business/templates/software.html
  53. 21 0
      business/templates/team.html
  54. 21 0
      business/templates/ticket.html
  55. 272 0
      business/test_farm.class.inc.php
  56. 38 0
      config-dist.php
  57. 35 0
      config-test-farm.php
  58. 36 0
      config-test-mymodel.php
  59. 491 0
      core/MyHelpers.class.inc.php
  60. 323 0
      core/archive.class.inc.php
  61. 989 0
      core/attributedef.class.inc.php
  62. 439 0
      core/bulkchange.class.inc.php
  63. 44 0
      core/cmdbchange.class.inc.php
  64. 167 0
      core/cmdbchangeop.class.inc.php
  65. 432 0
      core/cmdbobject.class.inc.php
  66. 420 0
      core/cmdbsource.class.inc.php
  67. 272 0
      core/config.class.inc.php
  68. 63 0
      core/coreexception.class.inc.php
  69. 192 0
      core/csvparser.class.inc.php
  70. 362 0
      core/data.generator.class.inc.php
  71. 831 0
      core/dbobject.class.php
  72. 973 0
      core/dbobjectsearch.class.php
  73. 250 0
      core/dbobjectset.class.php
  74. 485 0
      core/expression.class.inc.php
  75. 296 0
      core/filterdef.class.inc.php
  76. 2643 0
      core/metamodel.class.php
  77. 3 0
      core/oql/build.cmd
  78. 522 0
      core/oql/oql-lexer.php
  79. 305 0
      core/oql/oql-lexer.plex
  80. 1669 0
      core/oql/oql-parser.php
  81. 250 0
      core/oql/oql-parser.y
  82. 78 0
      core/oql/oqlexception.class.inc.php
  83. 59 0
      core/oql/oqlinterpreter.class.inc.php
  84. 168 0
      core/oql/oqlquery.class.inc.php
  85. 413 0
      core/sqlquery.class.inc.php
  86. 59 0
      core/stimulus.class.inc.php
  87. 504 0
      core/test.class.inc.php
  88. 211 0
      core/userrights.class.inc.php
  89. 238 0
      core/valuesetdef.class.inc.php
  90. 222 0
      css/blue_green.css
  91. 117 0
      css/date.picker.css
  92. 165 0
      css/default.css
  93. 40 0
      css/jqModal.css
  94. 46 0
      css/jquery.autocomplete.css
  95. 15 0
      css/jquery.tabs-ie.css
  96. 76 0
      css/jquery.tabs.css
  97. 47 0
      css/jquery.treeview.css
  98. 633 0
      css/light-grey.css
  99. BIN
      images/Resize of Resize of erwanIncidents.jpg
  100. BIN
      images/Resize of Resize of erwanNetwork.jpg

+ 385 - 0
addons/userrights/userrightsmatrix.class.inc.php

@@ -0,0 +1,385 @@
+<?php
+
+/**
+ * UserRightsMatrix
+ * User management Module 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+
+class UserRightsMatrixUsers extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "addon/userrights",
+			"name" => "user",
+			"description" => "users and credentials",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "login",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_ur_matrixusers",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeInteger("userid", array("label"=>"User id", "description"=>"User identifier (depends on the business model)", "allowed_values"=>null, "sql"=>"userid", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("login", array("label"=>"login", "description"=>"user identification string", "allowed_values"=>null, "sql"=>"login", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("password", array("label"=>"password", "description"=>"user authentication string", "allowed_values"=>null, "sql"=>"pwd", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("userid");
+		MetaModel::Init_AddFilterFromAttribute("login");
+	}
+}
+
+class UserRightsMatrixClassGrant extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "addon/userrights",
+			"name" => "class_permission",
+			"description" => "permissions on classes",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_ur_matrixclasses",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login")));
+		MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("userid");
+		MetaModel::Init_AddFilterFromAttribute("login");
+		MetaModel::Init_AddFilterFromAttribute("class");
+		MetaModel::Init_AddFilterFromAttribute("action");
+	}
+}
+
+class UserRightsMatrixClassStimulusGrant extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "addon/userrights",
+			"name" => "stimulus_permission",
+			"description" => "permissions on stimilus in the life cycle of the object",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_ur_matrixclassesstimulus",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login")));
+		MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeString("stimulus", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("userid");
+		MetaModel::Init_AddFilterFromAttribute("login");
+		MetaModel::Init_AddFilterFromAttribute("class");
+		MetaModel::Init_AddFilterFromAttribute("stimulus");
+	}
+}
+
+class UserRightsMatrixAttributeGrant extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "addon/userrights",
+			"name" => "attribute_permission",
+			"description" => "permissions at the attributes level",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_ur_matrixattributes",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("userid", array("targetclass"=>"UserRightsMatrixUsers", "jointype"=> "", "label"=>"user", "description"=>"user account", "allowed_values"=>null, "sql"=>"userid", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("login", array("label"=>"Login", "description"=>"Login", "allowed_values"=>null, "extkey_attcode"=> 'userid', "target_attcode"=>"login")));
+		MetaModel::Init_AddAttribute(new AttributeString("class", array("label"=>"class", "description"=>"class name", "allowed_values"=>null, "sql"=>"class", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"attribute", "description"=>"attribute code", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeString("action", array("label"=>"action", "description"=>"operations to perform on the given class", "allowed_values"=>null, "sql"=>"action", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("permission", array("label"=>"permission", "description"=>"allowed or not allowed?", "allowed_values"=>new ValueSetEnum('yes,no'), "sql"=>"permission", "default_value"=>"yes", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("userid");
+		MetaModel::Init_AddFilterFromAttribute("login");
+		MetaModel::Init_AddFilterFromAttribute("class");
+		MetaModel::Init_AddFilterFromAttribute("attcode");
+		MetaModel::Init_AddFilterFromAttribute("action");
+	}
+}
+
+
+
+
+class UserRightsMatrix extends UserRightsAddOnAPI
+{
+	static public $m_aActionCodes = array(
+		UR_ACTION_READ => 'read',
+		UR_ACTION_MODIFY => 'modify',
+		UR_ACTION_DELETE => 'delete',
+		UR_ACTION_BULK_READ => 'bulk read',
+		UR_ACTION_BULK_MODIFY => 'bulk modify',
+		UR_ACTION_BULK_DELETE => 'bulk delete',
+	);
+
+	// Installation: create the very first user
+	public function CreateAdministrator($sAdminUser, $sAdminPwd)
+	{
+		// Maybe we should check that no other user with userid == 0 exists
+		$oUser = new UserRightsMatrixUsers();
+		$oUser->Set('login', $sAdminUser);
+		$oUser->Set('password', $sAdminPwd);
+		$oUser->Set('userid', 1); // one is for root !
+		$iUserId = $oUser->DBInsertNoReload();
+		$this->SetupUser($iUserId, true);
+		return true;
+	}
+
+	public function Setup()
+	{
+		// Users must be added manually
+		// This procedure will then update the matrix when a new user is found or a new class/attribute appears
+		$oUserSet = new DBObjectSet(DBObjectSearch::FromSibuSQL("UserRightsMatrixUsers"));
+		while ($oUser = $oUserSet->Fetch())
+		{
+			$this->SetupUser($oUser->GetKey());
+		}
+		return true;
+	}
+
+	protected function SetupUser($iUserId, $bNewUser = false)
+	{
+		foreach(array('bizmodel', 'application', 'gui', 'core/cmdb') as $sCategory)
+		{
+			foreach (MetaModel::GetClasses($sCategory) as $sClass)
+			{
+				foreach (self::$m_aActionCodes as $iActionCode => $sAction)
+				{
+					if ($bNewUser)
+					{
+						$bAddCell = true;
+					}
+					else
+					{
+						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND userid = $iUserId"));
+						$bAddCell = ($oSet->Count() < 1);
+					}
+					if ($bAddCell)
+					{
+						// Create a new entry
+						$oMyClassGrant = MetaModel::NewObject("UserRightsMatrixClassGrant");
+						$oMyClassGrant->Set("userid", $iUserId);
+						$oMyClassGrant->Set("class", $sClass);
+						$oMyClassGrant->Set("action", $sAction);
+						$oMyClassGrant->Set("permission", "yes");
+						$iId = $oMyClassGrant->DBInsertNoReload();
+					}
+				}
+				foreach (MetaModel::EnumStimuli($sClass) as $sStimulusCode => $oStimulus)
+				{
+					if ($bNewUser)
+					{
+						$bAddCell = true;
+					}
+					else
+					{
+						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND userid = $iUserId"));
+						$bAddCell = ($oSet->Count() < 1);
+					}
+					if ($bAddCell)
+					{
+						// Create a new entry
+						$oMyClassGrant = MetaModel::NewObject("UserRightsMatrixClassStimulusGrant");
+						$oMyClassGrant->Set("userid", $iUserId);
+						$oMyClassGrant->Set("class", $sClass);
+						$oMyClassGrant->Set("stimulus", $sStimulusCode);
+						$oMyClassGrant->Set("permission", "yes");
+						$iId = $oMyClassGrant->DBInsertNoReload();
+					}
+				}
+				foreach (MetaModel::GetAttributesList($sClass) as $sAttCode)
+				{
+					if ($bNewUser)
+					{
+						$bAddCell = true;
+					}
+					else
+					{
+						$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE class = '$sClass' AND attcode = '$sAttCode' AND userid = $iUserId"));
+						$bAddCell = ($oSet->Count() < 1);
+					}
+					if ($bAddCell)
+					{
+						foreach (array('read', 'modify') as $sAction)
+						{
+							// Create a new entry
+							$oMyAttGrant = MetaModel::NewObject("UserRightsMatrixAttributeGrant");
+							$oMyAttGrant->Set("userid", $iUserId);
+							$oMyAttGrant->Set("class", $sClass);
+							$oMyAttGrant->Set("attcode", $sAttCode);
+							$oMyAttGrant->Set("action", $sAction);
+							$oMyAttGrant->Set("permission", "yes");
+							$iId = $oMyAttGrant->DBInsertNoReload();
+						}
+					}
+				}
+			}
+		}
+	}
+
+
+	public function Init()
+	{
+		// Could be loaded in a shared memory (?)
+		return true;
+	}
+
+	public function CheckCredentials($sUserName, $sPassword)
+	{
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixUsers WHERE login = '$sUserName'"));
+		if ($oSet->Count() < 1)
+		{
+		// todo: throw an exception?
+			return false;
+		}
+
+		$oLogin = $oSet->Fetch();
+		if ($oLogin->Get('password') == $sPassword)
+		{
+			return true;
+		}
+		// todo: throw an exception?
+		return false;
+	}
+
+	public function GetFilter($sUserName, $sClass)
+	{
+		$oNullFilter  = new DBObjectSearch($sClass);
+		return $oNullFilter;
+	}
+
+	public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances)
+	{
+		if (!array_key_exists($iActionCode, self::$m_aActionCodes))
+		{
+			return UR_ALLOWED_NO;
+		}
+		$sAction = self::$m_aActionCodes[$iActionCode];
+
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassGrant WHERE class = '$sClass' AND action = '$sAction' AND login = '$sUserName'"));
+		if ($oSet->Count() < 1)
+		{
+			return UR_ALLOWED_NO;
+		}
+
+		$oGrantRecord = $oSet->Fetch();
+		switch ($oGrantRecord->Get('permission'))
+		{
+			case 'yes':
+				$iRetCode = UR_ALLOWED_YES;
+				break;
+			case 'no':
+			default:
+				$iRetCode = UR_ALLOWED_NO;
+				break;
+		}
+		return $iRetCode;
+	}
+
+	public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	{
+		if (!array_key_exists($iActionCode, self::$m_aActionCodes))
+		{
+			return UR_ALLOWED_NO;
+		}
+		$sAction = self::$m_aActionCodes[$iActionCode];
+
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixAttributeGrant WHERE UserRightsMatrixAttributeGrant.class = '$sClass' AND UserRightsMatrixAttributeGrant.attcode = '$sAttCode' AND UserRightsMatrixAttributeGrant.action = '$sAction' AND UserRightsMatrixAttributeGrant.login = '$sUserName'"));
+		if ($oSet->Count() < 1)
+		{
+			return UR_ALLOWED_NO;
+		}
+
+		$oGrantRecord = $oSet->Fetch();
+		switch ($oGrantRecord->Get('permission'))
+		{
+			case 'yes':
+				$iRetCode = UR_ALLOWED_YES;
+				break;
+			case 'no':
+			default:
+				$iRetCode = UR_ALLOWED_NO;
+				break;
+		}
+		return $iRetCode;
+	}
+
+	public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances)
+	{
+		$oSet = new DBObjectSet(DBObjectSearch::FromOQL("SELECT UserRightsMatrixClassStimulusGrant WHERE class = '$sClass' AND stimulus = '$sStimulusCode' AND login = '$sUserName'"));
+		if ($oSet->Count() < 1)
+		{
+			return UR_ALLOWED_NO;
+		}
+
+		$oGrantRecord = $oSet->Fetch();
+		switch ($oGrantRecord->Get('permission'))
+		{
+			case 'yes':
+				$iRetCode = UR_ALLOWED_YES;
+				break;
+			case 'no':
+			default:
+				$iRetCode = UR_ALLOWED_NO;
+				break;
+		}
+		return $iRetCode;
+	}
+}
+
+UserRights::SelectModule('UserRightsMatrix');
+
+?>

+ 64 - 0
addons/userrights/userrightsnull.class.inc.php

@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * UserRightsNull
+ * User management Module - say Yeah! to everything
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+
+class UserRightsNull extends UserRightsAddOnAPI
+{
+	// Installation: create the very first user
+	public function CreateAdministrator($sAdminUser, $sAdminPwd)
+	{
+		return true;
+	}
+
+	public function Setup()
+	{
+		return true;
+	}
+
+	public function Init()
+	{
+		return true;
+	}
+
+	public function CheckCredentials($sUserName, $sPassword)
+	{
+		return true;
+	}
+
+	public function GetFilter($sUserName, $sClass)
+	{
+		$oNullFilter  = new DBObjectSearch($sClass);
+		return $oNullFilter;
+	}
+
+	public function IsActionAllowed($sUserName, $sClass, $iActionCode, dbObjectSet $aInstances)
+	{
+		return UR_ALLOWED_YES;
+	}
+
+	public function IsStimulusAllowed($sUserName, $sClass, $sStimulusCode, dbObjectSet $aInstances)
+	{
+		return UR_ALLOWED_YES;
+	}
+
+	public function IsActionAllowedOnAttribute($sUserName, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	{
+		return UR_ALLOWED_YES;
+	}
+}
+
+UserRights::SelectModule('UserRightsNull');
+
+?>

+ 145 - 0
application/ajaxwebpage.class.inc.php

@@ -0,0 +1,145 @@
+<?php
+require_once("../application/webpage.class.inc.php");
+/**
+ * Simple web page with no includes, header or fancy formatting, useful to
+ * generate HTML fragments when called by an AJAX method
+ *
+ * @package     iTopApplication
+ * @access		public 
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <dflaven@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ */
+ 
+class ajax_page extends web_page
+{
+    /**
+     * Jquery style ready script
+     * @var Hash     
+     */	  
+	protected $m_sReadyScript;
+	
+    /**
+     * constructor for the web page
+     * @param string $s_title Not used
+     */	  
+	function __construct($s_title)
+    {
+        parent::__construct($s_title);
+        $this->m_sReadyScript = "";
+    }	
+
+	
+    /**
+     * Echoes the content of the whole page
+     * @return void
+     */	  
+    public function output()
+    {
+        foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        $s_captured_output = ob_get_contents();
+        ob_end_clean();
+        echo trim($this->s_content);
+        if (!empty($this->m_sReadyScript))
+        {
+	        echo "<script>\n";
+	        echo $this->m_sReadyScript; // Ready Scripts are output as simple scripts
+	        echo "</script>\n";
+        }
+		if (trim($s_captured_output) != "")
+        {
+            echo $s_captured_output;
+        }
+    }
+
+    /**
+     * Adds a paragraph with a smaller font into the page
+     * NOT implemented (i.e does nothing)
+     * @param string $sText Content of the (small) paragraph     
+     * @return void
+     */	 	      
+    public function small_p($sText)
+    {
+	}
+	
+    /**
+     * Adds a tabular content to the web page
+     * @param Hash $aConfig Configuration of the table: hash array of 'column_id' => 'Column Label'
+     * @param Hash $aData Hash array. Data to display in the table: each row is made of 'column_id' => Data. A column 'pkey' is expected for each row
+     * @param Hash $aParams Hash array. Extra parameters for the table. Entry 'class' holds the class of the objects listed in the table
+     * @return void
+     */	  
+	public function table($aConfig, $aData, $aParams = array())
+	{
+		// WARNING WARNING WARNING
+		// This whole function is actually a copy paste from iTopWebPage::table
+		$oAppContext = new ApplicationContext();
+		
+		static $iNbTables = 0;
+		$iNbTables++;
+		$sHtml = "";
+		$sHtml .= "<table class=\"listResults\">\n";
+		$sHtml .= "<thead>\n";
+		$sHtml .= "<tr>\n";
+		foreach($aConfig as $sName=>$aDef)
+		{
+			$sHtml .= "<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n";
+		}
+		$sHtml .= "</tr>\n";
+		$sHtml .= "</thead>\n";
+		$sHtml .= "<tbody>\n";
+		foreach($aData as $aRow)
+		{
+			if (false) //(isset($aParams['preview']) && $aParams['preview'])
+			{
+				$sHtml .= "<tr id=\"Row_".$iNbTables."_".$aRow['key']."\" onClick=\"DisplayPreview(".$iNbTables.",".$aRow['key'].",'".$aParams['class']."')\">\n";
+			}
+			else if (isset($aRow['key']))
+			{
+				$sHtml .= "<tr onDblClick=\"DisplayDetails(".$aRow['key'].",'".$aParams['class']."')\">\n";
+			}
+			else
+			{
+				$sHtml .= "<tr>\n";
+			}
+			foreach($aConfig as $sName=>$aVoid)
+			{
+				if ($sName != 'key')
+				{
+					$sValue = empty($aRow[$sName]) ? '&nbsp;' : $aRow[$sName];
+					$sHtml .= "<td>$sValue</td>\n";
+				}
+				else
+				{
+					$sUIPage = cmdbAbstractObject::ComputeUIPage($aParams['class']);
+					$sHtml .= "<td><a class=\"no-arrow\" href=\"$sUIPage?operation=details&id=".$aRow['key']."&class=".$aParams['class']."&".$oAppContext->GetForLink()."\"><img src=\"../images/zoom.gif\" title=\"Details\" border=\"0\"></a></td>\n";
+				}
+			}
+			$sHtml .= "</tr>\n";
+		}
+		$sHtml .= "</tbody>\n";
+		$sHtml .= "</table>\n";
+		if (isset($aParams['preview']) && $aParams['preview'])
+		{
+			$sHtml .= "<div class=\"PreviewPane\" id=\"PreviewPane_".$iNbTables."\" style=\"height:100px;border:1px solid black;margin-top:2px;padding:3px;text-align:left;display:none;\">Preview Pane</div>";
+		}
+		$this->add($sHtml);	
+	}
+	/**
+	 * Adds a script to be executed when the DOM is ready (typical JQuery use)
+	 * NOT implemented in this version of the class.
+	 * @return void	 
+	 */	 	 	
+	public function add_ready_script($sScript)
+	{
+		// Does nothing in ajax rendered content.. for now...
+		// Maybe we should add this as a simple <script> tag at the end of the output
+		// considering that at this time everything in the page is "ready"...
+		$this->m_sReadyScript .= $sScript;
+	}
+}
+
+?>

+ 12 - 0
application/application.inc.php

@@ -0,0 +1,12 @@
+<?php
+// Includes all the classes to have the application up and running
+require_once('../application/applicationcontext.class.inc.php');
+require_once('../application/usercontext.class.inc.php');
+require_once('../application/cmdbabstract.class.inc.php');
+require_once('../application/displayblock.class.inc.php');
+require_once('../application/iotask.class.inc.php');
+require_once('../application/audit.category.class.inc.php');
+require_once('../application/audit.rule.class.inc.php');
+//require_once('../application/menunode.class.inc.php');
+require_once('../application/utils.inc.php');
+?>

+ 81 - 0
application/applicationcontext.class.inc.php

@@ -0,0 +1,81 @@
+<?php
+require_once("../application/utils.inc.php");
+/**
+ * Helper class to store and manipulate the parameters that make the application's context
+ *
+ * Usage:
+ * 1) Build the application's context by constructing the object
+ *   (the object will read some of the page's parameters)
+ *
+ * 2) Add these parameters to hyperlinks or to forms using the helper, functions
+ *    GetForLink(), GetForForm() or GetAsHash()
+ */
+class ApplicationContext
+{
+	protected $aNames;
+	protected $aValues;
+	
+	public function __construct()
+	{
+		$this->aNames = array(
+			'org_id', 'menu'
+		);
+		$this->ReadContext();
+	}
+	
+	/**
+	 * Read the context directly in the PHP parameters (either POST or GET)
+	 * return nothing
+	 */
+	protected function ReadContext()
+	{
+		$this->aValues = array();
+		foreach($this->aNames as $sName)
+		{
+			$sValue = utils::ReadParam($sName, '');
+			// TO DO: check if some of the context parameters are mandatory (or have default values)
+			if (!empty($sValue))
+			{
+				$this->aValues[$sName] = $sValue;
+			}
+		}
+	}
+	
+	/**
+	 * Returns the context as string with the format name1=value1&name2=value2....
+	 * return string The context as a string to be appended to an href property
+	 */
+	public function GetForLink()
+	{
+		$aParams = array();
+		foreach($this->aValues as $sName => $sValue)
+		{
+			$aParams[] = $sName.'='.urlencode($sValue);
+		}
+		return implode("&", $aParams);
+	}
+	
+	/**
+	 * Returns the context as sequence of input tags to be inserted inside a <form> tag
+	 * return string The context as a sequence of <input type="hidden" /> tags
+	 */
+	public function GetForForm()
+	{
+		$sContext = "";
+		foreach($this->aValues as $sName => $sValue)
+		{
+			$sContext .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\" />\n";
+		}
+		return $sContext;
+	}
+
+	/**
+	 * Returns the context as a hash array 'parameter_name' => value
+	 * return array The context information
+	 */
+	public function GetAsHash()
+	{
+		return $this->aValues;
+	}
+}
+?>

+ 45 - 0
application/audit.category.class.inc.php

@@ -0,0 +1,45 @@
+<?php
+require_once('../application/cmdbabstract.class.inc.php');
+
+/**
+ * This class manages the audit "categories". Each category defines a set of objects
+ * to check and is linked to a set of rules that determine the valid or invalid objects
+ * inside the set 
+ */
+class AuditCategory extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "application",
+			"name" => "AuditCategory",
+			"description" => "A section inside the overall audit",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array('name'),
+			"db_table" => "priv_auditcategory",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../application/templates/audit_category.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Category Name", "description"=>"Short name for this category", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Audit Category Description", "description"=>"Long description for this audit category", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("definition_set", array("label"=>"Definition Set", "description"=>"SibusQL expression defining the set of objects to audit", "allowed_values"=>null, "sql"=>"definition_set", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("description");
+		MetaModel::Init_AddFilterFromAttribute("definition_set");
+
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'definition_set')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'description', )); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'description')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'definition_set')); // Criteria of the advanced search form
+	}
+}
+?>

+ 52 - 0
application/audit.rule.class.inc.php

@@ -0,0 +1,52 @@
+<?php
+require_once('../application/audit.category.class.inc.php');
+
+/**
+ * This class manages the audit "rule" linked to a given audit category.
+ * Each rule is based ona SibusQL expression that returns either the "good" objects
+ * or the "bad" ones. The core audit engines computes the complement to the definition
+ * set when needed to obtain either the valid objects, or the ones with an error
+ */
+class AuditRule extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "application",
+			"name" => "AuditRule",
+			"description" => "A rule to check for a given Audit category",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array('name'),
+			"db_table" => "priv_auditrule",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Rule Name", "description"=>"Short name for this rule", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Audit Rule Description", "description"=>"Long description for this audit rule", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("query", array("label"=>"Query to Run", "description"=>"The SibusQL expression to run", "allowed_values"=>null, "sql"=>"query", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("valid_flag", array("label"=>"Valid objects?", "description"=>"True if the rule returns the valid objects, false otherwise", "allowed_values"=>new ValueSetEnum('true,false'), "sql"=>"valid_flag", "default_value"=>"true", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("category_id", array("label"=>"Category", "description"=>"The category for this rule", "allowed_values"=>null, "sql"=>"category_id", "targetclass"=>"AuditCategory", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("category_name", array("label"=>"Category", "description"=>"Name of the category for this rule", "allowed_values"=>null, "extkey_attcode"=> 'category_id', "target_attcode"=>"name")));
+
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("description");
+		MetaModel::Init_AddFilterFromAttribute("query");
+		MetaModel::Init_AddFilterFromAttribute("valid_flag");
+		MetaModel::Init_AddFilterFromAttribute("category_id");
+		MetaModel::Init_AddFilterFromAttribute("category_name");
+
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('category_id', 'name', 'description', 'query', 'valid_flag')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('category_id', 'name', 'description', 'valid_flag')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('category_id', 'name', 'description', 'valid_flag')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('category_id', 'name', 'description', 'valid_flag', 'query')); // Criteria of the advanced search form
+	}
+}
+?>

+ 647 - 0
application/cmdbabstract.class.inc.php

@@ -0,0 +1,647 @@
+<?php
+require_once('../core/cmdbobject.class.inc.php');
+require_once('../application/utils.inc.php');
+require_once('../application/applicationcontext.class.inc.php');
+require_once('../application/ui.linkswidget.class.inc.php');
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Abstract class that implements some common and useful methods for displaying
+* the objects
+*/
+////////////////////////////////////////////////////////////////////////////////////
+abstract class cmdbAbstractObject extends CMDBObject
+{
+	
+	public static function GetUIPage()
+	{
+		return './UI.php';
+	}
+	
+	public static function ComputeUIPage($sClass)
+	{
+		static $aUIPagesCache = array(); // Cache to store the php page used to display each class of object
+		if (!isset($aUIPagesCache[$sClass]))
+		{
+			$UIPage = false;
+			if (is_callable("$sClass::GetUIPage"))
+			{
+				$UIPage = eval("return $sClass::GetUIPage();"); // May return false in case of error
+			}
+			$aUIPagesCache[$sClass] = $UIPage === false ? './UI.php' : $UIPage;
+		}
+		$sPage = $aUIPagesCache[$sClass];
+		return $sPage;
+	}
+
+	protected static function MakeHyperLink($sObjClass, $sObjKey, $aAvailableFields)
+	{
+		$oAppContext = new ApplicationContext();	
+		$sExtClassNameAtt = MetaModel::GetNameAttributeCode($sObjClass);
+		$sPage = self::ComputeUIPage($sObjClass);
+		// Use the "name" of the target class as the label of the hyperlink
+		// unless it's not available in the external attributes...
+		if (isset($aAvailableFields[$sExtClassNameAtt]))
+		{
+			$sLabel = $aAvailableFields[$sExtClassNameAtt];
+		}
+		else
+		{
+			$sLabel = implode(' / ', $aAvailableFields);
+		}
+		$sHint = htmlentities("$sObjClass::$sObjKey");
+		return "<a href=\"$sPage?operation=details&class=$sObjClass&id=$sObjKey&".$oAppContext->GetForLink()."\" title=\"$sHint\">$sLabel</a>";
+	}
+
+	public function GetDisplayValue($sAttCode)
+	{
+		$sDisplayValue = "";
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		if ($sStateAttCode == $sAttCode)
+		{
+			$aStates = MetaModel::EnumStates(get_class($this));
+			$sDisplayValue = $aStates[$this->Get($sAttCode)]['label'];
+		}
+		else
+		{
+			$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+			
+			if ($oAtt->IsExternalKey())
+			{
+				// retrieve the "external fields" linked to this external key
+				$sTargetClass = $oAtt->GetTargetClass();
+				$aAvailableFields = array();
+				foreach (MetaModel::GetExternalFields(get_class($this), $sAttCode) as $oExtField)
+				{
+					$aAvailableFields[$oExtField->GetExtAttCode()] = $oExtField->GetAsHTML($this->Get($oExtField->GetCode()));
+				}
+				$sExtClassNameAtt = MetaModel::GetNameAttributeCode($sTargetClass);
+				// Use the "name" of the target class as the label of the hyperlink
+				// unless it's not available in the external fields...
+				if (isset($aAvailableFields[$sExtClassNameAtt]))
+				{
+					$sDisplayValue = $aAvailableFields[$sExtClassNameAtt];
+				}
+				else
+				{
+					$sDisplayValue = implode(' / ', $aAvailableFields);
+				}
+			}
+			else
+			{
+				$sDisplayValue = $this->GetAsHTML($sAttCode);
+			}
+		}
+		return $sDisplayValue;
+	}
+
+	function DisplayBareDetails(web_page $oPage)
+	{
+		$oPage->add($this->GetBareDetails($oPage));		
+	}
+
+	function GetDisplayName()
+	{
+		return $this->GetAsHTML(MetaModel::GetNameAttributeCode(get_class($this)));
+	}
+
+	function GetBareDetails(web_page $oPage)
+	{
+		$sHtml = '';
+		$oAppContext = new ApplicationContext();	
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		$aDetails = array();
+		$sClass = get_class($this);
+		$aList = MetaModel::GetZListItems($sClass, 'details');
+
+		foreach($aList as $sAttCode)
+		{
+			$iFlags = $this->GetAttributeFlags($sAttCode);
+			if ( ($iFlags & OPT_ATT_HIDDEN) == 0)
+			{
+				// The field is visible in the current state of the object
+				if ($sStateAttCode == $sAttCode)
+				{
+					// Special display for the 'state' attribute itself
+					$sDisplayValue = $this->GetState();
+				}
+				else
+				{
+					$sDisplayValue = $this->GetAsHTML($sAttCode);
+				}
+				$aDetails[] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'value' => $sDisplayValue);
+			}
+		}
+		$sHtml .= $oPage->GetDetails($aDetails);
+		return $sHtml;		
+	}
+
+	
+	function DisplayDetails(web_page $oPage)
+	{
+		$sTemplate = Utils::ReadFromFile(MetaModel::GetDisplayTemplate(get_class($this)));
+		if (!empty($sTemplate))
+		{
+			$oTemplate = new DisplayTemplate($sTemplate);
+			$oTemplate->Render($oPage, array('class'=> get_class($this),'pkey'=> $this->GetKey(), 'name' => $this->GetName()));
+		}
+		else
+		{
+			// Standard Header with name, actions menu and history block
+			$oPage->add("<div class=\"page_header\">\n");
+			$oSingletonFilter = new DBObjectSearch(get_class($this));
+			$oSingletonFilter->AddCondition('pkey', array($this->GetKey()));
+			$oBlock = new MenuBlock($oSingletonFilter, 'popup', false);
+			$oBlock->Display($oPage, -1);
+			$oPage->add("<h1>".Metamodel::GetName(get_class($this)).": <span class=\"hilite\">".$this->GetDisplayName()."</span></h1>\n");
+			$oHistoryFilter = new DBObjectSearch('CMDBChangeOpSetAttribute');
+			$oHistoryFilter->AddCondition('objkey', $this->GetKey());
+			$oBlock = new HistoryBlock($oHistoryFilter, 'toggle', false);
+			$oBlock->Display($oPage, -1);
+			$oPage->add("</div>\n");
+			
+			// Object's details
+			// template not found display the object using the *old style*
+			self::DisplayBareDetails($oPage);
+			
+			// Related objects
+			$oPage->AddTabContainer('Related Objects');
+			$oPage->SetCurrentTabContainer('Related Objects');
+			foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+			{
+				if ((get_class($oAttDef) == 'AttributeLinkedSetIndirect') || (get_class($oAttDef) == 'AttributeLinkedSet'))
+				{
+					$oPage->SetCurrentTab($oAttDef->GetLabel());
+					$oPage->p($oAttDef->GetDescription());
+					
+					if (get_class($oAttDef) == 'AttributeLinkedSet')
+					{
+						$sTargetClass = $oAttDef->GetLinkedClass();
+						$oFilter = new DBObjectSearch($sTargetClass);
+						$oFilter->AddCondition($oAttDef->GetExtKeyToMe(), $this->GetKey()); // @@@ condition has same name as field ??
+
+						$oBlock = new DisplayBlock($oFilter, 'list', false);
+						$oBlock->Display($oPage, 0);
+					}
+					else // get_class($oAttDef) == 'AttributeLinkedSetIndirect'
+					{
+						$sLinkClass = $oAttDef->GetLinkedClass();
+						// Transform the DBObjectSet into a CMBDObjectSet !!!
+						$aLinkedObjects = $this->Get($sAttCode)->ToArray(false);
+						if (count($aLinkedObjects) > 0)
+						{
+							$oSet = CMDBObjectSet::FromArray($sLinkClass, $aLinkedObjects);
+							$this->DisplaySet($oPage, $oSet, $oAttDef->GetExtKeyToMe());
+						}
+					}					
+				}
+			}
+			$oPage->SetCurrentTab('');
+		}
+	}
+	
+	function DisplayPreview(web_page $oPage)
+	{
+		$aDetails = array();
+		$sClass = get_class($this);
+		$aList = MetaModel::GetZListItems($sClass, 'preview');
+		foreach($aList as $sAttCode)
+		{
+			$aDetails[] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'value' =>$this->GetAsHTML($sAttCode));
+		}
+		$oPage->details($aDetails);		
+	}
+	
+	// Comment by Rom: this helper may be used to display objects of class DBObject
+	//                 -> I am using this to display the changes history 
+	public static function DisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '')
+	{
+		$oPage->add(self::GetDisplaySet($oPage, $oSet, $sLinkageAttribute));
+	}
+	
+	public static function GetDisplaySet(web_page $oPage, CMDBObjectSet $oSet, $sLinkageAttribute = '', $bDisplayMenu = true)
+	{
+		$sHtml = '';
+		$oAppContext = new ApplicationContext();
+		$sClassName = $oSet->GetFilter()->GetClass();
+		$aAttribs = array();
+		$aList = MetaModel::GetZListItems($sClassName, 'list');
+		if (!empty($sLinkageAttribute))
+		{
+			// The set to display is in fact a set of links between the object specified in the $sLinkageAttribute
+			// and other objects...
+			// The display will then group all the attributes related to the link itself:
+			// | Link_attr1 | link_attr2 | ... || Object_attr1 | Object_attr2 | Object_attr3 | .. | Object_attr_n |
+			$aAttDefs = MetaModel::ListAttributeDefs($sClassName);
+			assert(isset($aAttDefs[$sLinkageAttribute]));
+			$oAttDef = $aAttDefs[$sLinkageAttribute];
+			assert($oAttDef->IsExternalKey());
+			// First display all the attributes specific to the link record
+			foreach($aList as $sLinkAttCode)
+			{
+				$oLinkAttDef = $aAttDefs[$sLinkAttCode];
+				if ( (!$oLinkAttDef->IsExternalKey()) && (!$oLinkAttDef->IsExternalField()) )
+				{
+					$aDisplayList[] = $sLinkAttCode;
+				}
+			}
+			// Then display all the attributes neither specific to the link record nor to the 'linkage' object (because the latter are constant)
+			foreach($aList as $sLinkAttCode)
+			{
+				$oLinkAttDef = $aAttDefs[$sLinkAttCode];
+				if (($oLinkAttDef->IsExternalKey() && ($sLinkAttCode != $sLinkageAttribute))
+					|| ($oLinkAttDef->IsExternalField() && ($oLinkAttDef->GetKeyAttCode()!=$sLinkageAttribute)) )
+				{
+					$aDisplayList[] = $sLinkAttCode;
+				}
+			}
+			// First display all the attributes specific to the link
+			// Then display all the attributes linked to the other end of the relationship
+			$aList = $aDisplayList;
+		}
+		foreach($aList as $sAttCode)
+		{
+			$aAttribs['key'] = array('label' => '', 'description' => 'Click to display');
+			$aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode));
+		}
+		$aValues = array();
+		$oSet->Seek(0);
+		while ($oObj = $oSet->Fetch())
+		{
+			$aRow['key'] = $oObj->GetKey();
+			foreach($aList as $sAttCode)
+			{
+				$aRow[$sAttCode] = $oObj->GetAsHTML($sAttCode);
+			}
+			$aValues[] = $aRow;
+		}
+		$oMenuBlock = new MenuBlock($oSet->GetFilter());
+		$sHtml .= '<table class="listContainer">';
+		if ($bDisplayMenu)
+		{
+			$sHtml .= '<tr class="containerHeader"><td>';
+			$sHtml .= $oMenuBlock->GetRenderContent($oPage, $sLinkageAttribute);
+			$sHtml .= '</td></tr>';
+		}
+		$sHtml .= '<tr><td>';
+		$sHtml .= $oPage->GetTable($aAttribs, $aValues, array('class'=>$sClassName, 'filter'=>$oSet->GetFilter()->serialize(), 'preview' => true));
+		$sHtml .= '</td></tr>';
+		$sHtml .= '</table>';
+		return $sHtml;
+	}
+	
+	static function DisplaySetAsCSV(web_page $oPage, CMDBObjectSet $oSet, $aParams = array())
+	{
+		$oPage->add(self::GetSetAsCSV($oSet, $aParams));
+	}
+	
+	static function GetSetAsCSV(DBObjectSet $oSet, $aParams = array())
+	{
+		$sSeparator = isset($aParams['separator']) ? $aParams['separator'] : ','; // default separator is comma
+		$sTextQualifier = isset($aParams['text_qualifier']) ? $aParams['text_qualifier'] : '"'; // default text qualifier is double quote
+
+		$oAppContext = new ApplicationContext();
+		$sClassName = $oSet->GetFilter()->GetClass();
+		$aAttribs = array();
+		$aList = MetaModel::GetZListItems($sClassName, 'details');
+		$aHeader = array();
+		$aHeader[] = MetaModel::GetKeyLabel($sClassName);
+		foreach($aList as $sAttCode)
+		{
+			$aHeader[] = MetaModel::GetLabel($sClassName, $sAttCode);
+		}
+		$sHtml = '#'.$oSet->GetFilter()->ToOQL()."\n";
+		$sHtml .= implode($sSeparator, $aHeader)."\n";
+		$oSet->Seek(0);
+		while ($oObj = $oSet->Fetch())
+		{
+			$aRow = array();
+			$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);
+				}
+			}
+			$sHtml .= implode($sSeparator, $aRow)."\n";
+		}
+		
+		return $sHtml;
+	}
+	
+	static function DisplaySetAsXML(web_page $oPage, CMDBObjectSet $oSet, $aParams = array())
+	{
+		$oAppContext = new ApplicationContext();
+		$sClassName = $oSet->GetFilter()->GetClass();
+		$aAttribs = array();
+		$aList = MetaModel::GetZListItems($sClassName, 'details');
+		$oPage->add("<Set>\n");
+		$oSet->Seek(0);
+		while ($oObj = $oSet->Fetch())
+		{
+			$oPage->add("<$sClassName id=\"".$oObj->GetKey()."\">\n");
+			foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode=>$oAttDef)
+			{
+				if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()) && ($sAttCode != 'finalclass') )
+				{
+					$sValue = $oObj->GetAsXML($sAttCode);
+					$oPage->add("<$sAttCode>$sValue</$sAttCode>\n");
+				}
+			}
+			$oPage->add("</$sClassName>\n");
+		}
+		$oPage->add("</Set>\n");
+	}
+
+	// By rom
+	function DisplayChangesLog(web_page $oPage)
+	{
+		$oFltChangeOps = new CMDBSearchFilter('CMDBChangeOpSetAttribute');
+		$oFltChangeOps->AddCondition('objkey', $this->GetKey(), '=');
+		$oFltChangeOps->AddCondition('objclass', get_class($this), '=');
+		$oSet = new CMDBObjectSet($oFltChangeOps, array('date' => false)); // order by date descending (i.e. false)
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->p("Changes log ($count):");
+			self::DisplaySet($oPage, $oSet);
+		}
+		else
+		{
+			$oPage->p("Changes log is empty");
+		}
+	}
+	
+	public static function DisplaySearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
+	{
+
+		$oPage->add(self::GetSearchForm($oPage, $oSet, $aExtraParams));
+	}
+	
+	public static function GetSearchForm(web_page $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
+	{
+		$sHtml = '';
+		$numCols=4;
+		$sClassName = $oSet->GetFilter()->GetClass();
+		$oUnlimitedFilter = new DBObjectSearch($sClassName);
+		$sHtml .= "<form>\n";
+		$index = 0;
+		$sHtml .= "<table>\n";
+		$aFilterCriteria = $oSet->GetFilter()->GetCriteria();
+		$aMapCriteria = array();
+		foreach($aFilterCriteria as $aCriteria)
+		{
+			$aMapCriteria[$aCriteria['filtercode']][] = array('value' => $aCriteria['value'], 'opcode' => $aCriteria['opcode']);
+		}
+		$aList = MetaModel::GetZListItems($sClassName, 'standard_search');
+		foreach($aList as $sFilterCode)
+		{
+			if (($index % $numCols) == 0)
+			{
+				if ($index != 0)
+				{
+					$sHtml .= "</tr>\n";
+				}
+				$sHtml .= "<tr>\n";
+			}
+			$sFilterValue = '';
+			$sFilterValue = utils::ReadParam($sFilterCode, '');
+			$sFilterOpCode = null; // Use the default 'loose' OpCode
+			if (empty($sFilterValue))
+			{
+				if (isset($aMapCriteria[$sFilterCode]))
+				{
+					if (count($aMapCriteria[$sFilterCode]) > 1)
+					{
+						$sFilterValue = '* mixed *';
+					}
+					else
+					{
+						$sFilterValue = $aMapCriteria[$sFilterCode][0]['value'];
+						$sFilterOpCode = $aMapCriteria[$sFilterCode][0]['opcode'];
+					}
+					if ($sFilterCode != 'company')
+					{
+						$oUnlimitedFilter->AddCondition($sFilterCode, $sFilterValue, $sFilterOpCode);
+					}
+				}
+			}
+            $aAllowedValues = MetaModel::GetAllowedValues_flt($sClassName, $sFilterCode, array(), '');
+            if ($aAllowedValues != null)
+            {
+                //Enum field or external key, display a combo
+            	$sValue = "<select name=\"$sFilterCode\">\n";
+            	$sValue .= "<option value=\"\">* Any *</option>\n";
+            	foreach($aAllowedValues as $key => $value)
+            	{
+            		if ($sFilterValue == $key)
+            		{
+            			$sSelected = ' selected';
+            		}
+            		else
+            		{
+            			$sSelected = '';
+            		}
+            		$sValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
+            	}
+            	$sValue .= "</select>\n";
+		        $sHtml .= "<td><label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label></td><td>$sValue</td>\n";
+            }
+            else
+            {
+                // Any value is possible, display an input box
+		        $sHtml .= "<td><label>".MetaModel::GetFilterLabel($sClassName, $sFilterCode).":</label></td><td><input class=\"textSearch\" name=\"$sFilterCode\" value=\"$sFilterValue\"/></td>\n";
+            }
+			$index++;
+		}
+		if (($index % $numCols) != 0)
+		{
+			$sHtml .= "<td colspan=\"".(2*($numCols - ($index % $numCols)))."\"></td>\n";
+		}
+		$sHtml .= "</tr>\n";
+		$sHtml .= "<tr><td colspan=\"".(2*$numCols)."\" align=\"right\"><input type=\"submit\" value=\" Search \"></td></tr>\n";
+		$sHtml .= "</table>\n";
+		foreach($aExtraParams as $sName => $sValue)
+		{
+			$sHtml .= "<input type=\"hidden\" name=\"$sName\" value=\"$sValue\">\n";
+		}
+		$sHtml .= "<input type=\"hidden\" name=\"dosearch\" value=\"1\">\n";
+		$sHtml .= "</form>\n";
+		// Soem Debug dumps...
+		//$sHtml .= "<tt>".$oSet->GetFilter()->__DescribeHTML()."</tt><br/>\n";
+		//$sHtml .= "<tt>encoding=\"text/serialize\" : ".$oSet->GetFilter()->serialize()."</tt><br/>\n";
+		//$sHtml .= "<tt>encoding=\"text/sibusql\" : ".$oSet->GetFilter()->ToSibusQL()."</tt><br/>\n";
+		//$sHtml .= "<tt>(Unlimited) ".$oUnlimitedFilter->__DescribeHTML()."</tt><br/>\n";
+		//$sHtml .= "<tt>encoding=\"text/serialize\" : ".$oUnlimitedFilter->serialize()."</tt><br/>\n";
+		//$sHtml .= "<tt>encoding=\"text/sibusql\" : ".$oUnlimitedFilter->ToSibusQL()."</tt>\n";
+		return $sHtml;
+	}
+	
+	public static function GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $value = '', $sDisplayValue = '', $iId = '')
+	{
+		static $iInputId = 0;
+		if (!empty($iId))
+		{
+			$iInputId = $iId;
+		}
+		else
+		{
+			$iInputId++;
+		}
+		if (!$oAttDef->IsExternalField())
+		{
+			switch($oAttDef->GetEditClass())
+			{
+				case 'Date':
+				$sHTMLValue = "<input type=\"text\" size=\"20\" name=\"attr_$sAttCode\" value=\"$value\" id=\"$iInputId\" class=\"date-pick\"/>";
+				break;
+				
+				case 'Text':
+					$sHTMLValue = "<textarea name=\"attr_$sAttCode\" rows=\"8\" cols=\"40\" id=\"$iInputId\">$value</textarea>";
+				break;
+	
+				case 'List':
+					$oWidget = new UILinksWidget($sClass, $sAttCode, $iInputId);
+					$sHTMLValue = $oWidget->Display($oPage, $value);
+				break;
+							
+				case 'String':
+				default:
+			    $aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array(), '');
+				if ($aAllowedValues !== null)
+				{
+					//Enum field or external key, display a combo
+					if (count($aAllowedValues) == 0)
+					{
+						$sHTMLValue = "<input type=\"text\" size=\"70\" value=\"\" name=\"attr_$sAttCode\"  id=\"$iInputId\"/>";
+					}
+					else if (count($aAllowedValues) > 20)
+					{
+						// too many choices, use an autocomplete
+						// The input for the auto complete
+						$sHTMLValue = "<input type=\"text\" id=\"$iInputId\" size=\"50\" name=\"\" value=\"$sDisplayValue\" />";
+						// another hidden input to store & pass the object's Id
+						$sHTMLValue .= "<input type=\"hidden\" id=\"id_ac_$iInputId\" name=\"attr_$sAttCode\" value=\"$value\" />\n";
+						$oPage->add_ready_script("\$('#$iInputId').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_$iInputId', extraParams:{operation:'autocomplete', sclass:'$sClass',attCode:'".$sAttCode."'}});");
+					}
+					else
+					{
+						// Few choices, use a normal 'select'
+						$sHTMLValue = "<select name=\"attr_$sAttCode\"  id=\"$iInputId\">\n";
+						foreach($aAllowedValues as $key => $display_value)
+						{
+							$sSelected = ($value == $key) ? ' selected' : '';
+							$sHTMLValue .= "<option value=\"$key\"$sSelected>$display_value</option>\n";
+						}
+						$sHTMLValue .= "</select>\n";
+					}
+				}
+				else
+				{
+					$sHTMLValue = "<input type=\"text\" size=\"50\" name=\"attr_$sAttCode\" value=\"$value\" id=\"$iInputId\">";
+				}
+			}
+		}
+		return $sHTMLValue;
+	}
+	
+	public function DisplayModifyForm(web_page $oPage)
+	{
+		$oAppContext = new ApplicationContext();
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		$iKey = $this->GetKey();
+		$aDetails = array();
+		$oPage->add("<form method=\"post\">\n");
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if ('finalclass' == $sAttCode) // finalclass is a reserved word, hardcoded !
+			{
+				// Do nothing, the class field is always hidden, it cannot be edited
+			}
+			else if ($sStateAttCode == $sAttCode)
+			{
+				// State attribute is always read-only from the UI
+				$sHTMLValue = $this->GetState();
+				$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+			}
+			else if (!$oAttDef->IsExternalField())
+			{
+				$iFlags = $this->GetAttributeFlags($sAttCode);				
+				if ($iFlags & OPT_ATT_HIDDEN)
+				{
+					// Attribute is hidden, do nothing
+				}
+				else
+				{
+					if ($iFlags & OPT_ATT_READONLY)
+					{
+						// Attribute is read-only
+						$sHTMLValue = $this->GetAsHTML($sAttCode);
+					}
+					else
+					{
+						$sValue = $this->Get($sAttCode);
+						$sDisplayValue = $this->GetDisplayValue($sAttCode);
+						$sHTMLValue = self::GetFormElementForField($oPage, get_class($this), $sAttCode, $oAttDef, $sValue, $sDisplayValue);
+					}
+					$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+				}
+			}
+		}
+		$oPage->details($aDetails);
+		$oPage->add("<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"class\" value=\"".get_class($this)."\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"apply_modify\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n");
+		$oPage->add($oAppContext->GetForForm());
+		$oPage->add("<button type=\"button\" class=\"action\" onClick=\"goBack()\"><span>Cancel</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+		$oPage->add("<button type=\"submit\" class=\"action\"><span>Apply</span></button>\n");
+		$oPage->add("</form>\n");
+	}
+	
+	public static function DisplayCreationForm(web_page $oPage, $sClass, $oObjectToClone = null)
+	{
+		$oAppContext = new ApplicationContext();
+		$aDetails = array();
+		$sOperation = ($oObjectToClone == null) ? 'apply_new' : 'apply_clone';
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($oObjectToClone));
+		$oPage->add("<form method=\"post\">\n");
+		foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
+		{
+			if ('finalclass' == $sAttCode) // finalclass is a reserved word, hardcoded !
+			{
+				// Do nothing, the class field is always hidden, it cannot be edited
+			}
+			else if ($sStateAttCode == $sAttCode)
+			{
+				// State attribute is always read-only from the UI
+				$sHTMLValue = $oObjectToClone->GetState();
+				$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+			}
+			else if (!$oAttDef->IsExternalField())
+			{
+				$sValue = ($oObjectToClone == null) ? '' : $oObjectToClone->Get($sAttCode);
+				$sDisplayValue = ($oObjectToClone == null) ? '' : $oObjectToClone->GetDisplayValue($sAttCode);
+				$sHTMLValue = self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue);
+				$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+			}
+		}
+		$oPage->details($aDetails);
+		if ($oObjectToClone != null)
+		{
+			$oPage->add("<input type=\"hidden\" name=\"clone_id\" value=\"".$oObjectToClone->GetKey()."\">\n");
+		}
+		$oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"".utils::GetNewTransactionId()."\">\n");
+		$oPage->add($oAppContext->GetForForm());
+		$oPage->add("<button type=\"button\" class=\"action\" onClick=\"goBack()\"><span>Cancel</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
+		$oPage->add("<button type=\"submit\" class=\"action\"><span>Apply</span></button>\n");
+		$oPage->add("</form>\n");
+	}
+}
+?>

+ 35 - 0
application/csvpage.class.inc.php

@@ -0,0 +1,35 @@
+<?php
+require_once("../application/webpage.class.inc.php");
+/**
+ * Simple web page with no includes or fancy formatting, useful to generateXML documents
+ * The page adds the content-type text/XML and the encoding into the headers
+ */
+class CSVPage extends web_page
+{
+    function __construct($s_title)
+    {
+        parent::__construct($s_title);
+		$this->add_header("Content-type: text/html; charset=iso-8859-1");
+		$this->add_header("Cache-control: no-cache");
+    }	
+
+    public function output()
+    {
+		$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
+        foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        echo trim($this->s_content);
+    }
+
+    public function small_p($sText)
+    {
+	}
+	
+	public function table($aConfig, $aData, $aParams = array())
+	{
+	}
+}
+
+?>

+ 252 - 0
application/dialogstack.class.inc.php

@@ -0,0 +1,252 @@
+<?php
+/**
+ * Helper class to allow modal-style dialog box in an html form
+ *
+ * Possible improvement: do not use _SESSION for the caller's data,
+ *  instead set a member variable with caller information
+ *  and take the opportunity of the first edit button to place the information
+ *  into a hidden field   
+ *
+ * Usage:
+ */
+
+define('DLGSTACK_OK', 1);
+define('DLGSTACK_CANCEL', 2);
+
+//session_name("dialogstack");
+session_start();
+
+
+class dialogstack
+{
+	private static $m_bCurrPageDeclared = false;
+	/**
+	 * Declare the current page as being a dialog issuer, potentially pop...
+	 */
+	static public function DeclareCaller($sTitle)
+	{
+		self::$m_bCurrPageDeclared = false;
+		$_SESSION['dialogstack_calleruri'] = $_SERVER["REQUEST_URI"];
+		$_SESSION['dialogstack_callertitle'] = $sTitle;
+
+		if (isset($_POST["dialogstackpop"]) && ($_POST["dialogstackpop"] == count($_SESSION['dialogstack_currdlg'])))
+		{
+			// Pop !
+			array_pop($_SESSION['dialogstack_currdlg']);
+		} 
+	}
+
+	/**
+	 * True if the current page has been loaded from an "dialog startup button"
+	 */
+	static private function GetRetArgName()
+	{
+		foreach($_REQUEST as $sArgName=>$sArgValue)
+		{
+			if (strstr($sArgName, "dlgstack_go,"))
+			{
+				$aTokens = explode(",", $sArgName);
+				return self::ArgNameDecode($aTokens[1]);
+			}
+		}
+		return "";
+	}
+
+	/**
+	 * Protect against weird effects of PHP interpreting brackets...
+	 */
+	static private function ArgNameEncode($sArgName)
+	{
+		return str_replace(array('[', ']'), array('_bracket_open_', '_bracket_close_'), $sArgName);
+	}
+	static private function ArgNameDecode($sCodedArgName)
+	{
+		return str_replace(array('_bracket_open_', '_bracket_close_'), array('[', ']'), $sCodedArgName);
+	}
+
+	/**
+	 * True if the current page has been loaded from an "dialog startup button"
+	 */
+	static public function IsDialogStartup()
+	{
+		return (strlen(self::GetRetArgName()) > 0);
+	}
+
+
+	/**
+	 * Helper to  
+	 */
+	static private function RemoveArg(&$aValues, $sKey, &$retval = null)
+	{
+		if (isset($aValues[$sKey]))
+		{
+			if (empty($retval))
+			{
+				$retval = $aValues[$sKey];
+			}
+			unset($aValues[$sKey]);
+		}
+	}
+	
+
+	/**
+	 * Record current page args, and returns the initial value for the dialog 
+	 */
+	static public function StartDialog()
+	{
+		if (!isset($_SESSION['dialogstack_currdlg']))
+		{
+			// Init stack
+			$_SESSION['dialogstack_currdlg'] = array();
+		}
+
+		$sRetArgName = self::GetRetArgName();
+		$sCodedArgName = self::ArgNameEncode($sRetArgName);
+
+		$sArgForRetArgName = "dlgstack_init_".$sCodedArgName;
+		$sButtonName = "dlgstack_go,".$sCodedArgName;
+
+		// Do not record utility arguments, neither the current value (stored separately)
+		//
+		$initValue = null;
+		$aPost = $_POST;
+		self::RemoveArg($aPost, $sArgForRetArgName, $initValue);
+		self::RemoveArg($aPost, $sButtonName);
+		self::RemoveArg($aPost, 'dlgstack_onok_page', $sOnOKPage);
+		self::RemoveArg($aPost, 'dlgstack_onok_args', $aOnOKArgs);
+		$aGet = $_GET;
+		self::RemoveArg($aGet, $sArgForRetArgName, $initValue);
+		self::RemoveArg($aGet, $sButtonName);
+		self::RemoveArg($aGet, 'dlgstack_onok_page', $sOnOKPage);
+		self::RemoveArg($aGet, 'dlgstack_onok_args', $aOnOKArgs);
+
+		if (self::$m_bCurrPageDeclared)
+		{
+			throw new Exception("DeclareCaller() must not be called before StartDialog()");
+		}
+
+		$aCall = array(
+				"title"=>$_SESSION['dialogstack_callertitle'],
+				"uri"=>$_SESSION['dialogstack_calleruri'],
+				"post"=>$aPost,
+				"get"=>$aGet,
+				"retarg"=>$sRetArgName,
+				"initval"=>$initValue,
+		);
+		if (isset($sOnOKPage)) $aCall["onok_page"] = $sOnOKPage;
+		if (isset($aOnOKArgs)) $aCall["onok_args"] = $aOnOKArgs;
+
+		array_push($_SESSION['dialogstack_currdlg'], $aCall);
+		return $initValue;
+	}
+	/**
+	 * Render a button to launch a new dialog
+	 */
+	static public function RenderEditableField($sTitle, $sArgName, $sCurrValue, $bAddFieldValue, $sOnOKPage = "", $aOnOKArgs = array())
+	{
+		$sRet = "";
+		$sCodedArgName = self::ArgNameEncode($sArgName);
+		if ($bAddFieldValue)
+		{
+			$sRet .= "<input type=\"hidden\" name=\"$sArgName\" value=\"$sCurrValue\">\n";
+		}
+		$sRet .= "<input type=\"hidden\" name=\"dlgstack_init_$sCodedArgName\" value=\"$sCurrValue\">\n";
+		$sRet .= "<input type=\"submit\" name=\"dlgstack_go,$sCodedArgName\" value=\"$sTitle\">\n";
+		if (!empty($sOnOKPage))
+		{
+			$sRet .= "<input type=\"hidden\" name=\"dlgstack_onok_page\" value=\"$sCurrValue\">\n";
+		}
+		foreach($aOnOKArgs as $sArgName=>$value)
+		{
+			$sRet .= "<input type=\"hidden\" name=\"dlgstack_onok_args[$sArgName]\" value=\"$value\">\n";
+		}
+		return $sRet;
+	}
+	/**
+	 * Render a [set of] hidden field, from a value that may be an array
+	 */
+	static private function RenderHiddenField($sName, $value)
+	{
+		$sRet = "";
+		if (is_array($value))
+		{
+			foreach($value as $sKey=>$subvalue)
+			{
+				$sRet .= self::RenderHiddenField($sName.'['.$sKey.']', $subvalue);
+			}
+		}
+		else
+		{
+			$sRet .= "<input type=\"hidden\" name=\"$sName\" value=\"$value\">\n";
+		}
+		return $sRet;
+	}
+	/**
+	 * Render a form to end the current dialog and return to the caller
+	 */
+	static public function RenderEndDialogForm($iButtonStyle, $sTitle, $sRetValue = null)
+	{
+		$aCall = end($_SESSION['dialogstack_currdlg']);
+		if (!$aCall) return;
+		return self::privRenderEndDialogForm($aCall, $iButtonStyle, $sTitle, $sRetValue);
+	}
+
+	/**
+	 * Returns an array of buttons to get back to upper dialog levels
+	 */
+	static public function GetCurrentStack()
+	{
+		$aRet = array();
+		if (isset($_SESSION['dialogstack_currdlg']))
+		{
+			foreach ($_SESSION['dialogstack_currdlg'] as $aCall)
+			{
+				$aRet[] = self::privRenderEndDialogForm($aCall, DLGSTACK_CANCEL, $aCall["title"]);
+			}
+		}
+		return $aRet;
+	}
+
+	/**
+	 * Render a form to end the current dialog and return to the caller
+	 */
+	static private function privRenderEndDialogForm($aCall, $iButtonStyle, $sTitle, $sRetValue = null)
+	{
+		if (($iButtonStyle == DLGSTACK_OK) && isset($aCall["onok_page"])) $sFormAction = $aCall["onok_page"];
+		else                                                              $sFormAction = $aCall["uri"];  
+
+		$sRet = "<form method=\"post\" action=\"$sFormAction\">\n";
+		foreach ($aCall["post"] as $sName=>$value)
+		{
+			$sRet .= self::RenderHiddenField($sName, $value);
+		}
+		if ($iButtonStyle == DLGSTACK_OK)
+		{
+			if (isset($aCall["onok_args"]))
+			{
+				foreach($aCall["onok_args"] as $sArgName=>$value)
+				{
+					$sRet .= "<input type=\"hidden\" name=\"$sArgName\" value=\"$value\">\n";
+				}
+			}
+			$sRet .= "<input type=\"hidden\" name=\"".$aCall["retarg"]."\" value=\"$sRetValue\">\n";
+			$sRet .= "<input type=\"submit\" name=\"dlgstackOK\" value=\"$sTitle, (OK) Back to ".$aCall["title"]."\">\n";
+		}
+		elseif ($iButtonStyle == DLGSTACK_CANCEL)
+		{
+			if (!is_null($aCall["initval"]))
+			{
+				$sRet .= "<input type=\"hidden\" name=\"".$aCall["retarg"]."\" value=\"".$aCall["initval"]."\">\n";
+			}
+			$sRet .= "<input type=\"submit\" name=\"dlgstackCANCEL\" value=\"$sTitle\">\n";
+		}
+		else
+		{
+			throw new Exception("Wrong value for button style ($iButtonStyle)");		
+		}
+		$sRet .= "<input type=\"hidden\" name=\"dialogstackpop\" value=\"".count($_SESSION['dialogstack_currdlg'])."\">\n";
+		$sRet .= "</form>\n";
+		return $sRet;
+	}
+}
+?>

+ 686 - 0
application/displayblock.class.inc.php

@@ -0,0 +1,686 @@
+<?php
+require_once('../application/webpage.class.inc.php');
+require_once('../application/utils.inc.php');
+require_once('../core/userrights.class.inc.php');
+/**
+ * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects
+ *
+ * Each block is actually rendered as a <div></div> tag that can be rendered synchronously
+ * or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php).
+ * The list of cmdbObjects to be displayed into the block is defined by a filter
+ * Right now the type of display is either: list, count, bare_details, details, csv, modify or search
+ * - list produces a table listing the objects
+ * - count produces a paragraphs with a sentence saying 'cont' objects found
+ * - bare_details displays just the details of the attributes of the object  (best if only one)
+ * - details display the full details of each object found using its template (best if only one)
+ * - csv displays a textarea with the CSV export of the list of objects 
+ * - modify displays the form to modify an object (best if only one)
+ * - search displays a search form with the criteria of the filter set
+ */
+class DisplayBlock
+{
+	const TAG_BLOCK = 'itopblock';
+	protected $m_oFilter;
+	protected $m_sStyle;
+	protected $m_bAsynchronous;
+	protected $m_aParams;
+	protected $m_oSet;
+	
+	public function __construct(DBObjectSearch $oFilter, $sStyle = 'list', $bAsynchronous = false, $aParams = array(), $oSet = null)
+	{
+		$this->m_oFilter = $oFilter;
+		$this->m_sStyle = $sStyle;
+		$this->m_bAsynchronous = $bAsynchronous;
+		$this->m_aParams = $aParams;
+		$this->m_oSet = $oSet;
+	}
+	/**
+	 * Constructs a DisplayBlock object from a DBObjectSet already in memory
+	 * @param $oSet DBObjectSet
+	 * @return DisplayBlock The DisplayBlock object, or null if the creation failed
+	 */
+	public static function FromObjectSet(DBObjectSet $oSet, $sStyle, $aParams = array())
+	{
+		$oDummyFilter = new DBObjectSearch($oSet->GetClass());
+		$oBlock = new DisplayBlock($oDummyFilter, $sStyle, false, $aParams, $oSet); // DisplayBlocks built this way are synchronous
+		return $oBlock;
+	}
+	
+	/**
+	 * Constructs a DisplayBlock object from an XML template
+	 * @param $sTemplate string The XML template
+	 * @return DisplayBlock The DisplayBlock object, or null if the template is invalid
+	 */
+	public static function FromTemplate($sTemplate)
+	{
+		$iStartPos = stripos($sTemplate, '<'.self::TAG_BLOCK.' ',0);
+		$iEndPos = stripos($sTemplate, '</'.self::TAG_BLOCK.'>', $iStartPos); 
+		
+		if (($iStartPos === false) || ($iEndPos === false)) return null; // invalid template		
+		$sITopBlock = substr($sTemplate,$iStartPos, $iEndPos-$iStartPos);
+		$sITopData = substr($sITopBlock, 1+stripos($sITopBlock, ">"));
+		$sITopTag = substr($sITopBlock, 0, stripos($sITopBlock, ">"));
+		$aMatches = array();
+		$sBlockClass = "DisplayBlock";
+		$bAsynchronous = false;
+		$sBlockType = 'list';
+		$sEncoding = 'text/serialize';
+		if (preg_match('/ type="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$sBlockType = strtolower($aMatches[1]);
+		}
+		if (preg_match('/ asynchronous="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$bAsynchronous = (strtolower($aMatches[1]) == 'true');
+		}
+		if (preg_match('/ blockclass="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$sBlockClass = $aMatches[1];
+		}
+		if (preg_match('/ objectclass="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$sObjectClass = $aMatches[1];
+		}
+		if (preg_match('/ encoding="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$sEncoding = strtolower($aMatches[1]);
+		}
+		if (preg_match('/ linkage="(.*)"/U',$sITopTag, $aMatches))
+		{
+			// The list to display is a list of links to the specified object
+			$sExtKey = strtolower($aMatches[1]);
+			$aParams['linkage'] = $sExtKey; // Name of the Ext. Key that make this linkage
+		}
+		// Parameters contains a list of extra parameters for the block
+		// the syntax is param_name1:value1;param_name2:value2;...
+		$aParams = array();
+		if (preg_match('/ parameters="(.*)"/U',$sITopTag, $aMatches))
+		{
+			$sParameters = $aMatches[1];
+			$aPairs = explode(';', $sParameters);
+			foreach($aPairs as $sPair)
+			{
+				if (preg_match('/(.*)\:(.*)/',$sPair, $aMatches))
+				{
+					$aParams[trim($aMatches[1])] = trim($aMatches[2]);
+				}
+			}
+		}
+		switch($sEncoding)
+		{
+			case 'text/serialize':
+			$oFilter = CMDBSearchFilter::unserialize($sITopData);
+			break;
+			
+			case 'text/sibusql':
+			$oFilter = CMDBSearchFilter::FromSibusQL($sITopData);
+			break;
+			
+			case 'text/oql':
+			$oFilter = CMDBSearchFilter::FromOQL($sITopData);
+			break;
+		}
+		return new $sBlockClass($oFilter, $sBlockType, $bAsynchronous, $aParams);		
+	}
+	
+	public function Display(web_page $oPage, $sId, $aExtraParams = array())
+	{
+		$aExtraParams = array_merge($aExtraParams, $this->m_aParams);
+		if (!$this->m_bAsynchronous)
+		{
+			// render now
+			$oPage->add("<div id=\"$sId\" class=\"display_block\">\n");
+			$this->RenderContent($oPage, $aExtraParams);
+			$oPage->add("</div>\n");
+		}
+		else
+		{
+			// render it as an Ajax (asynchronous) call
+			$sFilter = $this->m_oFilter->serialize();
+			$oPage->add("<div id=\"$sId\" class=\"display_block loading\">\n");
+			$oPage->p("<img src=\"../images/indicator_arrows.gif\"> Loading...");
+			$oPage->add("</div>\n");
+			$oPage->add('
+			<script language="javascript">
+			$.get("ajax.render.php?filter='.$sFilter.'&style='.$this->m_sStyle.'",
+			   { operation: "ajax" },
+			   function(data){
+				 $("#'.$sId.'").empty();
+				 $("#'.$sId.'").append(data);
+				 $("#'.$sId.'").removeClass("loading");
+				}
+			 );
+			 </script>'); // TO DO: add support for $aExtraParams in asynchronous/Ajax mode
+		}
+	}
+	
+	public function GetDisplay(web_page $oPage, $sId, $aExtraParams = array())
+	{
+		$sHtml = '';
+		$aExtraParams = array_merge($aExtraParams, $this->m_aParams);
+		if (!$this->m_bAsynchronous)
+		{
+			// render now
+			$sHtml .= "<div id=\"$sId\" class=\"display_block\">\n";
+			$sHtml .= $this->GetRenderContent($oPage, $aExtraParams);
+			$sHtml .= "</div>\n";
+		}
+		else
+		{
+			// render it as an Ajax (asynchronous) call
+			$sFilter = $this->m_oFilter->serialize();
+			$sHtml .= "<div id=\"$sId\" class=\"display_block loading\">\n";
+			$sHtml .= $oPage->GetP("<img src=\"../images/indicator_arrows.gif\"> Loading...");
+			$sHtml .= "</div>\n";
+			$sHtml .= '
+			<script language="javascript">
+			$.get("ajax.render.php?filter='.$sFilter.'&style='.$this->m_sStyle.'",
+			   { operation: "ajax" },
+			   function(data){
+				 $("#'.$sId.'").empty();
+				 $("#'.$sId.'").append(data);
+				 $("#'.$sId.'").removeClass("loading");
+				}
+			 );
+			 </script>'; // TO DO: add support for $aExtraParams in asynchronous/Ajax mode
+		}
+		return $sHtml;
+	}
+	
+	public function RenderContent(web_page $oPage, $aExtraParams = array())
+	{
+		$oPage->add($this->GetRenderContent($oPage, $aExtraParams));
+	}
+	
+	public function GetRenderContent(web_page $oPage, $aExtraParams = array())
+	{
+		$sHtml = '';
+		// Add the extra params into the filter if they make sense for such a filter
+		$bDoSearch = utils::ReadParam('dosearch', false);
+		if ($this->m_oSet == null)
+		{
+			$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
+			foreach($aFilterCodes as $sFilterCode)
+			{
+				$sExternalFilterValue = utils::ReadParam($sFilterCode, '');
+				if (isset($aExtraParams[$sFilterCode]))
+				{
+					$this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator
+				}
+				else if ($bDoSearch && $sExternalFilterValue != "")
+				{
+					$this->m_oFilter->AddCondition($sFilterCode, $sExternalFilterValue); // Use the default 'loose' operator
+				}
+			}
+			$this->m_oSet = new CMDBObjectSet($this->m_oFilter);
+		}
+		switch($this->m_sStyle)
+		{
+			case 'count':
+			if (isset($aExtraParams['group_by']))
+			{
+				$sGroupByField = $aExtraParams['group_by'];
+				$aGroupBy = array();
+				while($oObj = $this->m_oSet->Fetch())
+				{
+					$sValue = $oObj->Get($sGroupByField);
+					$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
+				}
+				$sFilter = urlencode($this->m_oFilter->serialize());
+				$aData = array();
+				foreach($aGroupBy as $sValue => $iCount)
+				{
+					$aData[] = array ( 'group' => $sValue,
+									  'value' => "<a href=\"./UI.php?operation=search&dosearch=1&filter=$sFilter&$sGroupByField=".urlencode($sValue)."\">$iCount</a>"); // TO DO: add the context information
+				}
+				$sHtml .= $oPage->GetTable(array('group' => array('label' => MetaModel::GetLabel($this->m_oFilter->GetClass(), $sGroupByField), 'description' => ''), 'value' => array('label'=>'Count', 'description' => 'Number of elements')), $aData);
+			}
+			else
+			{
+				// Simply count the number of elements in the set
+				$iCount = $oSet->Count();
+				$sHtml .= $oPage->GetP("$iCount objects matching the criteria.");
+			}
+			
+			break;
+			
+			case 'list':
+			$bDashboardMode = isset($aExtraParams['dashboard']) ? ($aExtraParams['dashboard'] == 'true') : false;
+			if ( ($this->m_oSet->Count()> 0) && (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES) )
+			{
+				if (!$bDashboardMode)
+				{
+					$sHtml .= $oPage->GetP($this->m_oSet->Count()." object(s).");
+				}
+				$sLinkage = isset($aExtraParams['linkage']) ? $aExtraParams['linkage'] : '';
+				$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $this->m_oSet, $sLinkage, !$bDashboardMode /* bDisplayMenu */);
+			}
+			else
+			{
+				$sHtml .= $oPage->GetP("No object to display.");
+				$sClass = $this->m_oFilter->GetClass();
+				if (!$bDashboardMode)
+				{
+					if (UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES)
+					{
+						$sHtml .= $oPage->GetP("<a href=\"./UI.php?operation=new&class=$sClass\">Click here to create a new ".Metamodel::GetName($sClass)."</a>\n");
+					}
+				}
+			}
+			break;
+			
+			case 'details':
+			if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)
+			{
+				while($oObj = $this->m_oSet->Fetch())
+				{
+					$sHtml .= $oObj->GetDetails($oPage);
+				}
+			}
+			break;
+			
+			case 'bare_details':
+			if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)
+			{
+				while($oObj = $this->m_oSet->Fetch())
+				{
+					$sHtml .= $oObj->GetBareDetails($oPage);
+				}
+			}
+			break;
+			
+			case 'csv':
+			if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_READ, $this->m_oSet) == UR_ALLOWED_YES)
+			{
+				$sHtml .= "<textarea style=\"width:100%;height:98%\">\n";
+				$sHtml .= cmdbAbstractObject::GetSetAsCSV($this->m_oSet);
+				$sHtml .= "</textarea>\n";
+			}
+			break;
+
+			case 'modify':
+			if (UserRights::IsActionAllowed($this->m_oSet->GetClass(), UR_ACTION_MODIFY, $this->m_oSet) == UR_ALLOWED_YES)
+			{
+				while($oObj = $this->m_oSet->Fetch())
+				{
+					$sHtml .= $oObj->GetModifyForm($oPage);
+				}
+			}
+			break;
+			
+			case 'search':
+			$iSearchSectionId = 1;
+			$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
+			$sHtml .= "<div id=\"Search_$iSearchSectionId\" class=\"$sStyle\">\n";
+			$sHtml .= "<h1>Search form for ".Metamodel::GetName($this->m_oSet->GetClass())."</h1>\n";
+			$oPage->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});");
+			$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
+	 		$sHtml .= "</div>\n";
+	 		$sHtml .= "<div class=\"HRDrawer\"/></div>\n";
+	 		$sHtml .= "<div id=\"LnkSearch_$iSearchSectionId\" class=\"DrawerHandle\">Search</div>\n";
+			break;
+			
+			case 'pie_chart':
+			$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
+			$sFilter = $this->m_oFilter->ToOQL();
+			$sHtml .= "
+			<OBJECT classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"
+				codebase=\"http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0\" 
+				WIDTH=\"400\" 
+				HEIGHT=\"250\" 
+				id=\"charts\" 
+				ALIGN=\"\">
+			<PARAM NAME=movie VALUE=\"../images/charts.swf?library_path=../images/charts_library&xml_source=".urlencode("../pages/ajax.render.php?operation=pie_chart&group_by=$sGroupBy&encoding=oql&filter=".urlencode($sFilter))."\">
+			<PARAM NAME=\"quality\" VALUE=\"high\">
+			<PARAM NAME=\"bgcolor\" VALUE=\"#ffffff\">
+			
+			<EMBED src=\"../images/charts.swf?library_path=../images/charts_library&xml_source=".urlencode("../pages/ajax.render.php?operation=pie_chart&group_by=$sGroupBy&encoding=oql&filter=".urlencode($sFilter))."\" 
+			       quality=\"high\"
+			       bgcolor=\"#ffffff\"  
+			       WIDTH=\"400\" 
+			       HEIGHT=\"250\" 
+			       NAME=\"charts\" 
+			       ALIGN=\"\" 
+			       swLiveConnect=\"true\" 
+			       TYPE=\"application/x-shockwave-flash\" 
+			       PLUGINSPAGE=\"http://www.macromedia.com/go/getflashplayer\">
+			</EMBED>
+			</OBJECT>
+			";
+			break;
+			
+			case 'pie_chart_ajax':
+			if (isset($aExtraParams['group_by']))
+			{
+				$sGroupByField = $aExtraParams['group_by'];
+				$aGroupBy = array();
+				while($oObj = $this->m_oSet->Fetch())
+				{
+					$sValue = $oObj->Get($sGroupByField);
+					$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
+				}
+				$sFilter = urlencode($this->m_oFilter->serialize());
+				$aData = array();
+				$sHtml .= "<chart>\n";
+				$sHtml .= "<chart_type>3d pie</chart_type>\n";
+				$sHtml .= "<chart_data>\n";
+				$sHtml .= "<row>\n";
+				$sHtml .= "<null/>\n";
+				foreach($aGroupBy as $sValue => $void)
+				{
+					$sHtml .= "<string>$sValue</string>\n";
+				}
+				$sHtml .= "</row>\n";
+				$sHtml .= "<row>\n";
+				$sHtml .= "<string></string>\n";
+				foreach($aGroupBy as $void => $iCount)
+				{
+					$sHtml .= "<number>$iCount</number>\n";
+				}
+				$sHtml .= "</row>\n";
+				$sHtml .= "</chart_data>\n";
+				$sHtml .= "
+	<chart_value color='ffffff' alpha='90' font='arial' bold='true' size='10' position='inside' prefix='' suffix='' decimals='0' separator='' as_percentage='true' />
+
+	<draw>
+		<text color='000000' alpha='10' font='arial' rotation='0' bold='true' size='30' x='0' y='140' width='400' height='150' h_align='center' v_align='bottom'>|||||||||||||||||||||||||||||||||||||||||||||||</text>
+	</draw>
+
+	<legend_label layout='horizontal' bullet='circle' font='arial' bold='true' size='13' color='000000' alpha='85' />
+	<legend_rect fill_color='ffffff' fill_alpha='10' line_color='ffffff' line_alpha='50' line_thickness='0' />
+	<series_color>
+		<color>ddaa41</color>
+		<color>88dd11</color>
+		<color>4e62dd</color>
+		<color>ff8811</color>
+		<color>4d4d4d</color>
+		<color>5a4b6e</color>
+		<color>1188ff</color>
+	</series_color>
+				";
+				$sHtml .= "</chart>\n";
+			}
+			else
+			{
+				// Simply count the number of elements in the set
+				$iCount = $oSet->Count();
+				$sHtml .= "<chart>\n</chart>\n";
+			}
+			break;
+			
+			case 'open_flash_chart':
+			static $iChartCounter = 0;
+			$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
+			$sTitle = isset($aExtraParams['chart_title']) ? $aExtraParams['chart_title'] : '';
+			$sGroupBy = isset($aExtraParams['group_by']) ? $aExtraParams['group_by'] : '';
+			$sFilter = $this->m_oFilter->ToOQL();
+			$sHtml .= "<script>
+			swfobject.embedSWF(\"../images/open-flash-chart.swf\", \"my_chart_{$iChartCounter}\", \"400\", \"400\",\"9.0.0\", \"expressInstall.swf\",
+			{\"data-file\":\"".urlencode("../pages/ajax.render.php?operation=open_flash_chart&params[group_by]=$sGroupBy&params[chart_type]=$sChartType&params[chart_title]=$sTitle&encoding=oql&filter=".urlencode($sFilter))."\"});
+</script>\n";
+			$sHtml .= "<div id=\"my_chart_{$iChartCounter}\">Here goes the chart</div>\n";
+			$iChartCounter++;
+			break;
+			
+			case 'open_flash_chart_ajax':
+			include './php-ofc-library/open-flash-chart.php';
+			$sChartType = isset($aExtraParams['chart_type']) ? $aExtraParams['chart_type'] : 'pie';
+
+			$oChart = new open_flash_chart();
+			switch($sChartType)
+			{
+				case 'bars':
+				$oChartElement = new bar_glass();
+
+				if (isset($aExtraParams['group_by']))
+				{
+					$sGroupByField = $aExtraParams['group_by'];
+					$aGroupBy = array();
+					while($oObj = $this->m_oSet->Fetch())
+					{
+						$sValue = $oObj->Get($sGroupByField);
+						$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
+					}
+					$sFilter = urlencode($this->m_oFilter->serialize());
+					$aData = array();
+					$aLabels = array();
+					foreach($aGroupBy as $sValue => $iValue)
+					{
+						$aData[] = $iValue;
+						$aLabels[] = $sValue;
+					}
+					$maxValue = max($aData);
+					$oYAxis = new y_axis();
+					$aMagicValues = array(1,2,5,10);
+					$iMultiplier = 1;
+					$index = 0;
+					$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
+					while($maxValue > $iTop)
+					{
+						$index++;
+						$iTop = $aMagicValues[$index % count($aMagicValues)]*$iMultiplier;
+						if (($index % count($aMagicValues)) == 0)
+						{
+							$iMultiplier = $iMultiplier * 10;
+						}
+					}
+					//echo "oYAxis->set_range(0, $iTop, $iMultiplier);\n";
+					$oYAxis->set_range(0, $iTop, $iMultiplier);
+					$oChart->set_y_axis( $oYAxis );
+
+					$oChartElement->set_values( $aData );
+					$oXAxis = new x_axis();
+					$oXLabels = new x_axis_labels();
+					// set them vertical
+					$oXLabels->set_vertical();
+					// set the label text
+					$oXLabels->set_labels($aLabels);
+					// Add the X Axis Labels to the X Axis
+					$oXAxis->set_labels( $oXLabels );
+					$oChart->set_x_axis( $oXAxis );
+				}
+				break;
+				
+				case 'pie':
+				default:
+				$oChartElement = new pie();
+				$oChartElement->set_start_angle( 35 );
+				$oChartElement->set_animate( true );
+				$oChartElement->set_tooltip( '#label# - #val# (#percent#)' );
+				if (isset($aExtraParams['group_by']))
+				{
+					$sGroupByField = $aExtraParams['group_by'];
+					$aGroupBy = array();
+					while($oObj = $this->m_oSet->Fetch())
+					{
+						$sValue = $oObj->Get($sGroupByField);
+						$aGroupBy[$sValue] = isset($aGroupBy[$sValue]) ? $aGroupBy[$sValue]+1 : 1;
+					}
+					$sFilter = urlencode($this->m_oFilter->serialize());
+					$aData = array();
+					foreach($aGroupBy as $sValue => $iValue)
+					{
+						$aData[] = new pie_value($iValue, $sValue);
+					}
+	
+	
+					$oChartElement->set_values( $aData );
+					$oChart->x_axis = null;
+				}
+			}				
+			if (isset($aExtraParams['chart_title'])) //@@ BUG: not passed via ajax !!!
+			{
+				$oTitle = new title( $aExtraParams['chart_title'] );
+				$oChart->set_title( $oTitle );
+			}
+			$oChart->set_bg_colour('#FFFFFF');
+			$oChart->add_element( $oChartElement );
+			
+			$sHtml = $oChart->toPrettyString();
+			break;
+			
+			default:
+			// Unsupported style, do nothing.
+			$sHtml .= "Error: unsupported style of block: ".$this->m_sStyle;
+		}
+		return $sHtml;
+	}
+}
+
+/**
+ * Helper class to manage 'blocks' of HTML pieces that are parts of a page and contain some list of cmdb objects
+ *
+ * Each block is actually rendered as a <div></div> tag that can be rendered synchronously
+ * or as a piece of Javascript/JQuery/Ajax that will get its content from another page (ajax.render.php).
+ * The list of cmdbObjects to be displayed into the block is defined by a filter
+ * Right now the type of display is either: list, count or details
+ * - list produces a table listing the objects
+ * - count produces a paragraphs with a sentence saying 'cont' objects found
+ * - details display (as  table) the details of each object found (best if only one)
+ */
+class HistoryBlock extends DisplayBlock
+{
+	public function GetRenderContent(web_page $oPage, $aExtraParams = array())
+	{
+		$sHtml = '';
+		// Add the extra params into the filter if they make sense for such a filter
+		$aFilterCodes = array_keys(MetaModel::GetClassFilterDefs($this->m_oFilter->GetClass()));
+		foreach($aFilterCodes as $sFilterCode)
+		{
+			if (isset($aExtraParams[$sFilterCode]))
+			{
+				$this->m_oFilter->AddCondition($sFilterCode, $aExtraParams[$sFilterCode]); // Use the default 'loose' operator
+			}
+		}
+		$oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false));
+		$sHtml .= "<!-- filter: ".($this->m_oFilter->ToOQL())."-->\n";
+		switch($this->m_sStyle)
+		{
+			case 'toggle':
+			$oLatestChangeOp = $oSet->Fetch();
+			if (is_object($oLatestChangeOp))
+			{
+				global $oContext; // User Context.. should be statis instead of global...
+				// There is one change in the list... only when the object has been created !
+				$sDate = $oLatestChangeOp->GetAsHTML('date');
+				$oChange = $oContext->GetObject('CMDBChange', $oLatestChangeOp->Get('change'));
+				$sUserInfo = $oChange->GetAsHTML('userinfo');
+				$oSet->Load(); // Reset the pointer to the beginning of the set: there should be a better way to do this...
+				$sHtml .= $oPage->GetStartCollapsibleSection("Last modified on $sDate by $sUserInfo.");
+				$sHtml .= cmdbAbstractObject::GetDisplaySet($oPage, $oSet);			
+				$sHtml .= $oPage->GetEndCollapsibleSection();
+			}
+			break;
+						
+			default:
+			$sHtml .= parent::GetRenderContent($oPage, $aExtraParams);
+		}
+		return $sHtml;
+	}
+}
+
+class MenuBlock extends DisplayBlock
+{
+	public function GetRenderContent(web_page $oPage, $aExtraParams = array())
+	{
+		$sHtml = '';
+		$oAppContext = new ApplicationContext();
+		$sContext = $oAppContext->GetForLink();
+		$sClass = $this->m_oFilter->GetClass();
+		$oSet = new CMDBObjectSet($this->m_oFilter);
+		$sFilter = $this->m_oFilter->serialize();
+		$aActions = array();
+		$sUIPage = cmdbAbstractObject::ComputeUIPage($sClass);
+		switch($oSet->Count())
+		{
+			case 0:
+			// No object in the set, the only possible action is "new"
+			$bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet);
+			if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New', 'url' => "../page/$sUIPage?operation=new&class=$sClass&$sContext"); }
+			break;
+			
+			case 1:
+			$oObj = $oSet->Fetch();
+			$id = $oObj->GetKey();
+			$bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet);
+			$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
+			$bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet);
+			$bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet);
+			// Just one object in the set, possible actions are "new / clone / modify and delete"
+			if (isset($aExtraParams['linkage']))
+			{
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); }
+				if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); }
+				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); }
+				if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); }
+			}
+			else
+			{
+				$aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext"));
+				$aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
+				$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger');
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); }
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Clone...', 'url' => "../pages/$sUIPage?operation=clone&class=$sClass&id=$id&$sContext"); }
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'Modify...', 'url' => "../pages/$sUIPage?operation=modify&class=$sClass&id=$id&$sContext"); }
+				if ($bIsDeleteAllowed) { $aActions[] = array ('label' => 'Delete', 'url' => "../pages/$sUIPage?operation=delete&class=$sClass&id=$id&$sContext"); }
+			}
+			$aTransitions = $oObj->EnumTransitions();
+			$aStimuli = Metamodel::EnumStimuli($sClass);
+			foreach($aTransitions as $sStimulusCode => $aTransitionDef)
+			{
+				$iActionAllowed = UserRights::IsStimulusAllowed($sClass, $sStimulusCode, $oSet);
+				switch($iActionAllowed)
+				{
+					case UR_ALLOWED_YES:
+					$aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label'), 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext");
+					break;
+					
+					case UR_ALLOWED_DEPENDS:
+					$aActions[] = array('label' => $aStimuli[$sStimulusCode]->Get('label').' (*)', 'url' => "../pages/UI.php?operation=stimulus&stimulus=$sStimulusCode&class=$sClass&id=$id&$sContext");
+					break;
+					
+					default:
+					// Do nothing
+				}
+			}
+			//print_r($aTransitions);
+			break;
+			
+			default:
+			// Check rights
+			// New / Modify
+			$bIsModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY, $oSet);
+			$bIsBulkModifyAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_MODIFY, $oSet);
+			$bIsBulkDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_BULK_DELETE, $oSet);
+			if (isset($aExtraParams['linkage']))
+			{
+				$bIsDeleteAllowed = UserRights::IsActionAllowed($sClass, UR_ACTION_DELETE, $oSet);
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "#"); }
+				if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "#"); }
+				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Remove All', 'url' => "#"); }
+				if ($bIsModifyAllowed | $bIsDeleteAllowed) { $aActions[] = array ('label' => 'Manage Links...', 'url' => "#"); }
+			}
+			else
+			{
+				// many objects in the set, possible actions are: new / modify all / delete all
+				$aActions[] = array ('label' => 'eMail', 'url' => "mailto:?subject=".$oSet->GetFilter()->__DescribeHTML()."&body=".urlencode("http://localhost:81/pages/UI.php?operation=search&filter=$sFilter&$sContext"));
+				$aActions[] = array ('label' => 'CSV Export', 'url' => "../pages/$sUIPage?operation=search&filter=$sFilter&format=csv&$sContext");
+				$aActions[] = array ('label' => 'Bookmark...', 'url' => "../pages/ajax.render.php?operation=create&class=$sClass&filter=$sFilter", 'class' => 'jqmTrigger');
+				if ($bIsModifyAllowed) { $aActions[] = array ('label' => 'New...', 'url' => "../pages/$sUIPage?operation=new&class=$sClass&$sContext"); }
+				if ($bIsBulkModifyAllowed) { $aActions[] = array ('label' => 'Modify All...', 'url' => "../pages/$sUIPage?operation=modify_all&filter=$sFilter&$sContext"); }
+				if ($bIsBulkDeleteAllowed) { $aActions[] = array ('label' => 'Delete All', 'url' => "../pages/$sUIPage?operation=delete_all&filter=$sFilter&$sContext"); }
+			}
+		}
+		$sHtml .= "<div class=\"jd_menu_itop\"><ul class=\"jd_menu jd_menu_itop\">\n<li>Actions\n<ul>\n";
+		foreach ($aActions as $aAction)
+		{
+			$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
+			$sHtml .= "<li><a href=\"{$aAction['url']}\"$sClass>{$aAction['label']}</a></li>\n<li>\n";
+		}
+		$sHtml .= "</ul>\n</li>\n</ul></div>\n";
+		$oPage->add_ready_script("$(\"ul.jd_menu\").jdMenu();\n");
+		return $sHtml;
+	}
+	
+}
+?>

+ 54 - 0
application/iotask.class.inc.php

@@ -0,0 +1,54 @@
+<?php
+require_once('../application/cmdbabstract.class.inc.php');
+
+/**
+ * This class manages the input/output tasks
+ * for synchronizing information with external data sources 
+ */
+class InputOutputTask extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "application",
+			"name" => "IOTask",
+			"description" => "Input / Output Task for synchronizing information with external data sources",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_iotask",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Task Name", "description"=>"Short name for this task", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Task Description", "description"=>"Long description for this task", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("category", array("label"=>"Category", "description"=>"Type of task", "allowed_values"=>new ValueSetEnum('Input, Ouput'), "sql"=>"category", "default_value"=>"Input", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("source_type", array("label"=>"Source Type", "description"=>"Type of data source", "allowed_values"=>new ValueSetEnum('File, Database, Web Service'), "sql"=>"source_type", "default_value"=>"File", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("source_subtype", array("label"=>"Source Subtype", "description"=>"Subtype of Data Source", "allowed_values"=>new ValueSetEnum('Oracle, MySQL, Postgress, MSSQL, SOAP, HTTP-Get, HTTP-Post, XML/RPC, CSV, XML, Excel'), "sql"=>"source_subtype", "default_value"=>"CSV", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("source_path", array("label"=>"Source Path", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"source_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("objects_class", array("label"=>"Objects Class", "description"=>"Class of the objects processed by this task", "allowed_values"=>new ValueSetEnum('bizOrganization, bizContact, bizTeam, bizPerson, bizLocation, bizServer, bizPC, bizNetworkDevice, bizInterface, bizService, bizContract, bizInfraGroup, bizIncidentTicket, bizSoftware, bizApplication, bizPatch, bizWorkgroup, lnkContactRealObject, lnkInterfaces, bizInfraGrouping' ), "sql"=>"objects_class", "default_value"=>'', "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("test_mode", array("label"=>"Test Mode", "description"=>"If set to 'Yes' the modifications are not applied", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"test_mode", "default_value"=>'No', "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("verbose_mode", array("label"=>"Verbose Mode", "description"=>"If set to 'Yes' extra debug information is added to the log", "allowed_values"=>new ValueSetEnum('Yes,No'), "sql"=>"verbose_mode", "default_value" => 'No', "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("options", array("label"=>"Options", "description"=>"Reconciliation options", "allowed_values"=>new ValueSetEnum('Full, Update Only, Creation Only'), "sql"=>"options", "default_value"=> 'Full', "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("description");
+		MetaModel::Init_AddFilterFromAttribute("category");
+		MetaModel::Init_AddFilterFromAttribute("source_type");
+		MetaModel::Init_AddFilterFromAttribute("source_subtype");
+		MetaModel::Init_AddFilterFromAttribute("objects_class");
+
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'source_path' , 'options', 'test_mode', 'verbose_mode')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype', 'options')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'description', 'category', 'objects_class', 'source_type', 'source_subtype')); // Criteria of the advanced search form
+	}
+}
+?>

+ 414 - 0
application/itopwebpage.class.inc.php

@@ -0,0 +1,414 @@
+<?php
+require_once("../application/nicewebpage.class.inc.php");
+require_once("../application/usercontext.class.inc.php");
+require_once("../application/applicationcontext.class.inc.php");
+/**
+ * Web page with some associated CSS and scripts (jquery) for a fancier display
+ */
+class iTopWebPage extends nice_web_page
+{
+	private $m_sMenu;
+	private $m_currentOrganization;
+	private $m_aTabs;
+	private $m_sCurrentTabContainer;
+	private $m_sCurrentTab;
+	
+    public function __construct($sTitle, $currentOrganization)
+    {
+        parent::__construct($sTitle);
+        $this->m_sCurrentTabContainer = '';
+        $this->m_sCurrentTab = '';
+		$this->m_aTabs = array();
+		$this->m_sMenu = "";
+		$oAppContext = new ApplicationContext();
+		$sExtraParams = $oAppContext->GetForLink();
+		$this->add_header("Content-type: text/html; charset=utf-8");
+		$this->add_header("Cache-control: no-cache");
+		$this->m_currentOrganization = $currentOrganization;
+		$this->add_linked_script("../js/jquery.dimensions.js");
+		$this->add_linked_script("../js/splitter.js");
+		$this->add_linked_script("../js/jquery.tablehover.js");
+		$this->add_linked_script("../js/jquery.treeview.js");
+		$this->add_linked_script("../js/jquery.autocomplete.js");
+		$this->add_linked_script("../js/jquery.bgiframe.js");
+		$this->add_linked_script("../js/jquery.jdMenu.js");
+		$this->add_linked_script("../js/date.js");
+		$this->add_linked_script("../js/jquery.date.picker.js");
+		$this->add_linked_script("../js/jquery.tablesorter.min.js");
+		//$this->add_linked_script("../js/jquery-ui-personalized-1.5.3.js");
+		$this->add_linked_script("../js/swfobject.js");
+		$this->add_linked_stylesheet("../css/jquery.treeview.css");
+		$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
+		$this->add_linked_stylesheet("../css/date.picker.css");
+		$this->add_ready_script(
+<<<EOF
+	// Vertical splitter. The min/max/starting sizes for the left (A) pane
+	// are set here. All values are in pixels.
+	$("#MySplitter").splitter({
+		type: "v", 
+		minA: 100, initA: 250, maxA: 500,
+		accessKey: "|"
+	});
+
+	// Horizontal splitter, nested in the right pane of the vertical splitter.
+	if ( $("#TopPane").length > 0)
+	{
+		$("#RightPane").splitter({
+			type: "h" //,
+			//minA: 100, initA: 150, maxA: 500,
+			//accessKey: "_"
+		});
+	}
+	
+	// Manually set the outer splitter's height to fill the browser window.
+	// This must be re-done any time the browser window is resized.
+	$(window).bind("resize", function(){
+		var ms = $("#MySplitter");
+		var top = ms.offset().top;		// from dimensions.js
+		var wh = $(window).height();
+		// Account for margin or border on the splitter container
+		var mrg = parseInt(ms.css("marginBottom")) || 0;
+		var brd = parseInt(ms.css("borderBottomWidth")) || 0;
+		ms.css("height", (wh-top-mrg-brd)+"px");
+
+		// IE fires resize for splitter; others don't so do it here
+		if ( !jQuery.browser.msie )
+			ms.trigger("resize");
+
+		
+	}).trigger("resize");
+	
+	var ms = $("#MySplitter");
+	ms.trigger("resize");
+
+	if ( $("#TopPane").length > 0)
+	{
+		$("#RightPane").trigger("resize");
+	}
+	
+	$("#tabbedContent > ul").tabs( 1, { fxFade: true, fxSpeed: 'fast' } ); // tabs
+	$("table.listResults").tableHover(); // hover tables
+	$(".listResults").tablesorter( { headers: { 0:{sorter: false }}, widgets: ['zebra']} ); // sortable and zebra tables
+	$(".date-pick").datePicker( {clickInput: false, createButton: true, startDate: '2000-01-01'} ); // Date picker
+	$('#ModalDlg').jqm({ajax: '@href', trigger: 'a.jqmTrigger', overlay:70, modal:true, toTop:true}); // jqModal Window
+	//$('.display_block').draggable(); // make the blocks draggable
+EOF
+);
+		$this->add_script("
+		// For automplete
+		function findValue(li) {
+			if( li == null ) return alert(\"No match!\");
+			
+			// if coming from an AJAX call, let's use the CityId as the value
+			if( !!li.extra ) var sValue = li.extra[0];
+			
+			// otherwise, let's just display the value in the text box
+			else var sValue = li.selectValue;
+			
+			//alert(\"The value you selected was: \" + sValue);
+		}
+		
+		function selectItem(li) {
+			findValue(li);
+		}
+		
+		function formatItem(row) {
+			return row[0];
+		}
+		
+		function goBack()
+		{
+			window.history.back();
+		}
+		");
+		$this->DisplayMenu();
+	}
+	
+	public function AddToMenu($sHtml)
+	{
+		$this->m_sMenu .= $sHtml;
+	}
+
+    public function DisplayMenu()
+    {
+        // Combo box to select the organization
+		$this->AddToMenu("<div id=\"OrganizationSelection\">
+			  <form style=\"display:inline\"><select style=\"width:150px;font-size:x-small\" name=\"org_id\" \"title=\"Pick an organization\" onChange=\"this.form.submit();\">\n");
+		// List of visible Organizations
+		$oContext = new UserContext();
+		$oSearchFilter = $oContext->NewFilter("bizOrganization");
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$sSelected = ($this->m_currentOrganization == '') ? ' selected' : '';
+		$this->AddToMenu("<option value=\"\"$sSelected> All Organizations </option>");
+		if ($oSet->Count() > 0)
+		while($oOrg = $oSet->Fetch())
+		{
+			if ($this->m_currentOrganization == $oOrg->GetKey())
+			{
+				$oCurrentOrganization = $oOrg;
+				$sSelected = " selected";
+			}
+			else
+			{
+				$sSelected = "";
+			}
+			$this->AddToMenu("<option value=\"".$oOrg->GetKey()."\"$sSelected>".$oOrg->Get('name')."</option>\n");
+		}
+		$this->AddToMenu("</select></form>\n");
+		$this->AddToMenu("</div>\n");
+		$this->AddToMenu("<ul id=\"browser\" class=\"dir\">\n");
+        $oAppContext = new ApplicationContext();
+        // Display the menu
+        // 1) Application defined menus
+        $oSearchFilter = $oContext->NewFilter("menuNode");
+        $oSearchFilter->AddCondition('parent_id', 0, '=');
+        $oSearchFilter->AddCondition('type', 'application', '=');
+        // There may be more criteria added later to have a specific menu based on the user's profile
+        $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true));
+        while ($oRootMenuNode = $oSet->Fetch())
+        {
+        	$oRootMenuNode->DisplayMenu($this, 'application', $oAppContext->GetAsHash());
+        }
+        // 2) User defined menus (Bookmarks)
+        $oSearchFilter = $oContext->NewFilter("menuNode");
+        $oSearchFilter->AddCondition('parent_id', 0, '=');
+        $oSearchFilter->AddCondition('type', 'user', '=');
+        $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '=');
+        // There may be more criteria added later to have a specific menu based on the user's profile
+        $oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true));
+        while ($oRootMenuNode = $oSet->Fetch())
+        {
+        	$oRootMenuNode->DisplayMenu($this, 'user', $oAppContext->GetAsHash());
+        }
+		$this->AddToMenu("</ul>\n");
+    }
+
+	/**
+	 * Outputs (via some echo) the complete HTML page by assembling all its elements
+	 */
+    public function output()
+    {
+        foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        $s_captured_output = ob_get_contents();
+        ob_end_clean();
+        echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
+        echo "<html>\n";
+        echo "<head>\n";
+        echo "<title>{$this->s_title}</title>\n";
+        foreach($this->a_linked_scripts as $s_script)
+        {
+            echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
+        }
+        if (count($this->m_aReadyScripts)>0)
+        {
+			$this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});");
+		}
+        if (count($this->a_scripts)>0)
+        {
+            echo "<script type=\"text/javascript\">\n";
+            foreach($this->a_scripts as $s_script)
+            {
+                echo "$s_script\n";
+            }
+            echo "</script>\n";
+        }
+        foreach($this->a_linked_stylesheets as $a_stylesheet)
+        {
+			if ($a_stylesheet['condition'] != "")
+			{
+				echo "<!--[if {$a_stylesheet['condition']}]>\n";
+			}
+            echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$a_stylesheet['link']}\" />\n";
+			if ($a_stylesheet['condition'] != "")
+			{
+				echo "<![endif]-->\n";
+			}
+        }
+        
+        if (count($this->a_styles)>0)
+        {
+            echo "<style>\n";
+            foreach($this->a_styles as $s_style)
+            {
+                echo "$s_style\n";
+            }
+            echo "</style>\n";
+        }
+		echo "<link rel=\"search\" type=\"application/opensearchdescription+xml\" title=\"iTop\" href=\"./opensearch.xml.php\">\n";
+        echo "</head>\n";
+        echo "<body>\n";
+
+		// Display the header
+		echo "<div id=\"Header\">\n";
+		echo "<div class=\"iTopLogo\"><span>iTop</span></div>\n";
+		//echo "<div id=\"GlobalSearch\"><div style=\"border: 1px solid #999; padding:1px; background-color:#fff;\"><img src=\"/images/magnifier.gif\"/><input style=\"border:0\" type=\"text\" size=\"15\" title=\"Global Search\"></input></div></div>\n";
+		$sText = Utils::ReadParam('text', '');
+		$sOnClick = "";
+		if (empty($sText))
+		{
+			// if no search text is supplied then
+			// 1) the search text is filled with "your search"
+			// 2) clicking on it will erase it
+			$sText = "Your search";
+			$sOnClick = " onclick=\"this.value='';this.onclick=null;\"";
+		}
+		echo "<div id=\"OrganizationSelection\" style=\"position:absolute; top:18px; right:16px; width:400px;\">";
+		echo "<form action=\"../pages/UI.php\" style=\"display:inline\"><div style=\"padding:1px; background-color:#fff;display:inline;\"><img src=\"../images/magnifier.gif\"/><input style=\"border:0\" type=\"text\" size=\"15\" title=\"Global Search\" name=\"text\" value=\"$sText\"$sOnClick></input></div><input type=\"Submit\" value=\"Search\">
+			  <input type=\"hidden\" name=\"operation\" value=\"full_text\"></form>\n";
+		echo "</div>\n";
+
+		echo "</div>\n";
+
+		// Display the menu
+		echo "<div id=\"MySplitter\">\n";
+		echo "  <div id=\"LeftPane\">\n";
+		echo $this->m_sMenu;
+		echo "  </div> <!-- LeftPane -->\n";
+		
+		echo "<div id=\"RightPane\">\n";
+        
+		// Render the tabs in the page (if any)
+		foreach($this->m_aTabs as $sTabContainerName => $m_aTabs)
+		{
+			$sTabs = '';
+			if (count($m_aTabs) > 0)
+			{
+			  $sTabs = "<!-- tabs -->\n<div id=\"tabbedContent\" class=\"light\">\n";
+			  $sTabs .= "<ul>\n";
+			  // Display the unordered list that will be rendered as the tabs
+	          $i = 0;
+			  foreach($m_aTabs as $sTabName => $sTabContent)
+			  {
+			      $sTabs .= "<li><a href=\"#fragment_$i\" class=\"tab\"><span>".htmlentities($sTabName)."</span></a></li>\n";
+			      $i++;
+	          }
+			  $sTabs .= "</ul>\n";
+			  // Now add the content of the tabs themselves
+			  $i = 0;
+			  foreach($m_aTabs as $sTabName => $sTabContent)
+			  {
+			      $sTabs .= "<div id=\"fragment_$i\">".$sTabContent."</div>\n";
+			      $i++;
+	          }
+			  $sTabs .= "</div>\n<!-- end of tabs-->\n";
+	        }
+			$this->s_content = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $this->s_content);
+		}
+        
+		// Display the page's content
+        echo $this->s_content;
+
+        // Add the captured output
+        if (trim($s_captured_output) != "")
+        {
+            echo "<div class=\"raw_output\">$s_captured_output</div>\n";
+        }
+		echo "<div class=\"jqmWindow\" id=\"ex2\">Please wait...</div>\n"; // jqModal Window
+		echo "</div> <!-- RightPane -->\n";
+		echo "</div> <!-- Splitter -->\n";
+		echo "<div class=\"jqmWindow\" id=\"ModalDlg\"></div>";
+		echo "</body>\n";
+        echo "</html>\n";
+    }
+	
+	public function AddTabContainer($sTabContainer)
+	{
+		$this->m_aTabs[$sTabContainer] = array();
+		$this->add("\$Tabs:$sTabContainer\$");
+	}
+	
+	public function AddToTab($sTabContainer, $sTabLabel, $sHtml)
+	{
+		if (!isset($this->m_aTabs[$sTabContainer][$sTabLabel]))
+		{
+			// Set the content of the tab
+			$this->m_aTabs[$sTabContainer][$sTabLabel] = $sHtml;
+		}
+		else
+		{
+			// Append to the content of the tab
+			$this->m_aTabs[$sTabContainer][$sTabLabel] .= $sHtml;
+		}
+	}
+
+	public function SetCurrentTabContainer($sTabContainer = '')
+	{
+		$sPreviousTabContainer = $this->m_sCurrentTabContainer;
+		$this->m_sCurrentTabContainer = $sTabContainer;
+		return $sPreviousTabContainer;
+	}
+
+	public function SetCurrentTab($sTabLabel = '')
+	{
+		$sPreviousTab = $this->m_sCurrentTab;
+		$this->m_sCurrentTab = $sTabLabel;
+		return $sPreviousTab;
+	}
+	
+	public function StartCollapsibleSection($sSectionLabel, $bOpen = false)
+	{
+		$this->add($this->GetStartCollapsibleSection($sSectionLabel, $bOpen));
+	}
+
+	public function GetStartCollapsibleSection($sSectionLabel, $bOpen = false)
+	{
+		$sHtml = '';
+		static $iSectionId = 0;
+		$sHtml .= "<a id=\"LnkCollapse_$iSectionId\" class=\"CollapsibleLabel\" href=\"#\">$sSectionLabel</a></br>\n";
+		$sStyle = $bOpen ? '' : 'style="display:none" ';
+		$sHtml .= "<div id=\"Collapse_$iSectionId\" $sStyle>";
+		$this->add_ready_script("\$(\"#LnkCollapse_$iSectionId\").click(function() {\$(\"#Collapse_$iSectionId\").slideToggle('normal'); $(\"#LnkCollapse_$iSectionId\").toggleClass('open');});");
+		//$this->add_ready_script("$('#LnkCollapse_$iSectionId').hide();");
+		$iSectionId++;
+		return $sHtml;
+	}
+
+	public function EndCollapsibleSection()
+	{
+		$this->add($this->GetEndCollapsibleSection());
+	}
+
+	public function GetEndCollapsibleSection()
+	{
+		return "</div>";
+	}
+
+    public function add($sHtml)
+    {
+        if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
+        {
+            $this->AddToTab($this->m_sCurrentTabContainer, $this->m_sCurrentTab, $sHtml);
+        }
+        else
+        {
+            parent::add($sHtml);
+        }
+    }
+    
+    /*
+    public function AddSearchForm($sClassName, $bOpen = false)
+    {
+    	$iSearchSectionId = 0;
+    	
+		$sStyle = $bOpen ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
+		$this->add("<div id=\"Search_$iSearchSectionId\" class=\"$sStyle\">\n");
+		$this->add("<h1>Search form for ".Metamodel::GetName($sClassName)."</h1>\n");
+		$this->add_ready_script("\$(\"#LnkSearch_$iSearchSectionId\").click(function() {\$(\"#Search_$iSearchSectionId\").slideToggle('normal'); $(\"#LnkSearch_$iSearchSectionId\").toggleClass('open');});");
+		$oFilter = new DBObjectSearch($sClassName);
+		$sFilter = $oFilter->serialize();
+		$oSet = new CMDBObjectSet($oFilter);
+		cmdbAbstractObject::DisplaySearchForm($this, $oSet, array('operation' => 'search', 'filter' => $sFilter, 'search_form' => true));
+ 		$this->add("</div>\n");
+ 		$this->add("<div class=\"HRDrawer\"/></div>\n");
+ 		$this->add("<div id=\"LnkSearch_$iSearchSectionId\" class=\"DrawerHandle\">Search</div>\n");
+
+    	
+    	$iSearchSectionId++;
+	}
+	*/
+}
+
+?>

+ 32 - 0
application/itopwizardwebpage.class.inc.php

@@ -0,0 +1,32 @@
+<?php
+require_once('itopwebpage.class.inc.php');
+/**
+ * Web page to display a wizard in the iTop framework
+ */
+class iTopWizardWebPage extends iTopWebPage
+{
+	var $m_iCurrentStep;
+	var $m_aSteps;
+    public function __construct($sTitle, $currentOrganization, $iCurrentStep, $aSteps)
+    {
+    	parent::__construct($sTitle." - step $iCurrentStep of ".count($aSteps)." - ".$aSteps[$iCurrentStep - 1], $currentOrganization);
+		$this->m_iCurrentStep = $iCurrentStep;
+		$this->m_aSteps = $aSteps;
+    }
+    
+    public function output()
+    {
+    	$aSteps = array();
+    	$iIndex = 0;
+    	foreach($this->m_aSteps as $sStepTitle)
+    	{
+    		$iIndex++;
+    		$sStyle = ($iIndex == $this->m_iCurrentStep) ? 'wizActiveStep' : 'wizStep';
+    		$aSteps[] = "<div class=\"$sStyle\"><span>$sStepTitle</span></div>";
+    	}
+    	$sWizardHeader = "<div class=\"wizHeader\"><h1>{$this->s_title}</h1>\n".implode("<div class=\"wizSeparator\"><img align=\"bottom\" src=\"/images/wizArrow.gif\"></div>", $aSteps)."<br style=\"clear:both;\"/></div>\n";
+    	$this->s_content = "$sWizardHeader<div class=\"wizContainer\">".$this->s_content."</div>";
+    	parent::output();
+	}
+}
+?>

+ 117 - 0
application/loginwebpage.class.inc.php

@@ -0,0 +1,117 @@
+<?php
+require_once("../application/nicewebpage.class.inc.php");
+/**
+ * Web page used for displaying the login form
+ */
+class login_web_page extends nice_web_page
+{
+    public function __construct()
+    {
+        parent::__construct("iTop Login");
+        $this->add_style("
+body {
+	background-color: #eee;
+	margin: 0;
+	padding: 0;
+}
+#login {
+	width: 230px;
+	margin-left: auto;
+	margin-right: auto;
+	margin-top: 150px;
+	padding: 20px;
+	background-color: #fff;
+	border: 1px solid #000;
+}
+.center {
+	text-align: center;
+}
+
+h1 {
+	color: #83b217;
+	font-size: 16pt;
+}
+.v-spacer {
+	padding-top: 1em;
+}
+		");
+	}
+	
+	public function DisplayLoginForm($bFailedLogin = false)
+	{
+		$sAuthUser = utils::ReadParam('auth_user', '');
+		$sAuthPwd = utils::ReadParam('suggest_pwd', '');
+
+		$this->add("<div id=\"login\">\n");
+		$this->add("<h1>Welcome to iTop!</h1>\n");
+		if ($bFailedLogin)
+		{
+			$this->add("<p class=\"hilite\">Incorrect login/password, please try again.</p>\n");
+		}
+		else
+		{
+			$this->add("<p>Please identify yourself before continuing.</p>\n");
+		}
+		$this->add("<form method=\"post\">\n");
+		$this->add("<table>\n");
+		$this->add("<tr><td><label for=\"user\">User Name:</label></td><td><input id=\"user\" type=\"text\" name=\"auth_user\" value=\"$sAuthUser\" /></td></tr>\n");
+		$this->add("<tr><td><label for=\"pwd\">Password:</label></td><td><input id=\"pwd\" type=\"password\" name=\"auth_pwd\" value=\"$sAuthPwd\" /></td></tr>\n");
+		$this->add("<tr><td colspan=\"2\" class=\"center v-spacer\"> <input type=\"submit\" value=\"Enter iTop\" /></td></tr>\n");
+		$this->add("</table>\n");
+		$this->add("<input type=\"hidden\" name=\"operation\" value=\"login\" />\n");
+		$this->add("</form>\n");
+		$this->add("</div>\n");
+	}
+	
+	static function DoLogin()
+	{
+		$operation = utils::ReadParam('operation', '');
+		session_start();
+		
+		if (!session_is_registered('auth_user') || !session_is_registered('auth_pwd'))
+		{
+			if ($operation == 'login')
+			{
+				$sAuthUser = utils::ReadParam('auth_user', '', 'post');
+				$sAuthPwd = utils::ReadParam('auth_pwd', '', 'post');
+			}
+			else
+			{
+				$oPage = new login_web_page();
+			    $oPage->DisplayLoginForm();
+			    $oPage->output();
+			    exit;
+			}
+		}
+		else
+		{
+			$sAuthUser = $_SESSION['auth_user'];
+			$sAuthPwd = $_SESSION['auth_pwd'];
+		}
+		if (!UserRights::Login($sAuthUser, $sAuthPwd))
+		{
+			// Unset all of the session variables.
+			$_SESSION = array();
+			// If it's desired to kill the session, also delete the session cookie.
+			// Note: This will destroy the session, and not just the session data!
+			if (isset($_COOKIE[session_name()]))
+			{
+				setcookie(session_name(), '', time()-3600, '/');
+			}		
+			// Finally, destroy the session.
+			session_destroy();
+
+			$oPage = new login_web_page();
+		    $oPage->DisplayLoginForm( true /* failed attempt */);
+		    $oPage->output();
+		    exit;
+		}
+		else
+		{
+			$_SESSION['auth_user'] = $sAuthUser ;
+			$_SESSION['auth_pwd'] = $sAuthPwd;
+			
+		}
+	}
+} // End of class
+?>

+ 219 - 0
application/menunode.class.inc.php

@@ -0,0 +1,219 @@
+<?php
+require_once('../core/attributedef.class.inc.php');
+require_once('../core/filterdef.class.inc.php');
+require_once('../core/stimulus.class.inc.php');
+require_once('../core/MyHelpers.class.inc.php');
+
+require_once('../core/cmdbsource.class.inc.php');
+require_once('../core/sqlquery.class.inc.php');
+
+require_once('../core/dbobject.class.php');
+require_once('../core/dbobjectsearch.class.php');
+require_once('../core/dbobjectset.class.php');
+
+require_once('../application/displayblock.class.inc.php');
+
+/**
+ * This class manages en entries in the menu tree on the left of the iTop pages
+ */
+class menuNode extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "gui",
+			"name" => "menuNode",
+			"description" => "Main menu configuration elements",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_menunode",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+//		MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "jointype"=>"closed")));
+//		MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Menu Name", "description"=>"Short name for this menu", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("label", array("label"=>"Menu Description", "description"=>"Long description for this menu", "allowed_values"=>null, "sql"=>"label", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("hyperlink", array("label"=>"Hyperlink", "description"=>"Hyperlink to the page", "allowed_values"=>null, "sql"=>"hyperlink", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("icon_path", array("label"=>"Menu Icon", "description"=>"Path to the icon o the menu", "allowed_values"=>null, "sql"=>"icon_path", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("template", array("label"=>"Template", "description"=>"HTML template for the view", "allowed_values"=>null, "sql"=>"template", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of menu", "allowed_values"=>new ValueSetEnum('application,user'), "sql"=>"type", "default_value"=>"application", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeInteger("rank", array("label"=>"Display rank", "description"=>"Sort order for displaying the menu", "allowed_values"=>null, "sql"=>"rank", "default_value" => 999, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "sql"=>"parent_id", "targetclass"=>"menuNode", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Menu Item", "description"=>"Parent Menu Item", "allowed_values"=>null, "extkey_attcode"=>"parent_id", "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("user_id", array("label"=>"Owner of the menu", "description"=>"User who owns this menu (for user defined menus)", "allowed_values"=>null, "sql"=>"user_id", "targetclass"=>"UserRightsMatrixUsers", "is_null_allowed"=>true, "depends_on"=>array('type'))));
+
+		MetaModel::Init_AddFilterFromAttribute("label");
+		MetaModel::Init_AddFilterFromAttribute("parent_id");
+		MetaModel::Init_AddFilterFromAttribute("rank");
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("user_id");
+		
+		MetaModel::Init_SetZListItems('details', array('parent_id', 'name', 'label', 'hyperlink', 'template', 'rank', 'type')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('parent_id', 'name', 'label', 'rank', 'type')); // Attributes to be displayed for a list
+	}
+	
+	public function IsVisible()
+	{
+		return true;
+	}
+	
+	public function GetMenuName()
+	{
+		return $this->Get('name');
+	}
+
+	public function GetMenuIcon()
+	{
+		return $this->Get('icon_path');
+	}
+
+	public function GetMenuLabel()
+	{
+		return $this->Get('label');
+	}
+	
+	public function GetMenuLink($aExtraParams)
+	{
+		$aExtraParams['menu'] = $this->GetKey(); // Make sure we overwrite the current menu id (if any)
+		$aParams = array();
+		foreach($aExtraParams as $sName => $sValue)
+		{
+			$aParams[] = urlencode($sName)."=".urlencode($sValue);
+		}
+		return $this->Get('hyperlink')."?".implode("&", $aParams);
+	}
+
+	public function GetChildNodesSet($sType)
+	{
+		$oSearchFilter = new DBObjectSearch("menuNode");
+		$oSearchFilter->AddCondition('parent_id', $this->GetKey(), '=');
+		$oSearchFilter->AddCondition('type', $sType, '=');
+		if ($sType == 'user')
+		{
+		    $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '=');
+		}
+		$oSet = new CMDBObjectSet($oSearchFilter, array('rank' => true));
+		return $oSet;
+	}
+
+	public function RenderContent(web_page $oPage, $aExtraParams = array())
+	{
+		$sTemplate = $this->Get('template');
+		$this->ProcessTemplate($sTemplate, $oPage, $aExtraParams);
+	}
+	
+	protected function ProcessTemplate($sTemplate, web_page $oPage, $aExtraParams = array())
+	{
+		$iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0);
+		$index = 0;
+		while(($iStartPos = stripos($sTemplate, '<'.DisplayBlock::TAG_BLOCK.' ',0)) !== false)
+		{
+			$iEndPos = stripos($sTemplate, '</'.DisplayBlock::TAG_BLOCK.'>', $iStartPos); 
+			
+			$sBlockDefinition = substr($sTemplate, $iStartPos, $iEndPos - $iStartPos + strlen('</'.DisplayBlock::TAG_BLOCK.'>'));
+			$oBlock = DisplayBlock::FromTemplate($sBlockDefinition);
+
+			$oPage->add(substr($sTemplate, 0, $iStartPos));		
+			if ($oBlock) // Protects agains invalid XML templates
+			{
+				$oBlock->Display($oPage, "block{$index}", $aExtraParams); // Values from $aExtraParams have precedence over $aParams
+			}
+			$index++;
+			$sTemplate = substr($sTemplate, $iEndPos + strlen('</'.DisplayBlock::TAG_BLOCK.'>'));
+		}
+		// What remains is purely static (without any block inside), just output as it is
+		$oPage->add($sTemplate);
+	}
+	
+	public function DisplayMenu(iTopWebPage $oP, $sType, $aExtraParams)
+	{
+		$oP->AddToMenu("<li><a href=\"".$this->GetMenuLink($aExtraParams)."\" title=\"".$this->GetMenuLabel()."\">".$this->GetMenuName()."</a>");
+		$oSet = $this->GetChildNodesSet($sType);
+		if ($oSet->Count() > 0)
+		{
+			$oP->AddToMenu("\n<ul>\n");
+			while($oChildNode = $oSet->Fetch())
+			{
+				$oChildNode->DisplayMenu($oP, $sType, $aExtraParams);
+			}
+			$oP->AddToMenu("</ul>\n");
+		}
+		$oP->AddToMenu("</li>\n");
+	}
+	static public function DisplayCreationForm(web_page $oP, $sClass, $sFilter, $aExtraParams = array())
+	{
+		$oFilter = DBObjectSearch::unserialize($sFilter);
+		$oP->p('Create a new menu item for: '.$oFilter->__DescribeHTML());
+		$oP->add('<form action="UniversalSearch.php" method="post">');
+		$oP->add('<input type="hidden" name="operation" value="add_menu">');
+		$oP->add('<input type="hidden" name="filter" value="'.$sFilter.'">');
+		$oP->add('<input type="hidden" name="class" value="'.$sClass.'">');
+		$oP->p('Menu Label: <input type="text" name="label" size="30">');
+		$oP->p('Description: <input type="text" name="description" size="30">');
+		$oP->add('<p>Insert after: <select name="previous_node_id">');
+		$aNodes = self::GetMenuAsArray(null, 'user');
+		foreach($aNodes as $aNodeDesc)
+		{
+			$oP->add('<option value="'.$aNodeDesc['id'].'">'.str_repeat('&nbsp;&nbsp;&nbsp;', $aNodeDesc['depth']).$aNodeDesc['label'].'</option>');
+		}
+		$oP->add('</select></p>');
+		$oP->p('<input type="checkbox" name="child_item" value="1"> Create as a child menu item');
+		$oP->p('<input type="submit" value=" Ok "> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<input type="button" class="jqmClose" value="Cancel">');
+		$oP->add('</form>');
+	}
+	
+	static public function GetMenuAsArray($oRootNode = null, $sType = 'application', $iDepth = 0)
+	{
+		$aNodes = array();
+		if (is_object($oRootNode))
+		{
+			$oChildSet = $oRootNode->GetChildNodesSet($sType);
+			while($oNode = $oChildSet->Fetch())
+			{
+				$aNodes[] = array('depth' => $iDepth, 'id' => $oNode->GetKey(), 'label' => $oNode->GetName());
+				$aNodes = array_merge($aNodes, self::GetMenuAsArray($oNode, $sType, $iDepth+1));
+			}
+		}
+		else
+		{
+			$oSearchFilter = new DbObjectSearch("menuNode");
+			$oSearchFilter->AddCondition('parent_id', 0, '=');
+			$oSearchFilter->AddCondition('type', $sType, '=');
+			if ($sType == 'user')
+			{
+			    $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '=');
+			}
+			$oRootSet = new CMDBObjectSet($oSearchFilter, array('rank' => true));
+			while($oNode = $oRootSet->Fetch())
+			{
+				$aNodes[] = array('depth' => $iDepth, 'id' => $oNode->GetKey(), 'label' => $oNode->GetName());
+				$aNodes = array_merge($aNodes, self::GetMenuAsArray($oNode, $sType, $iDepth+1));
+			}
+		}
+		return $aNodes;
+	}
+	/**
+	 * Returns a set of all the nodes following the current node in the tree
+	 * (i.e. nodes with the same parent but with a greater rank)
+	 */
+	public function GetNextNodesSet($sType = 'application')
+	{
+		$oSearchFilter = new DBObjectSearch("menuNode");
+		$oSearchFilter->AddCondition('parent_id', $this->Get('parent_id'));
+		$oSearchFilter->AddCondition('rank', $this->Get('rank'), '>');
+		$oSearchFilter->AddCondition('type', $sType, '=');
+		if ($sType == 'user')
+		{
+		    $oSearchFilter->AddCondition('user_id', UserRights::GetUserId(), '=');
+		}
+		$oSet = new DBObjectSet($oSearchFilter, array('rank'=> true)); // Order by rank (true means ascending)
+		return $oSet;
+	}
+}
+?>

+ 76 - 0
application/nicewebpage.class.inc.php

@@ -0,0 +1,76 @@
+<?php
+require_once("../application/webpage.class.inc.php");
+/**
+ * Web page with some associated CSS and scripts (jquery) for a fancier display
+ */
+class nice_web_page extends web_page
+{
+	var $m_aReadyScripts;
+	
+    public function __construct($s_title)
+    {
+        parent::__construct($s_title);
+		$this->m_aReadyScripts = array();
+		$this->add_linked_script("../js/jquery.latest.js");
+		$this->add_linked_script("../js/jquery.history_remote.pack.js");
+		//$this->add_linked_script("../js/ui.resizable.js");
+		$this->add_linked_script("../js/ui.tabs.js");
+		$this->add_linked_script("../js/hovertip.js");
+		$this->add_linked_script("../js/jqModal.js");
+		$this->add_linked_stylesheet("../css/light-grey.css");
+		$this->add_linked_stylesheet("../js/themes/light/light.tabs.css");
+		//$this->add_linked_stylesheet("../css/jquery.tabs-ie.css", "lte IE 7");
+		$this->add_linked_stylesheet("../css/jqModal.css");
+		$this->add_ready_script('    window.setTimeout(hovertipInit, 1);');
+    }
+	
+	public function small_p($sText)
+	{
+		$this->add("<p style=\"font-size:smaller\">$sText</p>\n");
+	}	
+
+	// By Rom, used by CSVImport and Advanced search
+	public function MakeClassesSelect($sName, $sDefaultValue, $iWidthPx)
+	{
+		// $aTopLevelClasses = array('bizService', 'bizContact', 'logInfra', 'bizDocument');
+		// These are classes wich root class is cmdbAbstractObject ! 
+		$this->add("<select id=\"select_$sName\" name=\"$sName\">");
+		foreach(MetaModel::GetClasses('bizmodel') as $sClassName)
+		{
+			$sSelected = ($sClassName == $sDefaultValue) ? " SELECTED" : "";
+			$this->add("<option style=\"width: ".$iWidthPx." px;\" value=\"$sClassName\"$sSelected>$sClassName - ".MetaModel::GetClassDescription($sClassName)."</option>");
+		}
+		$this->add("</select>");
+	}
+
+	// By Rom, used by Advanced search
+	public function add_select($aChoices, $sName, $sDefaultValue, $iWidthPx)
+	{
+		$this->add("<select id=\"select_$sName\" name=\"$sName\">");
+		foreach($aChoices as $sKey => $sValue)
+		{
+			$sSelected = ($sKey == $sDefaultValue) ? " SELECTED" : "";
+			$this->add("<option style=\"width: ".$iWidthPx." px;\" value=\"$sKey\"$sSelected>$sValue</option>");
+		}
+		$this->add("</select>");
+	}
+	
+	public function add_ready_script($sScript)
+	{
+		$this->m_aReadyScripts[] = $sScript;
+	}
+	
+		/**
+	 * Outputs (via some echo) the complete HTML page by assembling all its elements
+	 */
+    public function output()
+    {
+        if (count($this->m_aReadyScripts)>0)
+        {
+			$this->add_script("\$(document).ready(function() {\n".implode("\n", $this->m_aReadyScripts)."\n});");
+		}
+		parent::output();
+	}
+}
+
+?>

+ 7 - 0
application/startup.inc.php

@@ -0,0 +1,7 @@
+<?
+
+require_once('../application/utils.inc.php');
+
+MetaModel::Startup('../config-itop.php');
+
+?>

+ 224 - 0
application/template.class.inc.php

@@ -0,0 +1,224 @@
+<?php
+require_once('../application/displayblock.class.inc.php');
+/**
+ * This class manages the special template format used internally to build the iTop web pages
+ */
+class DisplayTemplate
+{
+	protected $m_sTemplate;
+	protected $m_aTags;
+	
+	public function __construct($sTemplate)
+	{
+		$this->m_aTags = array('itopblock', 'itoptabs', 'itoptab', 'itoptoggle');
+		$this->m_sTemplate = $sTemplate;
+	}
+	
+	public function Render(web_page $oPage, $aParams = array())
+	{
+		$this->ApplyParams($aParams);
+		$iStart = 0;
+		$iEnd = strlen($this->m_sTemplate);
+		$iCount = 0;
+		$iBeforeTagPos = $iStart;
+		$iAfterTagPos = $iStart;
+		while($sTag = $this->GetNextTag($iStart, $iEnd))
+		{
+			$sContent = $this->GetTagContent($sTag, $iStart, $iEnd);
+			$aAttributes = $this->GetTagAttributes($sTag, $iStart, $iEnd);
+			//$oPage->p("Tag: $sTag - ($iStart, $iEnd)");
+			$oPage->add(substr($this->m_sTemplate, $iBeforeTagPos, $iStart - $iBeforeTagPos));
+			$this->RenderTag($oPage, $sTag, $aAttributes, $sContent);
+
+			$iAfterTagPos = $iEnd + strlen('</'.$sTag.'>');
+			$iBeforeTagPos = $iAfterTagPos;
+			$iStart = $iEnd;
+			$iEnd = strlen($this->m_sTemplate); 
+			$iCount++;
+			if ($iCount > 10) break;
+		}
+		$oPage->add(substr($this->m_sTemplate, $iAfterTagPos));
+	}
+	
+	/**
+	 * Replaces all the parameters by the values passed in the hash array
+	 */
+	public function ApplyParams($aParams)
+	{
+		$aSearches = array();
+		$aReplacements = array();
+		foreach($aParams as $sSearch => $sReplace)
+		{
+			$aSearches[] = '$'.$sSearch.'$';
+			$aReplacements[] = $sReplace;
+		}
+		$this->m_sTemplate = str_replace($aSearches, $aReplacements, $this->m_sTemplate);
+	}
+	
+	public function GetNextTag(&$iStartPos, &$iEndPos)
+	{
+		$iChunkStartPos = $iStartPos;
+		$sNextTag = null;
+		$iStartPos = $iEndPos;
+		foreach($this->m_aTags as $sTag)
+		{
+			// Search for the opening tag
+			$iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.' ', $iChunkStartPos);
+			if ($iOpeningPos === false)
+			{
+				$iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.'>', $iChunkStartPos);
+			}
+			if ($iOpeningPos !== false)
+			{
+				$iClosingPos = stripos($this->m_sTemplate, '</'.$sTag.'>', $iOpeningPos);
+			}
+			if ( ($iOpeningPos !== false) && ($iClosingPos !== false))
+			{
+				if ($iOpeningPos < $iStartPos)
+				{
+					// This is the next tag
+					$iStartPos = $iOpeningPos;
+					$iEndPos = $iClosingPos;
+					$sNextTag = $sTag;
+				}
+			}
+		}
+		return $sNextTag;
+	}
+	
+	public function GetTagContent($sTag, $iStartPos, $iEndPos)
+	{
+		$sContent  = "";
+		$iContentStart = strpos($this->m_sTemplate, '>', $iStartPos); // Content of tag start immediatly after the first closing bracket
+		if ($iContentStart !== false)
+		{
+			$sContent = substr($this->m_sTemplate, 1+$iContentStart, $iEndPos - $iContentStart - 1);
+		}
+		return $sContent;
+	}
+
+	public function GetTagAttributes($sTag, $iStartPos, $iEndPos)
+	{
+		$aAttr  = array();
+		$iAttrStart = strpos($this->m_sTemplate, ' ', $iStartPos); // Attributes start just after the first space
+		$iAttrEnd = strpos($this->m_sTemplate, '>', $iStartPos); // Attributes end just before the first closing bracket
+		if ( ($iAttrStart !== false) && ($iAttrEnd !== false) && ($iAttrEnd > $iAttrStart))
+		{
+			$sAttributes = substr($this->m_sTemplate, 1+$iAttrStart, $iAttrEnd - $iAttrStart - 1);
+			$aAttributes = explode(' ', $sAttributes);
+			foreach($aAttributes as $sAttr)
+			{
+				if ( preg_match('/(.+) *= *"(.+)"$/', $sAttr, $aMatches) )
+				{
+					$aAttr[strtolower($aMatches[1])] = $aMatches[2];
+				}
+			}
+		}
+		return $aAttr;
+	}
+	
+	protected function RenderTag($oPage, $sTag, $aAttributes, $sContent)
+	{
+		static $iTabContainerCount = 0;
+		static $iBlockCount = 0;
+		switch($sTag)
+		{
+			case 'itoptabs':
+				$oPage->AddTabContainer('Tabs_'.$iTabContainerCount);
+				$oPage->SetCurrentTabContainer('Tabs_'.$iTabContainerCount);
+				$iTabContainerCount++;
+				//$oPage->p('Content:<pre>'.htmlentities($sContent).'</pre>');
+				$oTemplate = new DisplayTemplate($sContent);
+				$oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
+				$oPage->SetCurrentTabContainer('');
+			break;
+			
+			case 'itoptab':
+				$oPage->SetCurrentTab($aAttributes['name']);
+				$oTemplate = new DisplayTemplate($sContent);
+				$oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
+				//$oPage->p('iTop Tab Content:<pre>'.htmlentities($sContent).'</pre>');
+				$oPage->SetCurrentTab('');
+			break;
+			
+			case 'itoptoggle':
+				$oPage->StartCollapsibleSection($aAttributes['name']);
+				$oTemplate = new DisplayTemplate($sContent);
+				$oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
+				//$oPage->p('iTop Tab Content:<pre>'.htmlentities($sContent).'</pre>');
+				$oPage->EndCollapsibleSection();
+			break;
+			
+			case 'itopblock': // TO DO: Use DisplayBlock::FromTemplate here
+				$sBlockClass = $aAttributes['blockclass'];
+				$sBlockType = $aAttributes['type'];
+				$aExtraParams = array();
+				if (isset($aAttributes['linkage']))
+				{
+					$aExtraParams['linkage'] = $aAttributes['linkage'];
+				}
+
+				switch($aAttributes['encoding'])
+				{
+					case 'text/sibusql':
+					$oFilter = CMDBSearchFilter::FromSibusQL($sContent);
+					break;
+
+					case 'text/oql':
+					$oFilter = CMDBSearchFilter::FromOQL($sContent);
+					break;
+
+					case 'text/serialize':
+					default:
+					$oFilter = CMDBSearchFilter::unserialize($sContent);
+					break;
+				}
+				$oBlock = new $sBlockClass($oFilter, $sBlockType, false, $aExtraParams);
+				$oBlock->Display($oPage, 'block_'.$iBlockCount);
+				$iBlockCount++;
+			break;
+			
+			default:
+				// Unknown tag, just ignore it or now -- output an HTML comment
+				$oPage->add("<!-- unsupported tag: $sTag -->");
+		}
+	}
+	
+	/**
+	 * Unit test
+	 */
+	static public function UnitTest()
+	{
+		require_once('../application/startup.inc.php');
+		require_once("../application/itopwebpage.class.inc.php");
+		
+		$sTemplate = '<div class="page_header">
+		<div class="actions_details"><a href="#"><span>Actions</span></a></div>
+		<h1>$class$: <span class="hilite">$name$</span></h1>
+		<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/sibusql">CMDBChangeOpSetAttribute: objkey = $pkey$</itopblock>
+		</div>
+		<img src="../../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
+		<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizNetworkDevice: pkey = $pkey$</itopblock>
+		<itoptabs>
+			<itoptab name="Interfaces">
+				<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizInterface: device_id = $pkey$</itopblock>
+			</itoptab>
+			<itoptab name="Contacts">
+				<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizContact: PKEY IS contact_id IN (ContactsLinks: object_id = $pkey$)</itopblock>
+			</itoptab>
+			<itoptab name="Documents">
+				<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$)</itopblock>
+			</itoptab>
+		</itoptabs>';
+		
+		$oPage = new iTopWebPage('Unit Test', 3);
+		//$oPage->add("Template content: <pre>".htmlentities($sTemplate)."</pre>\n");
+		$oTemplate = new DisplayTemplate($sTemplate);
+		$oTemplate->Render($oPage, array('class'=>'Network device','pkey'=> 271, 'name' => 'deliversw01.mecanorama.fr', 'org_id' => 3));
+		$oPage->output();
+	}
+}
+
+//DisplayTemplate::UnitTest();
+
+?>

+ 12 - 0
application/templates/audit_category.html

@@ -0,0 +1,12 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../../images/clean.png" style="margin-top:-20px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Rules">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">AuditRule: category_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 301 - 0
application/ui.linkswidget.class.inc.php

@@ -0,0 +1,301 @@
+<?php
+require_once('../application/webpage.class.inc.php');
+require_once('../application/displayblock.class.inc.php');
+
+class UILinksWidget 
+{
+	protected $m_sClass;
+	protected $m_sAttCode;
+	protected $m_iInputId;
+	
+	public function __construct($sClass, $sAttCode, $iInputId)
+	{
+		$this->m_sClass = $sClass;
+		$this->m_sAttCode = $sAttCode;
+		$this->m_iInputId = $iInputId;
+	}
+	
+	public function Display(web_page $oPage, $oCurrentValuesSet = null)
+	{
+		$sHTMLValue = '';
+		$sTargetClass = self::GetTargetClass($this->m_sClass, $this->m_sAttCode);
+	    $aAllowedValues = MetaModel::GetAllowedValues_att($this->m_sClass, $this->m_sAttCode, array(), '');
+		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
+		$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+		$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
+		$sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass);
+		$sDefaultState = MetaModel::GetDefaultState($this->m_sClass);
+
+		$sLinkedClass = $oAttDef->GetLinkedClass();
+		foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef)
+		{
+			if ($sStateAttCode == $sAttCode)
+			{
+				// State attribute is always hidden from the UI
+			}
+			else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote))
+			{
+				$iFlags = MetaModel::GetAttributeFlags($this->m_sClass, $sDefaultState, $sAttCode);				
+				if ( !($iFlags & OPT_ATT_HIDDEN) && !($iFlags & OPT_ATT_READONLY) )
+				{
+					$aAttributes[] = $sAttCode;
+				}
+			}
+		}
+		$sAttributes = "['".implode("','", $aAttributes)."']";
+		if ($oCurrentValuesSet != null)
+		{
+			// Serialize the link set into a JSon object
+			$aCurrentValues = array();
+			$sRow = '{';
+			while($oLinkObj = $oCurrentValuesSet->Fetch())
+			{
+				foreach($aAttributes as $sLinkAttCode)
+				{
+					$sRow.= "\"$sLinkAttCode\": \"".addslashes($oLinkObj->Get($sLinkAttCode))."\", ";
+				}
+				$sRow .= "\"$sExtKeyToRemote\": ".$oLinkObj->Get($sExtKeyToRemote).'}';
+				$aCurrentValues[] = $sRow;
+			}
+			$sJSON = '['.implode(',', $aCurrentValues).']';
+		}
+
+		// Many values (or even a unknown list) display an autocomplete
+		if ( (count($aAllowedValues) == 0) || (count($aAllowedValues) > 20) )
+		{
+			// too many choices, use an autocomplete
+			// The input for the auto complete
+			$sTitle = $oAttDef->GetDescription();
+			$sHTMLValue .= "<script>\n";
+			$sHTMLValue .= "oLinkWidget{$this->m_iInputId} = new LinksWidget('{$this->m_iInputId}', '$sLinkedClass', '$sExtKeyToMe', '$sExtKeyToRemote', $sAttributes);\n";
+			$sHTMLValue .= "</script>\n";
+			$sHTMLValue .= $this->GetObjectPickerDialog($oPage, $sTargetClass, 'oLinkWidget'.$this->m_iInputId.'.OnOk');
+			$sHTMLValue .= $this->GetLinkObjectDialog($oPage, $this->m_iInputId);
+			$sHTMLValue .= "<input type=\"text\" id=\"ac_{$this->m_iInputId}\" size=\"35\" name=\"\" value=\"\" style=\"border: 1px solid red;\" />";
+			$sHTMLValue .= "<input type=\"button\" value=\" Add... \"  class=\"action\" onClick=\"oLinkWidget{$this->m_iInputId}.AddObject();\"/>";
+			$sHTMLValue .= "&nbsp;<input type=\"button\" value=\"Browse...\"  class=\"action\" onClick=\"return ManageObjects('$sTitle', '$sTargetClass', '$this->m_iInputId', '$sExtKeyToRemote');\"/>";
+			// another hidden input to store & pass the object's Id
+			$sHTMLValue .= "<input type=\"hidden\" id=\"id_ac_{$this->m_iInputId}\"/>\n";
+			$sHTMLValue .= "<input type=\"hidden\" id=\"{$this->m_iInputId}\" name=\"attr_{$this->m_sAttCode}\" value=\"\"/>\n";
+			$oPage->add_ready_script("\$('#{$this->m_iInputId}').val('$sJSON');\n\$('#ac_{$this->m_iInputId}').autocomplete('./ajax.render.php', { minChars:3, onItemSelect:selectItem, onFindValue:findValue, formatItem:formatItem, autoFill:true, keyHolder:'#id_ac_{$this->m_iInputId}', extraParams:{operation:'ui.linkswidget', sclass:'{$this->m_sClass}', attCode:'{$this->m_sAttCode}', max:30}});");
+		}
+		else
+		{
+			// Few choices, use a normal 'select'
+			$sHTMLValue = "<select name=\"attr_{$this->m_sAttCode}\"  id=\"{$this->m_iInputId}\">\n";
+			
+			foreach($aAllowedValues as $key => $value)
+			{
+				$sHTMLValue .= "<option value=\"$key\"$sSelected>$value</option>\n";
+			}
+			$sHTMLValue .= "</select>\n";
+		}
+		$sHTMLValue .= "<div id=\"{$this->m_iInputId}_values\">\n";
+		if ($oCurrentValuesSet != null)
+		{
+		 	// transform the DBObjectSet into a CMDBObjectSet !!!
+			$aLinkedObjects = $oCurrentValuesSet->ToArray(false);
+			if (count($aLinkedObjects) > 0)
+			{
+				$oSet = CMDBObjectSet::FromArray($sLinkedClass, $aLinkedObjects);
+				$oDisplayBlock = DisplayBlock::FromObjectSet($oSet, 'list');
+				$sHTMLValue .= $oDisplayBlock->GetDisplay($oPage, $this->m_iInputId.'_current', array('linkage' => $sExtKeyToMe));
+			}
+		}
+		$sHTMLValue .= "</div>\n";
+		return $sHTMLValue;
+	}
+	/**
+	 * This static function is called by the Ajax Page when there is a need to fill an autocomplete combo
+	 * @param $oPage web_page The ajax page used for the put^put (sent back to the browser
+	 * @param $oContext UserContext The context of the user (for limiting the search)
+	 * @param $sClass string The name of the class of the current object being edited
+	 * @param $sAttCode string The name of the attribute being edited
+	 * @param $sName string The partial name that was typed by the user
+	 * @param $iMaxCount integer The maximum number of items to return
+	 * @return void
+	 */	 	 	  	 	 	 	
+	static public function Autocomplete(web_page $oPage, UserContext $oContext, $sClass, $sAttCode, $sName, $iMaxCount)
+	{
+		$aAllowedValues = MetaModel::GetAllowedValues_att($sClass, $sAttCode, array() /* $aArgs */, $sName);
+		if ($aAllowedValues != null)
+		{
+			$iCount = $iMaxCount;
+			foreach($aAllowedValues as $key => $value)
+			{
+				$oPage->add($value."|".$key."\n");
+				$iCount--;
+				if ($iCount == 0) break;
+			}
+		}
+		else // No limitation to the allowed values
+		{
+			// Search for all the object of the linked class
+			$oAttDef = 	$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+			$sLinkedClass = $oAttDef->GetLinkedClass();
+			$sSearchClass = self::GetTargetClass($sClass, $sAttCode);
+			$oFilter = $oContext->NewFilter($sSearchClass);
+			$sSearchAttCode = MetaModel::GetNameAttributeCode($sSearchClass);
+			$oFilter->AddCondition($sSearchAttCode, $sName, 'Begins with');
+			$oSet = new CMDBObjectSet($oFilter, array($sSearchAttCode => true));
+			$iCount = 0;
+			while( ($iCount < $iMaxCount) && ($oObj = $oSet->fetch()) )
+			{
+				$oPage->add($oObj->GetName()."|".$oObj->GetKey()."\n");
+				$iCount++;
+			}
+		}
+	}
+
+	/**
+	 * This static function is called by the Ajax Page display a set of objects being linked
+	 * to the object being created	 
+	 * @param $oPage web_page The ajax page used for the put^put (sent back to the browser
+	 * @param $sClass string The name of the class 'linking class' which is the class of the objects to display
+	 * @param $sAttCode string The name of the attribute is the main object being created
+	 * @param $sSet JSON serialized set of objects
+	 * @return void
+	 */	 	 	  	 	 	 	
+	static public function RenderSet($oPage, $sClass, $sJSONSet, $sExtKeyToMe)
+	{
+		$aSet = json_decode($sJSONSet, true); // true means hash array instead of object
+		$oSet = CMDBObjectSet::FromScratch($sClass);
+		foreach($aSet as $aObject)
+		{
+			$oObj = MetaModel::NewObject($sClass);
+			foreach($aObject as $sAttCode => $value)
+			{
+				$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+				if ($oAttDef->IsExternalKey())
+				{
+					$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value); // @@ optimization, don't do & query per object in the set !
+					$oObj->Set($sAttCode, $oTargetObj);
+				}
+				else
+				{
+					$oObj->Set($sAttCode, $value);
+				}
+
+			}
+			$oSet->AddObject($oObj);
+		}
+		cmdbAbstractObject::DisplaySet($oPage, $oSet, $sExtKeyToMe);
+	}
+
+	
+	protected static function GetTargetClass($sClass, $sAttCode)
+	{
+		$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+		$sLinkedClass = $oAttDef->GetLinkedClass();
+		switch(get_class($oAttDef))
+		{
+			case 'AttributeLinkedSetIndirect':
+			$oLinkingAttDef = 	MetaModel::GetAttributeDef($sLinkedClass, $oAttDef->GetExtKeyToRemote());
+			$sTargetClass = $oLinkingAttDef->GetTargetClass();
+			break;
+
+			case 'AttributeLinkedSet':
+			$sTargetClass = $sLinkedClass;
+			break;
+		}
+		
+		return $sTargetClass;
+	}
+	
+	protected function GetObjectPickerDialog($oPage, $sTargetClass, $sOkFunction)
+	{
+		$sHTML = <<< EOF
+		<div class="jqmWindow" id="ManageObjectsDlg_{$this->m_iInputId}">
+		<div class="page_header"><h1 id="Manage_DlgTitle_{$this->m_iInputId}">Selected Objects</h1></div>
+		<table width="100%">
+			<tr>
+				<td>
+					<p>Selected objects:</p>
+					<button type="button" class="action" onClick="FilterLeft('$sTargetClass');"><span> Filter... </span></button>
+					<p><select id="selected_objects_{$this->m_iInputId}" size="10" multiple onChange="Manage_UpdateButtons('$this->m_iInputId')" style="width:300px;">
+					</select></p>
+				</td>
+				<td style="text-align:center; valign:middle;">
+					<p><button type="button" id="btn_add_objects_{$this->m_iInputId}" onClick="Manage_AddObjects('$this->m_iInputId');"> &lt;&lt; Add </button></p>
+					<p><button type="button" id="btn_remove_objects_{$this->m_iInputId}" onClick="Manage_RemoveObjects('$this->m_iInputId');"> Remove &gt;&gt; </button></p>
+				</td>
+				<td>
+					<p>Available objects:</p>
+					<button type="button" class="action" onClick="FilterRight('$sTargetClass');"><span> Filter... </span></button>
+					<p><select id="available_objects_{$this->m_iInputId}" size="10" multiple onChange="Manage_UpdateButtons('$this->m_iInputId')" style="width:300px;">
+					</select></p>
+				</td>
+			</tr>
+			<tr>
+				<td colspan="3">
+				<button type="button" class="jqmClose" onClick="$('#ManageObjectsDlg_{$this->m_iInputId}').jqmHide(); $sOkFunction('$sTargetClass', 'selected_objects')"> Ok </button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type="button" class="jqmClose"> Cancel</button>
+				</td>
+			</tr>
+		</table>
+		</div>
+EOF;
+		$oPage->add_ready_script("$('#ManageObjectsDlg_$this->m_iInputId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window
+		//$oPage->add_ready_script("UpdateObjectList('$sClass');");
+		return $sHTML;
+	}
+	
+	protected function GetLinkObjectDialog($oPage, $sId)
+	{
+		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
+		$sLinkedClass = $oAttDef->GetLinkedClass();
+		$sStateAttCode = MetaModel::GetStateAttributeCode($sLinkedClass);
+		$sDefaultState = MetaModel::GetDefaultState($sLinkedClass);
+		$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $this->m_sAttCode);
+		$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
+		$sExtKeyToRemote = $oAttDef->GetExtKeyToRemote();
+		
+		$sHTML = "<div class=\"jqmWindow\" id=\"LinkDlg_$sId\">\n";
+		$sHTML .= "<div class=\"page_header\"><h1 id=\"LinkObject_DlgTitle\">$sLinkedClass attributes</h1></div>\n";
+		$sHTML .= "<form>\n";
+		$index = 0;
+		$aAttrsMap = array();
+		foreach(MetaModel::ListAttributeDefs($sLinkedClass) as $sAttCode=>$oAttDef)
+		{
+			if ($sStateAttCode == $sAttCode)
+			{
+				// State attribute is always hidden from the UI
+				//$sHTMLValue = $this->GetState();
+				//$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+			}
+			else if (!$oAttDef->IsExternalField() && ($sAttCode != $sExtKeyToMe) && ($sAttCode != $sExtKeyToRemote))
+			{
+				$iFlags = MetaModel::GetAttributeFlags($sLinkedClass, $sDefaultState, $sAttCode);				
+				if ($iFlags & OPT_ATT_HIDDEN)
+				{
+					// Attribute is hidden, do nothing
+				}
+				else
+				{
+					if ($iFlags & OPT_ATT_READONLY)
+					{
+						// Attribute is read-only
+						$sHTMLValue = $this->GetAsHTML($sAttCode);
+					}
+					else
+					{
+						$sValue = ""; //$this->Get($sAttCode);
+						$sDisplayValue = ""; //$this->GetDisplayValue($sAttCode);
+						$sSubId = $sId.'_'.$index;
+						$aAttrsMap[$sAttCode] = $sSubId;
+						$index++;
+						$sHTMLValue = cmdbAbstractObject::GetFormElementForField($oPage, $sLinkedClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sSubId);
+					}
+					$aDetails[] = array('label' => $oAttDef->GetLabel(), 'value' => $sHTMLValue);
+				}
+			}
+		}
+		$sHTML .= $oPage->GetDetails($aDetails);
+		$sHTML .= "<button type=\"button\" class=\"jqmClose\" onClick=\"oLinkWidget$sId.OnLinkOk()\"> Ok </button>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<button type=\"button\" class=\"jqmClose\"  onClick=\"LinkWidget$sId.OnLinkCancel()\"> Cancel</button>\n";
+		$sHTML .= "</form>\n";
+		$sHTML .= "</div>\n";
+		$oPage->add_ready_script("$('#LinkDlg_$sId').jqm({overlay:70, modal:true, toTop:true});"); // jqModal Window
+		//$oPage->add_ready_script("UpdateObjectList('$sClass');");
+		return $sHTML;
+	}
+}
+?>

+ 257 - 0
application/uiwizard.class.inc.php

@@ -0,0 +1,257 @@
+<?php
+class UIWizard
+{
+	protected $m_oPage;
+	protected $m_sClass;
+	protected $m_sTargetState;
+	protected $m_aWizardSteps;
+	
+	public function __construct($oPage, $sClass, $sTargetState = '')
+	{
+		$this->m_oPage = $oPage;
+		$this->m_sClass = $sClass;
+		if (empty($sTargetState))
+		{
+			$sTargetState = MetaModel::GetDefaultState($sClass);
+		}
+		$this->m_sTargetState = $sTargetState;
+		$this->m_aWizardSteps = $this->ComputeWizardStructure();
+	}
+	
+	public function GetObjectClass() { return $this->m_sClass; }
+	public function GetTargetState() { return $this->m_sTargetState; }
+	public function GetWizardStructure() { return $this->m_aWizardSteps; }
+	
+	/**
+	 * Displays one step of the wizard
+	 */	 
+	public function DisplayWizardStep($aStep, $iStepIndex, &$iMaxInputId, &$aFieldsMap, $bFinishEnabled = false)
+	{
+		$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);
+		$aDetails = array();
+		$sJSHandlerCode = ''; // Javascript code to be executed each time this step of the wizard is entered
+		foreach($aStep as $sAttCode)
+		{
+			$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
+			$sAttLabel = $oAttDef->GetLabel();
+			$iOptions = isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode]) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0;
+	
+			$aPrerequisites = $oAttDef->GetPrerequisiteAttributes();
+			if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT))
+			{
+				$aFields[$sAttCode] = array();
+				foreach($aPrerequisites as $sCode)
+				{
+					$aFields[$sAttCode][$sCode] = '';
+				}
+			}
+			if (count($aPrerequisites) > 0)
+			{
+				$aOptions[] = 'Prerequisites: '.implode(', ', $aPrerequisites);
+			}
+			
+			$sFieldFlag = ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE)) ? ' <span class="hilite">*</span>' : '';
+			$oDefaultValuesSet = $oAttDef->GetDefaultValue(); // @@@ TO DO: get the object's current value if the object exists
+			$sHTMLValue = cmdbAbstractObject::GetFormElementForField($this->m_oPage, $this->m_sClass, $sAttCode, $oAttDef, $oDefaultValuesSet, '', "att_$iMaxInputId");
+			$aFieldsMap[$iMaxInputId] = $sAttCode;
+			$aDetails[] = array('label' => $oAttDef->GetLabel().$sFieldFlag, 'value' => "<div id=\"field_$iMaxInputId\">$sHTMLValue</div>");
+			if ($oAttDef->GetValuesDef() != null)
+			{
+				$sJSHandlerCode .= "\toWizardHelper.RequestAllowedValues('$sAttCode');\n";
+			}
+			if ($oAttDef->GetDefaultValue() != null)
+			{
+				$sJSHandlerCode .= "\toWizardHelper.RequestDefaultValue('$sAttCode');\n";
+			}
+			if ($oAttDef->IsLinkSet())
+			{
+				$sJSHandlerCode .= "\toLinkWidgetatt_$iMaxInputId.Init();";
+			}
+			$iMaxInputId++;
+		}
+		//$aDetails[] = array('label' => '', 'value' => '<input type="button" value="Next &gt;&gt;">');
+		$this->m_oPage->details($aDetails);
+		$sBackButtonDisabled = ($iStepIndex <= 1) ? 'disabled' : '';
+		$sDisabled = $bFinishEnabled ? '' : 'disabled';
+		$nbSteps = count($this->m_aWizardSteps['mandatory']) + count($this->m_aWizardSteps['optional']);
+		$this->m_oPage->add("<div style=\"text-align:center\">
+		<input type=\"button\" value=\"&lt;&lt; Back \" $sBackButtonDisabled onClick=\"GoToStep($iStepIndex, $iStepIndex - 1)\">
+		<input type=\"button\" value=\" Next &gt;&gt;\" onClick=\"GoToStep($iStepIndex, 1+$iStepIndex)\">
+		<input type=\"button\" value=\" Finish \" $sDisabled onClick=\"GoToStep($iStepIndex, 1+$nbSteps)\">
+		</div>\n");
+		$this->m_oPage->add("
+<script>
+function OnEnterStep{$iStepIndex}()
+{
+	oWizardHelper.ResetQuery();
+	
+$sJSHandlerCode
+
+	oWizardHelper.AjaxQueryServer();
+}
+</script>\n");
+		$this->m_oPage->add("</div>\n\n");
+	}	
+
+	/**
+	 * Display the final step of the wizard: a confirmation screen
+	 */	 	
+	public function DisplayFinalStep($iStepIndex, $aFieldsMap)
+	{
+		$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");
+		$this->m_oPage->add("<script>\n");
+		$this->m_oPage->add("function OnEnterStep$iStepIndex() {\n");
+		foreach($aFieldsMap as $iInputId => $sAttCode)
+		{
+			$this->m_oPage->add("\toWizardHelper.UpdateCurrentValue('$sAttCode');\n");		
+		}
+		$this->m_oPage->add("\toWizardHelper.Preview('object_preview');\n");		
+		$this->m_oPage->add("\t$('#wizard_json_obj').val(oWizardHelper.ToJSON());\n");		
+		$this->m_oPage->add("}\n");
+		$this->m_oPage->add("</script>\n");
+		$this->m_oPage->add("<div id=\"object_preview\">\n");
+		$this->m_oPage->add("</div>\n");
+		$this->m_oPage->add("<input type=\"submit\" value=\"Create {$this->m_sClass}\">\n");
+		$this->m_oPage->add("</form>\n");
+		$this->m_oPage->add("</div>\n");
+	}	
+	/**
+	 * Compute the order of the fields & pages in the wizard
+	 * @param $oPage iTopWebPage The current page (used to display error messages) 
+	 * @param $sClass string Name of the class
+	 * @param $sStateCode string Code of the target state of the object
+	 * @return hash Two dimensional array: each element represents the list of fields for a given page   
+	 */
+	protected function ComputeWizardStructure()
+	{
+		$aWizardSteps = array( 'mandatory' => array(), 'optional' => array());
+		$aFieldsDone = array(); // Store all the fields that are already covered by a previous step of the wizard
+	
+		$aStates = MetaModel::EnumStates($this->m_sClass);
+		
+		if ( (!empty($this->m_sTargetState)) && (count($aStates[$this->m_sTargetState]['attribute_list']) > 0) )
+		{
+			// Check all the fields that *must* be included in the wizard for this
+			// particular target state
+			$aFields = array();
+			foreach($aStates[$this->m_sTargetState]['attribute_list'] as $sAttCode => $iOptions)
+			{
+				$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
+				$sAttLabel = $oAttDef->GetLabel();
+		
+				if ($iOptions & (OPT_ATT_MANDATORY | OPT_ATT_MUSTCHANGE | OPT_ATT_MUSTPROMPT))
+				{
+					$aPrerequisites = $oAttDef->GetPrerequisiteAttributes();
+					$aFields[$sAttCode] = array();
+					foreach($aPrerequisites as $sCode)
+					{
+						$aFields[$sAttCode][$sCode] = '';
+					}
+				}
+			}
+			
+			// Now use the dependencies between the fields to order them
+			while(count($aFields) > 0)
+			{
+				$aCurrentStep = array();
+				foreach($aFields as $sAttCode => $aDependencies)
+				{
+					// All fields with no remaining dependencies can be entered at this
+					// step of the wizard
+					if (count($aDependencies) == 0)
+					{
+						$aCurrentStep[] = $sAttCode;
+						$aFieldsDone[$sAttCode] = '';
+						unset($aFields[$sAttCode]);
+						// Remove this field from the dependencies of the other fields
+						foreach($aFields as $sUpdatedCode => $aDummy)
+						{
+							// remove the dependency
+							unset($aFields[$sUpdatedCode][$sAttCode]);
+						}
+					}
+				}
+				if (count($aCurrentStep) == 0)
+				{
+					// This step of the wizard would contain NO field !
+					echo "<strong>Error:</strong> Circular reference in the dependencies between the fields.";
+					print_r($aFields);
+					break;
+				}
+				$aWizardSteps['mandatory'][] = $aCurrentStep;
+			}
+		}
+
+		// Now computes the steps to fill the optional fields
+		$sStateAttCode = MetaModel::GetStateAttributeCode($this->m_sClass);
+		$aFields = array(); // reset
+		foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAttDef)
+		{
+			$iOptions = (isset($aStates[$this->m_sTargetState]['attribute_list'][$sAttCode])) ? $aStates[$this->m_sTargetState]['attribute_list'][$sAttCode] : 0;				
+			if ( ($sStateAttCode != $sAttCode) &&
+				 (!$oAttDef->IsExternalField()) &&
+				 (($iOptions & (OPT_ATT_HIDDEN | OPT_ATT_READONLY)) == 0) &&
+				 (!isset($aFieldsDone[$sAttCode])) )
+				 
+			{
+				// 'State', external fields, read-only and hidden fields
+				// and fields that are already listed in the wizard
+				// are removed from the 'optional' part of the wizard
+				$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
+				$aPrerequisites = $oAttDef->GetPrerequisiteAttributes();
+				$aFields[$sAttCode] = array();
+				foreach($aPrerequisites as $sCode)
+				{
+					if (!isset($aFieldsDone[$sCode]))
+					{
+						// retain only the dependencies that were not covered
+						// in the 'mandatory' part of the wizard
+						$aFields[$sAttCode][$sCode] = '';
+					}
+				}
+			}
+		}
+		// Now use the dependencies between the fields to order them
+		while(count($aFields) > 0)
+		{
+			$aCurrentStep = array();
+			foreach($aFields as $sAttCode => $aDependencies)
+			{
+				// All fields with no remaining dependencies can be entered at this
+				// step of the wizard
+				if (count($aDependencies) == 0)
+				{
+					$aCurrentStep[] = $sAttCode;
+					$aFieldsDone[$sAttCode] = '';
+					unset($aFields[$sAttCode]);
+					// Remove this field from the dependencies of the other fields
+					foreach($aFields as $sUpdatedCode => $aDummy)
+					{
+						// remove the dependency
+						unset($aFields[$sUpdatedCode][$sAttCode]);
+					}
+				}
+			}
+			if (count($aCurrentStep) == 0)
+			{
+				// This step of the wizard would contain NO field !
+				$oPage->add("<strong>Error:</strong> Circular reference in the dependencies between the fields.");
+				print_r($aFields);
+				break;
+			}
+			$aWizardSteps['optional'][] = $aCurrentStep;
+		}
+		
+		return $aWizardSteps;
+	
+	} 
+}
+?>

+ 106 - 0
application/usercontext.class.inc.php

@@ -0,0 +1,106 @@
+<?php
+require_once('../core/cmdbobject.class.inc.php');
+require_once('../core/userrights.class.inc.php');
+/**
+ * Helper class to capture a user's restrictions (access rights, profiles...) as a set of limiting conditions
+ *
+ * **** NOW OBSOLETE *** SHOULD BE REPLACED EVERYWHERE BY UserRights *****
+ * 
+ *
+ *
+ *     
+ * Usage:
+ * 1) Build the user's context (from her rights, a lookup in the database, a cookie, whatever)
+ * 	$oContext = new UserContext();
+ *  $oContext->AddCondition('SomeClass', 'someFilter', 'SomeValue', '=');
+ *   ...
+ *
+ * 2) Use the restrictions contained in the context when retrieving objects either when:
+ * getting directly an instance of an object
+ * $oObj = $oContext->GetObject('myClass', 'someKey'); // Instead of $oObj = MetaModel::GetObject('Klass', 'someKey');
+ * or when building a new search filter
+ * $oFilter = $oContext->NewFilter('myClass'); // Instead of $oFilter = new CMDBSearchFilter('Klass');
+ */
+class UserContext
+{
+	/**
+	 * Hash array to store the restricting conditions by myClass
+	 */
+	protected $m_aConditions;
+
+	/**
+	 * Constructor
+	 */
+	public function __construct()
+	{
+		$this->m_aConditions = array();
+	}
+	/**
+	 * Create a new search filter for the given class of objects that already contains the context's restrictions
+	 */
+	public function NewFilter($sClass)
+	{
+		return UserRights::GetFilter($sClass);
+		/*
+		$oFilter = new CMDBSearchFilter($sClass);
+		foreach($this->m_aConditions as $sConditionClass => $aConditionList)
+		{
+			// Add to the filter all the conditions of the parent classes of this class
+			if ($this->IsSubclass($sConditionClass,$sClass))
+			{
+				foreach($aConditionList as $sFilterCode => $aCondition)
+				{
+					$oFilter->AddCondition($sFilterCode, $aCondition['value'], $aCondition['operator']);
+				}
+			}
+		}
+		return $oFilter;
+		*/
+	}
+	/**
+	 * Retrieve an instance of an object (if allowed by the context)
+	 */
+	public function GetObject($sClass, $sKey)
+	{
+		$oObject = null;
+		$oFilter = $this->NewFilter($sClass);
+		$oFilter->AddCondition('pkey', $sKey, '=');
+		$oSet = new CMDBObjectSet($oFilter);
+		if ($oSet->Count() > 0)
+		{
+			$oObject = $oSet->Fetch();
+		}
+		return $oObject;
+	}
+	
+	/**
+	 * Add a restriction to the context for a given class of objects (and all its persistent subclasses)
+	 */
+	public function AddCondition($sClass, $sFilterCode, $value, $sOperator)
+	{
+		if(!isset($this->m_aConditions[$sClass]))
+		{
+			$this->m_aConditions[$sClass] = array();
+		}
+		$this->m_aConditions[$sClass][$sFilterCode] = array('value'=>$value, 'operator'=>$sOperator);
+	}
+	
+	/**
+	 * Check if a given class is a subclass of (or same as) another one
+	 */
+	protected function IsSubclass($sParentClass, $sSubclass)
+	{
+		$bResult = false;
+		if ($sParentClass == $sSubclass)
+		{
+			$bResult = true;
+		}
+		else
+		{
+			$aParentList = MetaModel::EnumParentClasses($sSubclass);
+			$bResult = in_array($sParentClass, $aParentList);
+		}
+		return $bResult;
+	}
+}
+?>

+ 80 - 0
application/utils.inc.php

@@ -0,0 +1,80 @@
+<?php
+
+define('CONFIGFILE', '../config.txt');
+
+class utils
+{
+	private static $m_aConfig = null;
+
+	public static function ReadParam($sName, $defaultValue = "")
+	{
+		return isset($_REQUEST[$sName]) ? $_REQUEST[$sName] : $defaultValue;
+	}
+	
+	public static function ReadPostedParam($sName, $defaultValue = "")
+	{
+		return isset($_POST[$sName]) ? $_POST[$sName] : $defaultValue;
+	}
+	
+	public static function GetNewTransactionId()
+	{
+		// TO DO implement the real mechanism here
+		return sprintf("%08x", rand(0,2000000000));
+	}
+	
+	public static function IsTransactionValid($sId)
+	{
+		// TO DO implement the real mechanism here
+		return true;
+	}
+	
+	public static function ReadFromFile($sFileName)
+	{
+		if (!file_exists($sFileName)) return false;
+		return file_get_contents($sFileName);
+	}
+
+	public static function ReadConfig()
+	{
+		self::$m_aConfig = array();
+
+		$sConfigContents = self::ReadFromFile(CONFIGFILE);
+		if (!$sConfigContents) throw new Exception("Could not load file ".CONFIGFILE);
+
+		foreach (explode("\n", $sConfigContents) as $sLine)
+		{
+			$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; 
+			}
+		}
+	}
+
+	public static function GetConfig($sParamName, $defaultValue = "")
+	{
+		if (is_null(self::$m_aConfig))
+		{
+			self::ReadConfig();
+		}
+
+		if (array_key_exists($sParamName, self::$m_aConfig))
+		{
+			return self::$m_aConfig[$sParamName];
+		}
+		else
+		{
+			return $defaultValue;
+		}
+	}
+}
+?>

+ 289 - 0
application/webpage.class.inc.php

@@ -0,0 +1,289 @@
+<?php
+/**
+ * Simple helper class to ease the production of HTML pages
+ *
+ * This class provide methods to add content, scripts, includes... to a web page 
+ * and renders the full web page by putting the elements in the proper place & order
+ * when the output() method is called.
+ * Usage:
+ * 	$oPage = new web_page("Title of my page");
+ *	$oPage->p("Hello World !");
+ *	$oPage->output();
+ */
+class web_page
+{
+    protected $s_title;
+    protected $s_content;
+    protected $a_scripts;
+    protected $a_styles;
+    protected $a_include_scripts;
+    protected $a_include_stylesheets;
+    protected $a_headers;
+    
+    public function __construct($s_title)
+    {
+        $this->s_title = $s_title;
+        $this->s_content = "";
+        $this->a_scripts = array();
+        $this->a_styles = array();
+        $this->a_linked_scripts = array();
+        $this->a_linked_stylesheets = array();
+        $this->a_headers = array();
+        ob_start(); // Start capturing the output
+    }
+	
+	/**
+	 * Change the title of the page after its creation
+	 */
+    public function set_title($s_title)
+    {
+        $this->s_title = $s_title;
+    }
+    
+	/**
+	 * Add any text or HTML fragment to the body of the page
+	 */
+    public function add($s_html)
+    {
+        $this->s_content .= $s_html;
+    }
+    
+	/**
+	 * Add a paragraph to the body of the page
+	 */
+    public function p($s_html)
+    {
+        $this->add($this->GetP($s_html));
+    }
+    
+	/**
+	 * Add a paragraph to the body of the page
+	 */
+    public function GetP($s_html)
+    {
+        return "<p>$s_html</p>\n";
+    }
+    
+	public function table($aConfig, $aData, $aParams = array())
+	{
+		$this->add($this->GetTable($aConfig, $aData, $aParams));
+	}
+	
+	public function GetTable($aConfig, $aData, $aParams = array())
+	{
+		$oAppContext = new ApplicationContext();
+		
+		static $iNbTables = 0;
+		$iNbTables++;
+		$sHtml = "";
+		$sHtml .= "<table class=\"listResults\">\n";
+		$sHtml .= "<thead>\n";
+		$sHtml .= "<tr>\n";
+		foreach($aConfig as $sName=>$aDef)
+		{
+			$sHtml .= "<th title=\"".$aDef['description']."\">".$aDef['label']."</th>\n";
+		}
+		$sHtml .= "</tr>\n";
+		$sHtml .= "</thead>\n";
+		$sHtml .= "<tbody>\n";
+		foreach($aData as $aRow)
+		{
+			if (false) //(isset($aParams['preview']) && $aParams['preview'])
+			{
+				$sHtml .= "<tr id=\"Row_".$iNbTables."_".$aRow['key']."\" onClick=\"DisplayPreview(".$iNbTables.",".$aRow['key'].",'".$aParams['class']."')\">\n";
+			}
+			else if (isset($aRow['key']))
+			{
+				$sHtml .= "<tr onDblClick=\"DisplayDetails(".$aRow['key'].",'".$aParams['class']."')\">\n";
+			}
+			else
+			{
+				$sHtml .= "<tr>\n";
+			}
+			foreach($aConfig as $sName=>$aAttribs)
+			{
+				$sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : '';
+				if ($sName != 'key')
+				{
+					$sValue = ($aRow[$sName] === '') ? '&nbsp;' : $aRow[$sName];
+					$sHtml .= "<td $sClass>$sValue</td>\n";
+				}
+				else
+				{
+					$sUIPage = cmdbAbstractObject::ComputeUIPage($aParams['class']);
+					$sHtml .= "<td><a class=\"no-arrow\" href=\"$sUIPage?operation=details&id=".$aRow['key']."&class=".$aParams['class']."&".$oAppContext->GetForLink()."\"><img src=\"../images/zoom.gif\" title=\"Details\" border=\"0\"></a></td>\n";
+				}
+			}
+			$sHtml .= "</tr>\n";
+		}
+		$sHtml .= "</tbody>\n";
+		$sHtml .= "</table>\n";
+		return $sHtml;
+	}
+    
+	/**
+	 * Add some Javascript to the header of the page
+	 */
+    public function add_script($s_script)
+    {
+        $this->a_scripts[] = $s_script;
+    }
+    
+	/**
+	 * Add some Javascript to the header of the page
+	 */
+    public function add_ready_script($s_script)
+    {
+        // Do nothing silently... this is not supported by this type of page...
+    }
+	/**
+	 * Add some CSS definitions to the header of the page
+	 */
+    public function add_style($s_style)
+    {
+        $this->a_styles[] = $s_style;
+    }
+
+	/**
+	 * Add a script (as an include, i.e. link) to the header of the page
+	 */
+    public function add_linked_script($s_linked_script)
+    {
+        $this->a_linked_scripts[] = $s_linked_script;
+    }
+
+	/**
+	 * Add a CSS stylesheet (as an include, i.e. link) to the header of the page
+	 */
+    public function add_linked_stylesheet($s_linked_stylesheet, $s_condition = "")
+    {
+        $this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition);
+    }
+
+	/**
+	 * Add some custom header to the page
+	 */
+    public function add_header($s_header)
+    {
+        $this->a_headers[] = $s_header;
+    }
+
+	/**
+	 * Add needed headers to the page so that it will no be cached
+	 */
+    public function no_cache()
+    {
+        $this->add_header("Cache-Control: no-cache, must-revalidate");  // HTTP/1.1
+        $this->add_header("Expires: Fri, 17 Jul 1970 05:00:00 GMT");    // Date in the past
+    }
+
+	/**
+	 * Build a special kind of TABLE useful for displaying the details of an object from a hash array of data
+	 */
+	public function details($aFields)
+	{
+
+		$this->add($this->GetDetails($aFields));
+    }
+	
+	/**
+	 * Build a special kind of TABLE useful for displaying the details of an object from a hash array of data
+	 */
+	public function GetDetails($aFields)
+	{
+		$sHtml = "<table>\n";
+		$sHtml .= "<tbody>\n";
+		foreach($aFields as $aAttrib)
+		{
+			$sHtml .= "<tr>\n";
+			// By Rom, for csv import, proposed to show several values for column selection
+			if (is_array($aAttrib['value']))
+			{
+				$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".implode("</td><td>", $aAttrib['value'])."</td>\n";
+			}
+			else
+			{
+				$sHtml .= "<td class=\"label\">".$aAttrib['label']."</td><td>".$aAttrib['value']."</td>\n";
+			}
+    		$sHtml .= "</tr>\n";
+		}
+		$sHtml .= "</tbody>\n";
+		$sHtml .= "</table>\n";
+		return $sHtml;
+    }
+	
+	/**
+	 * Outputs (via some echo) the complete HTML page by assembling all its elements
+	 */
+    public function output()
+    {
+        foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        $s_captured_output = ob_get_contents();
+        ob_end_clean();
+        echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
+        echo "<html>\n";
+        echo "<head>\n";
+        echo "<title>{$this->s_title}</title>\n";
+        foreach($this->a_linked_scripts as $s_script)
+        {
+            echo "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
+        }
+        if (count($this->a_scripts)>0)
+        {
+            echo "<script type=\"text/javascript\">\n";
+            foreach($this->a_scripts as $s_script)
+            {
+                echo "$s_script\n";
+            }
+            echo "</script>\n";
+        }
+        foreach($this->a_linked_stylesheets as $a_stylesheet)
+        {
+			if ($a_stylesheet['condition'] != "")
+			{
+				echo "<!--[if {$a_stylesheet['condition']}]>\n";
+			}
+            echo "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$a_stylesheet['link']}\" />\n";
+			if ($a_stylesheet['condition'] != "")
+			{
+				echo "<![endif]-->\n";
+			}
+        }
+        
+        if (count($this->a_styles)>0)
+        {
+            echo "<style>\n";
+            foreach($this->a_styles as $s_style)
+            {
+                echo "$s_style\n";
+            }
+            echo "</style>\n";
+        }
+        echo "</head>\n";
+        echo "<body>\n";
+        echo $this->s_content;
+        if (trim($s_captured_output) != "")
+        {
+            echo "<div class=\"raw_output\">$s_captured_output</div>\n";
+        }
+        echo "</body>\n";
+        echo "</html>\n";
+    }
+
+	/**
+	 * Build a series of hidden field[s] from an array
+	 */
+	 // By Rom - je verrais bien une serie d'outils pour gerer des parametres que l'on retransmet entre pages d'un wizard...
+	 //          ptet deriver webpage en webwizard
+	public function add_input_hidden($sLabel, $aData)
+	{
+		foreach($aData as $sKey=>$sValue)
+		{
+			$this->add("<input type=\"hidden\" name=\"".$sLabel."[$sKey]\" value=\"$sValue\">");
+		}
+	}
+}
+?>

+ 204 - 0
application/wizardhelper.class.inc.php

@@ -0,0 +1,204 @@
+<?php
+require_once('../application/uiwizard.class.inc.php');
+
+class WizardHelper
+{
+	protected $m_aData;
+	
+	public function __construct()
+	{
+	}
+	
+	public function GetTargetObject()
+	{
+		$oObj = MetaModel::NewObject($this->m_aData['m_sClass']);
+		foreach($this->m_aData['m_aCurrentValues'] as $iIndex => $value)
+		{
+			$sAttCode = array_search($iIndex, $this->m_aData['m_oFieldsMap']);
+			// Because this is stored in a Javascript array, unused indexes
+			// are filled with null values
+			if ( ($sAttCode !== false) && ($value !== null))
+			{
+				$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
+				if (($oAttDef->IsLinkSet()) && ($value != '') )
+				{
+					// special handling for lists
+					// assumes this is handled as an array of objects
+					// thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...]
+					$aData = json_decode($value, true); // true means decode as a hash array (not an object)
+					// Check what are the meaningful attributes
+					$aFields = $this->GetLinkedWizardStructure($oAttDef);
+					$sLinkedClass = $oAttDef->GetLinkedClass();
+					$aLinkedObjectsArray = array();
+					if (!is_array($aData))
+					{
+						echo ("aData: '$aData' (value: '$value')\n");
+					}
+					foreach($aData as $aLinkedObject)
+					{
+						$oLinkedObj = MetaModel::NewObject($sLinkedClass);
+						foreach($aFields as $sLinkedAttCode)
+						{
+							if ( isset($aLinkedObject[$sLinkedAttCode]) && ($aLinkedObject[$sLinkedAttCode] !== null) )
+							{
+								$sLinkedAttDef = MetaModel::GetAttributeDef($sLinkedClass, $sLinkedAttCode);
+								if (($sLinkedAttDef->IsExternalKey()) && ($aLinkedObject[$sLinkedAttCode] != '') )
+								{
+									// For external keys: load the target object so that external fields
+									// get filled too
+									$oTargetObj = MetaModel::GetObject($sLinkedAttDef->GetTargetClass(), $aLinkedObject[$sLinkedAttCode]);
+									$oLinkedObj->Set($sLinkedAttCode, $oTargetObj);
+								}
+								else
+								{
+									$oLinkedObj->Set($sLinkedAttCode, $aLinkedObject[$sLinkedAttCode]);
+								}
+							}
+						}
+						$aLinkedObjectsArray[] = $oLinkedObj;
+					}
+					$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
+					$oObj->Set($sAttCode, $oSet);
+				}
+				else if (($oAttDef->IsExternalKey()) && ($value != '') )
+				{
+					// For external keys: load the target object so that external fields
+					// get filled too
+					$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value);
+					$oObj->Set($sAttCode, $oTargetObj);
+				}
+				else
+				{
+					$oObj->Set($sAttCode, $value);
+				}	
+			}
+		}
+		return $oObj;
+	}
+	
+	public function GetFieldsForDefaultValue()
+	{
+		return $this->m_aData['m_aDefaultValueRequested'];
+	}
+	
+	public function SetDefaultValue($sAttCode, $value)
+	{
+		// Protect against a request for a non existing field
+		if (isset($this->m_aData['m_oFieldsMap'][$sAttCode]))
+		{
+			$iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode];
+			$oAttDef = MetaModel::GetAttributeDef($this->m_aData['m_sClass'], $sAttCode);
+			if ($oAttDef->GetEditClass() == 'List')
+			{
+				// special handling for lists
+				// this as to be handled as an array of objects
+				// thus encoded in json like: [ { name:'link1', 'id': 123}, { name:'link2', 'id': 124}...]
+				// NOT YET IMPLEMENTED !!
+				$sLinkedClass = $oAttDef->GetLinkedClass();
+				$oSet = $value;
+				$aData = array();
+				$aFields = $this->GetLinkedWizardStructure($oAttDef);
+				while($oSet->fetch())
+				{
+					foreach($aFields as $sLinkedAttCode)
+					{
+						$aRow[$sAttCode] = $oLinkedObj->Get($sLinkedAttCode);
+					}
+					$aData[] = $aRow;
+				}
+				$this->m_aData['m_aDefaultValue'][$iIndex] = json_encode($aData);
+				
+			}
+			else
+			{
+				// Normal handling for all other scalar attributes
+				$this->m_aData['m_aDefaultValue'][$iIndex] = $value;
+			}
+		}
+	}
+	
+	public function GetFieldsForAllowedValues()
+	{
+		return $this->m_aData['m_aAllowedValuesRequested'];
+	}
+	
+	public function SetAllowedValuesHtml($sAttCode, $sHtml)
+	{
+		// Protect against a request for a non existing field
+		if (isset($this->m_aData['m_oFieldsMap'][$sAttCode]))
+		{
+			$iIndex = $this->m_aData['m_oFieldsMap'][$sAttCode];
+			$this->m_aData['m_aAllowedValues'][$iIndex] = $sHtml;
+		}
+	}
+	
+	public function ToJSON()
+	{
+		return json_encode($this->m_aData);
+	}
+	
+	static public function FromJSON($sJSON)
+	{
+		$oWizHelper = new WizardHelper();
+		if (get_magic_quotes_gpc())
+		{
+			$sJSON = stripslashes($sJSON);
+		}
+		$aData = json_decode($sJSON, true); // true means hash array instead of object
+		$oWizHelper->m_aData = $aData;
+		return $oWizHelper;
+	}
+	
+	protected function GetLinkedWizardStructure($oAttDef)
+	{
+		$oWizard = new UIWizard(null, $oAttDef->GetLinkedClass());
+		$aWizardSteps = $oWizard->GetWizardStructure();
+		$aFields = array();
+		$sExtKeyToMeCode = $oAttDef->GetExtKeyToMe();
+		// Retrieve as a flat list, all the attributes that are needed to create
+		// an object of the linked class and put them into a flat array, except
+		// the attribute 'ext_key_to_me' which is a constant in our case
+		foreach($aWizardSteps as $sDummy => $aMainSteps)
+		{
+			// 2 entries: 'mandatory' and 'optional'
+			foreach($aMainSteps as $aSteps)
+			{
+				// One entry for each step of the wizard
+				foreach($aSteps as $sAttCode)
+				{
+					if ($sAttCode != $sExtKeyToMeCode)
+					{
+						$aFields[] = $sAttCode;
+					}
+				}
+			}
+		}
+		return $aFields;
+	}
+	
+	static function ParseJsonSet($oMe, $sLinkClass, $sExtKeyToMe, $sJsonSet)
+	{
+		$aSet = json_decode($sJsonSet, true); // true means hash array instead of object
+		$oSet = CMDBObjectSet::FromScratch($sLinkClass);
+		foreach($aSet as $aLinkObj)
+		{
+			$oLink = MetaModel::NewObject($sLinkClass);
+			foreach($aLinkObj as $sAttCode => $value)
+			{
+				$oAttDef = MetaModel::GetAttributeDef($sLinkClass, $sAttCode);
+				if (($oAttDef->IsExternalKey()) && ($value != '') )
+				{
+					// For external keys: load the target object so that external fields
+					// get filled too
+					$oTargetObj = MetaModel::GetObject($oAttDef->GetTargetClass(), $value);
+					$oLink->Set($sAttCode, $oTargetObj);
+				}
+				$oLink->Set($sAttCode, $value);
+			}
+			$oLink->Set($sExtKeyToMe, $oMe->GetKey());
+			$oSet->AddObject($oLink);
+		}
+		return $oSet;
+	}
+}
+?>

+ 36 - 0
application/xmlpage.class.inc.php

@@ -0,0 +1,36 @@
+<?php
+require_once("../application/webpage.class.inc.php");
+/**
+ * Simple web page with no includes or fancy formatting, useful to generateXML documents
+ * The page adds the content-type text/XML and the encoding into the headers
+ */
+class XMLPage extends web_page
+{
+    function __construct($s_title)
+    {
+        parent::__construct($s_title);
+		$this->add_header("Content-type: text/xml; charset=utf-8");
+		$this->add_header("Cache-control: no-cache");
+		$this->add_header("Content-location: export.xml");
+		$this->add("<?xml version=\"1.0\" encoding=\"UTF-8\"?".">\n");
+    }	
+
+    public function output()
+    {
+		$this->add_header("Content-Length: ".strlen(trim($this->s_content)));
+        foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        echo trim($this->s_content);
+    }
+
+    public function small_p($sText)
+    {
+	}
+	
+	public function table($aConfig, $aData, $aParams = array())
+	{
+	}
+}
+?>

+ 284 - 0
business/ChangeMgmt.php

@@ -0,0 +1,284 @@
+<?php
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A Change Ticket
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizChangeTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Change",
+			"description" => "Change ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",  
+			"state_attcode" => "ticket_status",
+			"reconc_keys" => array("title"),
+			"db_table" => "change_ticket",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/change.html",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"TicketID", "description"=>"Refence number ofr this change", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Change", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeString("type", array("label"=>"Change Type", "description"=>"Type of the Change", "allowed_values"=>new ValueSetEnum("Routine, Normal, Emergency"), "sql"=>"type", "default_value"=>"Routine", "is_null_allowed"=>false, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeString("domain", array("label"=>"Domain", "description"=>"Domain for the Change", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"domain", "default_value"=>"Desktop", "is_null_allowed"=>false, "depends_on"=>array())));
+
+    MetaModel::Init_AddAttribute(new AttributeString("reason", array("label"=>"Reason for change", "description"=>"Reason for the Change", "allowed_values"=>null, "sql"=>"reason", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+  
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("requestor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Requestor", "description"=>"who is requesting this change", "allowed_values"=>null, "sql"=>"requestor_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("requestor_mail", array("label"=>"Requested by", "description"=>"mail of user requesting this change", "allowed_values"=>null, "extkey_attcode"=> 'requestor_id', "target_attcode"=>"email")));
+
+    
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Validated,Rejected,PlannedScheduled,Approved,NotApproved,Implemented,Monitored, Closed"), "sql"=>"change_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array())));
+		// SetPossibleValues("status",array("Open","Monitored","Closed"));
+
+		MetaModel::Init_AddAttribute(new AttributeDate("creation_date", array("label"=>"Creation date", "description"=>"Change creation date", "allowed_values"=>null, "sql"=>"creation_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+    	// définir une date de défaut à maintenant, alias creation ou modification du ticket
+		MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Start date", "description"=>"Time the change is expected to start", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"End Date", "description"=>"Date when the change is supposed to end", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("close_date", array("label"=>"Closed Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Risk Assessment", "description"=>"Impact of the change", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Managed by Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name")));  
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array('workgroup_id'))));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("agent_name", array("label"=>"Managed by Agent", "description"=>"name of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"name")));
+
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisorgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Supervisor group", "description"=>"which workgroup is supervising ticket", "allowed_values"=>null, "sql"=>"supervisorgroup_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("supervisorgroup_name", array("label"=>"Supervise by Workgroup", "description"=>"name of the group supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisorgroup_id', "target_attcode"=>"name")));  
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("supervisor_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Supervisor", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"supervisor_id", "is_null_allowed"=>true, "depends_on"=>array('supervisorgroup_id'))));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("supervisor_name", array("label"=>"Managed by Supervisor", "description"=>"name of agent supervising the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'supervisor_id', "target_attcode"=>"name")));
+
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("managergroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Manager group", "description"=>"which workgroup is approving ticket", "allowed_values"=>null, "sql"=>"managergroup_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("managergroup_name", array("label"=>"Approved by group", "description"=>"name of workgroup approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'managergroup_id', "target_attcode"=>"name")));  
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("manager_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Manager", "description"=>"who is approving the ticket", "allowed_values"=>null, "sql"=>"manager_id", "is_null_allowed"=>true, "depends_on"=>array('managergroup_id'))));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("manager_name", array("label"=>"Approved by Agent", "description"=>"name of agent approving the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'manager_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeEnum("outage", array("label"=>"Planned Outage", "description"=>"Flag to define if there is a planned outage", "allowed_values"=>new ValueSetEnum("Yes,No"), "sql"=>"outage", "default_value"=>"No", "is_null_allowed"=>false, "depends_on"=>array())));
+
+
+		MetaModel::Init_AddAttribute(new AttributeText("change_request", array("label"=>"Change Request", "description"=>"Description of Change required", "allowed_values"=>null, "sql"=>"change_req", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("change_log", array("label"=>"Implementation log", "description"=>"List all action performed during the change", "allowed_values"=>null, "sql"=>"change_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeText("fallback", array("label"=>"Fallback plan", "description"=>"Instruction to come back to former situation", "allowed_values"=>null, "sql"=>"fallback", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
+		
+		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are impacted by this change", "linked_class"=>"lnkInfraChangeTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("title");
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("domain");
+		MetaModel::Init_AddFilterFromAttribute("customer_id");
+		MetaModel::Init_AddFilterFromAttribute("requestor_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_status");
+		MetaModel::Init_AddFilterFromAttribute("creation_date");
+		MetaModel::Init_AddFilterFromAttribute("start_date");
+		MetaModel::Init_AddFilterFromAttribute("last_update");
+		MetaModel::Init_AddFilterFromAttribute("end_date");
+		MetaModel::Init_AddFilterFromAttribute("close_date");
+		MetaModel::Init_AddFilterFromAttribute("workgroup_id");
+		MetaModel::Init_AddFilterFromAttribute("workgroup_name");
+		MetaModel::Init_AddFilterFromAttribute("supervisorgroup_id");
+		MetaModel::Init_AddFilterFromAttribute("managergroup_id");
+		MetaModel::Init_AddFilterFromAttribute("supervisor_id");
+		MetaModel::Init_AddFilterFromAttribute("manager_id");
+		MetaModel::Init_AddFilterFromAttribute("agent_id");
+		MetaModel::Init_AddFilterFromAttribute("impact");
+		MetaModel::Init_AddFilterFromAttribute("assignment_count");
+		MetaModel::Init_AddFilterFromAttribute("outage");
+
+		// doit-on aussi ajouter un filtre sur les extfields lié à une extkey ? ici le name de l'agent?
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name','title', 'customer_id','type','domain','requestor_id','change_request','ticket_status', 'outage','impact', 'last_update', 'start_date','end_date', 'assignment_count', 'workgroup_id','agent_id','supervisorgroup_id','supervisor_id','managergroup_id','manager_id','change_log','fallback')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('name', 'title', 'customer_id', 'ticket_status','outage','start_date','type')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'title', 'customer_id', 'ticket_status','type', 'outage','requestor_id','workgroup_id','agent_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'customer_id', 'ticket_status','type', 'outage','workgroup_id','agent_id')); // Criteria of the advanced search form
+
+		// State machine
+		MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_MANDATORY, 'title' => OPT_ATT_MANDATORY, 'reason' => OPT_ATT_MANDATORY, 'impacted_infra_manual' => OPT_ATT_MANDATORY,
+												 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+		MetaModel::Init_DefineState("Validated", array("label"=>"Validated", "description"=>"Ticket is approved", "attribute_inherit"=>null,
+												"attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'managergroup_id' => OPT_ATT_MANDATORY, 'supervisorgroup_id' => OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("Rejected", array("label"=>"Rejected", "description"=>"This ticket is not approved", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+		MetaModel::Init_DefineState("PlannedScheduled", array("label"=>"Planned&Scheduled", "description"=>"Evaluation is done for this change", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY, 'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_MANDATORY, 'impact' => OPT_ATT_MANDATORY, 'workgroup_id' => OPT_ATT_MANDATORY, 'change_log' => OPT_ATT_MUSTCHANGE,'fallback' => OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("Approved", array("label"=>"Approved", "description"=>"Ticket is approved by CAB", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+		MetaModel::Init_DefineState("NotApproved", array("label"=>"Not Approved", "description"=>"Ticket has not been approved by CAB", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+		MetaModel::Init_DefineState("Implemented", array("label"=>"Implementation", "description"=>"Work is in progress for this ticket", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+		MetaModel::Init_DefineState("Monitored", array("label"=>"Monitored", "description"=>"Change performed is now monitored", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY,'customer_id' => OPT_ATT_READONLY,'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN)));
+	  	MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array('customer_id' => OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY)));
+
+		MetaModel::Init_DefineStimulus("ev_validate", new StimulusUserAction(array("label"=>"Validate this change", "description"=>"Make sure it is a valid change request")));
+		MetaModel::Init_DefineStimulus("ev_reject", new StimulusUserAction(array("label"=>"Reject this change", "description"=>"This change request is rejected because it is a non valid one")));
+		MetaModel::Init_DefineStimulus("ev_reopen", new StimulusUserAction(array("label"=>"Modify this change", "description"=>"Update change request to make it valid")));
+		MetaModel::Init_DefineStimulus("ev_plan", new StimulusUserAction(array("label"=>"Plan this change", "description"=>"Plan and Schedule this change for validation")));
+	  	MetaModel::Init_DefineStimulus("ev_approve", new StimulusUserAction(array("label"=>"Approve this change", "description"=>"This change is approved by CAB")));
+		MetaModel::Init_DefineStimulus("ev_replan", new StimulusUserAction(array("label"=>"Update planning and schedule", "description"=>"Modify Plan and Schedule in order to have this change re-validated")));
+		MetaModel::Init_DefineStimulus("ev_notapprove", new StimulusUserAction(array("label"=>"Not approve this change", "description"=>"This change is not approved by CAB")));
+		MetaModel::Init_DefineStimulus("ev_implement", new StimulusUserAction(array("label"=>"Implement this change", "description"=>"Implementation pahse for current change")));
+		MetaModel::Init_DefineStimulus("ev_monitor", new StimulusUserAction(array("label"=>"Monitor this change", "description"=>"Starting monitoring period for this change")));
+		MetaModel::Init_DefineStimulus("ev_finish", new StimulusUserAction(array("label"=>"Close change", "description"=>"Change is done, and can be closed")));
+
+		MetaModel::Init_DefineTransition("New", "ev_validate", array("target_state"=>"Validated", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("New", "ev_reject", array("target_state"=>"Rejected", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Rejected", "ev_reopen", array("target_state"=>"New", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Validated", "ev_plan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("PlannedScheduled", "ev_approve", array("target_state"=>"Approved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("PlannedScheduled", "ev_notapprove", array("target_state"=>"NotApproved", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("NotApproved", "ev_replan", array("target_state"=>"PlannedScheduled", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Approved", "ev_implement", array("target_state"=>"Implemented", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Implemented", "ev_monitor", array("target_state"=>"Monitored", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Implemented", "ev_finish", array("target_state"=>"Closed", "actions"=>array('SetLastUpDate'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Monitored", "ev_finish", array("target_state"=>"Closed", "actions"=>array(), "user_restriction"=>null));		
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('customer_id', $oGenerator->GetOrganizationId());
+		$this->Set('title', $oGenerator->GenerateString("enum(Site,Server,Line)| |enum(is down,is flip-flopping,is not responding)"));
+		$this->Set('agent_id', $oGenerator->GenerateKey("bizPerson", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_status', $oGenerator->GenerateString("enum(Open,Closed,Closed,Monitored)"));
+		$this->Set('start_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+		$this->Set('last_update', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+		$this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+	}
+	
+	
+	
+	// State machine actions
+	public function IncrementAssignmentCount($sStimulusCode)
+	{
+		$this->Set('assignment_count', $this->Get('assignment_count') + 1);
+		return true;
+	}
+	
+	public function SetClosureDate($sStimulusCode)
+	{
+		$this->Set('end_date', time());
+		return true;
+	}
+	public function SetLastUpDate($sStimulusCode)
+	{
+		$this->Set('last_update', time());
+		return true;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Change Ticket
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInfraChangeTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Infra Change Ticket",
+			"description" => "Infra impacted by a Change ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "impact",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("impact"),  // ????
+			"db_table" => "infra_changeticket",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title")));
+		MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("infra_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'ticket_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'ticket_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+}
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any contact and a Contract
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkContactChange extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "ContactChangeLink",
+			"description" => "Contact associated to a change",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "role",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("role"),  // ????
+			"db_table" => "contact_change",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("change_id", array("targetclass"=>"bizChangeTicket", "jointype"=> '', "label"=>"Change Ticket", "description"=>"Change ticket ID", "allowed_values"=>null, "sql"=>"change_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("change_number", array("label"=>"change number", "description"=>"Ticket number for this change", "allowed_values"=>null, "extkey_attcode"=> 'change_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this change", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("change_id");
+		MetaModel::Init_AddFilterFromAttribute("contact_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('change_id', 'contact_id', 'role')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('change_id', 'contact_id', 'role')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('change_id', 'contact_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('change_id', 'contact_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('contract_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('contact_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('role', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+
+?>

+ 32 - 0
business/Changes-04-Sep-2007.php

@@ -0,0 +1,32 @@
+Changements principaux:
+- la classe AbstractObject est sortie du biz model
+- join_type remplacé par is_null_allowed (placé à la fin pour être + facile à retrouver)
+- j'ai enlevé toute la classe logLocatedObject qui était en commentaire
+- Enlevé 'address' de l'advanced search sur une location car ce n'est plus un critère de recherche possible (remplacé par country)
+- Ajouté des critères de recherche sur bizCircuit
+- Ajouté les ZList sur bizCircuit
+- Ajouté les Zlist pour bizInterface
+- Ajouté les Zlist pour lnkInfraInfra
+- Ajouté les Zlist pour lnkInfraTicket
+
+Dans AbstractObject: désactivé l'affichage des contacts liés qui ne marche pas pour les tickets.
+
+Bug fix ?
+- J'ai rajouté un blindage if (is_object($proposedValue) &&... dans AttributeDate::MakeRealValue mais je ne comprends pas d'où sort la classe DateTime... et pourtant il y en a...
+
+Améliorations:
+- Ajouter une vérification des ZList (les attributs/critèresde recherche déclarés dans la liste existent-ils pour cet objet)
+
+Ne marche pas:
+- Objets avec des clefs externes vides
+- Enums !!!!
+
+Data Generator:
+Organization '1' updated.
+5 Location objects created.
+19 PC objects created.
+19 Network Device objects created.
+42 Person objects created.
+6 Incident objects created.
+17 Infra Group objects created.
+34 Infra Infra objects created.

+ 165 - 0
business/KEDB.php

@@ -0,0 +1,165 @@
+<?php
+
+$oAllowedStatuses = new ValueSetEnum('production,implementation,obsolete');
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Description of known error
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizKnownError extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Known Error",
+			"description" => "Error documented for a known issue",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("cust_id", "name"), // inherited attributes
+			"db_table" => "known_error",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/knownError.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+    MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name to identify this error", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("cust_id", array("targetclass"=>"bizOrganization", "label"=>"Organization", "description"=>"Organization for this known error", "allowed_values"=>null, "sql"=>"cust_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("cust_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'cust_id', "target_attcode"=>"name")));
+
+   	MetaModel::Init_AddAttribute(new AttributeText("symptom", array("label"=>"Symptom", "description"=>"Description of this error", "allowed_values"=>null, "sql"=>"symptom", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ 	 	MetaModel::Init_AddAttribute(new AttributeText("root_cause", array("label"=>"Root cause", "description"=>"Original cause for this known error", "allowed_values"=>null, "sql"=>"rootcause", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ 	  MetaModel::Init_AddAttribute(new AttributeText("workaround", array("label"=>"Work around", "description"=>"Work around to fix this error", "allowed_values"=>null, "sql"=>"workaround", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ 		MetaModel::Init_AddAttribute(new AttributeText("solution", array("label"=>"Solution", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"solution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+ 
+    MetaModel::Init_AddAttribute(new AttributeString("error_code", array("label"=>"Error Code", "description"=>"Key word to identify error", "allowed_values"=>null, "sql"=>"error_code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("domain", array("label"=>"Domain", "description"=>"Domain for this known error, network, desktop, ...", "allowed_values"=>new ValueSetEnum("Network, Server, Application, Desktop"), "sql"=>"domain", "default_value"=>"Application", "is_null_allowed"=>false, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("vendor", array("label"=>"Vendor", "description"=>"Vendor concerned by this known error", "allowed_values"=>null, "sql"=>"vendor", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"Model concerned by this known error, it may be an application, a device ...", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Version related to model impacted by known error", "allowed_values"=>null, "sql"=>"version", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("cust_id");
+		MetaModel::Init_AddFilterFromAttribute("cust_name");
+		MetaModel::Init_AddFilterFromAttribute("error_code");
+		MetaModel::Init_AddFilterFromAttribute("domain");
+		
+
+
+		
+
+		MetaModel::Init_SetZListItems('details', array('name', 'cust_id','error_code','domain','vendor','model','version', 'symptom','root_cause','workaround','solution')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'cust_id','error_code', 'symptom')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'error_code','domain')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'cust_id','error_code', 'error_code','symptom')); // Criteria of the advanced search form
+
+	}
+	
+	// State machine actions
+	public function IncrementVersion($sStimulusCode)
+	{
+		$this->Set('version_number', $this->Get('version_number') + 1);
+		return true;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Known Error
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInfraError extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "InfraErrorLinks",
+			"description" => "Infra related to a known error",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("infra_id","error_id"),  // ????
+			"db_table" => "infra_error_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "jointype"=> '', "label"=>"Error name", "description"=>"Error id", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error name", "description"=>"Name of the error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name")));
+
+		MetaModel::Init_AddFilterFromAttribute("infra_id");
+		MetaModel::Init_AddFilterFromAttribute("error_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('infra_id', 'error_id')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('infra_id', 'infra_status','error_id')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'error_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'error_id')); // Criteria of the advanced search form
+	}
+
+	
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Contract and a Document
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkDocumentError extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "DocumentsErrorLinks",
+			"description" => "A link between a document and a known error",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "link_type",
+			"state_attcode" => "",
+			"reconc_keys" => array("doc_name", "error_name"),
+			"db_table" => "documents_error_link",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("error_id", array("targetclass"=>"bizKnownError", "label"=>"Error", "description"=>"Error linked to this document", "allowed_values"=>null, "sql"=>"error_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("error_name", array("label"=>"Error name", "description"=>"name of the linked error", "allowed_values"=>null, "extkey_attcode"=> 'error_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("doc_id");
+		MetaModel::Init_AddFilterFromAttribute("doc_name");
+		MetaModel::Init_AddFilterFromAttribute("error_id");
+		MetaModel::Init_AddFilterFromAttribute("error_name");
+		MetaModel::Init_AddFilterFromAttribute("link_type");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('doc_id', 'error_name', 'link_type')); // Attributes to be displayed for a list
+	}
+}
+
+
+?>

+ 261 - 0
business/ServiceMgmt.business.php

@@ -0,0 +1,261 @@
+<?php
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Description of a contract signed with a customer
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizContract extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Contract",
+			"description" => "Contract signed by an organization",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "status",
+			"reconc_keys" => array("customer_id", "name"), // inherited attributes
+			"db_table" => "contracts",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/contract.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+    MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Name of the contract", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"Customer for this contract", "allowed_values"=>null, "sql"=>"customer_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"name of the Customer", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name")));
+
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "label"=>"Provider", "description"=>"Provider for this contract", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("provider_name", array("label"=>"Provider", "description"=>"name of the service provider", "allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name")));
+    MetaModel::Init_AddAttribute(new AttributeString("service_name", array("label"=>"Service Name", "description"=>"Name of service for this contract", "allowed_values"=>null, "sql"=>"service_name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team managing this contract", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team", "description"=>"name of the team managing this contract", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeEnum("service_level", array("label"=>"Service Level", "description"=>"Level of service for this contract", "allowed_values"=>new ValueSetEnum("Gold,Silver,Bronze"), "sql"=>"service_level", "default_value"=>"Bronze", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("cost_unit", array("label"=>"Cost unit", "description"=>"Cost unit to compute global cost for this contract", "allowed_values"=>new ValueSetEnum("Devices,Persons,Applications,Global"), "sql"=>"cost_unit", "default_value"=>"Global", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("cost_freq", array("label"=>"Cost frequency", "description"=>"Frequency of cost for this contract", "allowed_values"=>new ValueSetEnum("Monthly,Yearly,Once"), "sql"=>"cost_freq", "default_value"=>"Once", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("cost", array("label"=>"Cost", "description"=>"Cost of this contract", "allowed_values"=>null, "sql"=>"cost", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("currency", array("label"=>"Currency", "description"=>"Currency of cost for this contract", "allowed_values"=>new ValueSetEnum("Euros,Dollars"), "sql"=>"currency", "default_value"=>"Euros", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeText("description", array("label"=>"Description", "description"=>"Description of this contract", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("move2prod_date", array("label"=>"Date of move to production", "description"=>"Date when the contract is on production", "allowed_values"=>null, "sql"=>"move2prod_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("end_prod", array("label"=>"Date of end of production", "description"=>"Date when the contract is stopped", "allowed_values"=>null, "sql"=>"end_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the contract", "allowed_values"=>new ValueSetEnum("New, Negotiating, Signed, Production, Notice, Finished"), "sql"=>"status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the contract", "allowed_values"=>new ValueSetEnum("Hardware,Software,Support,Licence"), "sql"=>"type", "default_value"=>"Support", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeInteger("version_number", array("label"=>"Version number", "description"=>"Revision number for this contract", "allowed_values"=>null, "sql"=>"version_number", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array())));
+
+
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("service_name");
+		MetaModel::Init_AddFilterFromAttribute("provider_id");
+		MetaModel::Init_AddFilterFromAttribute("customer_id");
+		MetaModel::Init_AddFilterFromAttribute("team_id");
+		MetaModel::Init_AddFilterFromAttribute("team_name");
+		MetaModel::Init_AddFilterFromAttribute("service_level");
+		MetaModel::Init_AddFilterFromAttribute("end_prod");
+		MetaModel::Init_AddFilterFromAttribute("status");
+		MetaModel::Init_AddFilterFromAttribute("version_number");
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("type");
+
+
+		// Life cycle
+		MetaModel::Init_DefineState("New", array("label"=>"New", "description"=>"Newly created contract", "attribute_inherit"=>null,
+												 "attribute_list"=>array()));
+		MetaModel::Init_DefineState("Negotiating", array("label"=>"Negotiating", "description"=>"The contract is being worked on", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Signed", array("label"=>"Signed", "description"=>"The contract has been signed", "attribute_inherit"=>null,
+													"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Production", array("label"=>"Production", "description"=>"The contract is effective in production", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Notice", array("label"=>"Notice", "description"=>"The contract is about to be terminated", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Finished", array("label"=>"Finished", "description"=>"The contract is terminated", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+
+		MetaModel::Init_DefineStimulus("ev_freeze_version", new StimulusUserAction(array("label"=>"Freeze this version", "description"=>"This version of the contract is published")));
+		MetaModel::Init_DefineStimulus("ev_sign", new StimulusUserAction(array("label"=>"Sign this contract", "description"=>"This contract is being signed")));
+		MetaModel::Init_DefineStimulus("ev_begin", new StimulusUserAction(array("label"=>"Move to production", "description"=>"The contract becomes applicable in production")));
+		MetaModel::Init_DefineStimulus("ev_notice", new StimulusUserAction(array("label"=>"Start notice period", "description"=>"The end date of the contract is approaching")));
+		MetaModel::Init_DefineStimulus("ev_terminate", new StimulusUserAction(array("label"=>"Ends this contract", "description"=>"The contract is ending")));
+		MetaModel::Init_DefineStimulus("ev_elapsed", new StimulusUserAction(array("label"=>"Times up [Do not click!]", "description"=>"The contract over")));
+
+		MetaModel::Init_DefineTransition("New", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Negotiating", "ev_freeze_version", array("target_state"=>"Negotiating", "actions"=>array('IncrementVersion'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Negotiating", "ev_sign", array("target_state"=>"Signed", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Negotiating", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Signed", "ev_freeze_version", array("target_state"=>"Signed", "actions"=>array('IncrementVersion'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Signed", "ev_begin", array("target_state"=>"Production", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Signed", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Production", "ev_freeze_version", array("target_state"=>"Production", "actions"=>array('IncrementVersion'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Production", "ev_elapsed", array("target_state"=>"Notice", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Production", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Notice", "ev_elapsed", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Notice", "ev_terminate", array("target_state"=>"Finished", "actions"=>array(), "user_restriction"=>null));
+
+
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'customer_id', 'service_name','provider_id','type','description','team_id','service_level','cost','currency','cost_unit','cost_freq','move2prod_date','end_prod', 'version_number')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'customer_id', 'provider_id','service_name','service_level','type')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status','service_name','provider_id','team_name','service_level','type')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'service_name','provider_id','team_name', 'service_level', 'customer_id')); // Criteria of the advanced search form
+
+	}
+	
+	// State machine actions
+	public function IncrementVersion($sStimulusCode)
+	{
+		$this->Set('version_number', $this->Get('version_number') + 1);
+		return true;
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Contract
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInfraContract extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "InfraContractLinks",
+			"description" => "Infra covered by a contract",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "coverage",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("infra_id","contract_id"),  // ????
+			"db_table" => "infra_contract_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract name", "description"=>"Contract id", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("coverage", array("label"=>"coverage", "description"=>"coverage for the given infra", "allowed_values"=>null, "sql"=>"coverage", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+    MetaModel::Init_AddAttribute(new AttributeString("service_level", array("label"=>"service level", "description"=>"service level for the given infra", "allowed_values"=>null, "sql"=>"sla", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+
+		MetaModel::Init_AddFilterFromAttribute("infra_id");
+		MetaModel::Init_AddFilterFromAttribute("contract_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('infra_id', 'contract_id', 'coverage','service_level')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('infra_id', 'infra_status','contract_id' , 'coverage','service_level')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'contract_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'contract_id')); // Criteria of the advanced search form
+	}
+
+	
+}
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any contact and a Contract
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkContactContract extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "ContactContractLink",
+			"description" => "Contact associated to a contract",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "role",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("role"),  // ????
+			"db_table" => "contact_Contract",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"The contact linked to contract", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_mail", array("label"=>"Contact E-mail", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "jointype"=> '', "label"=>"Contract", "description"=>"Contract ID", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"Contract name", "description"=>"Name of the contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this contact for this contract", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("contract_id");
+		MetaModel::Init_AddFilterFromAttribute("contact_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('contract_id', 'contact_id', 'role')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('contract_id', 'contact_id', 'role')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('contract_id', 'contact_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('contract_id', 'contact_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('contract_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('contact_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('role', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Contract and a Document
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkDocumentContract extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "DocumentsContractLinks",
+			"description" => "A link between a document and another contract",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "link_type",
+			"state_attcode" => "",
+			"reconc_keys" => array("doc_name", "contract_name"),
+			"db_table" => "documents_contracts",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contract_id", array("targetclass"=>"bizContract", "label"=>"Contract", "description"=>"Contract linked to this document", "allowed_values"=>null, "sql"=>"contract_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contract_name", array("label"=>"contract name", "description"=>"name of the linked contract", "allowed_values"=>null, "extkey_attcode"=> 'contract_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("doc_id");
+		MetaModel::Init_AddFilterFromAttribute("doc_name");
+		MetaModel::Init_AddFilterFromAttribute("contract_id");
+		MetaModel::Init_AddFilterFromAttribute("contract_name");
+		MetaModel::Init_AddFilterFromAttribute("link_type");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('doc_id', 'contract_name', 'link_type')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('doc_id', 'contract_name', 'link_type')); // Attributes to be displayed for a list
+	}
+}
+
+
+?>

+ 261 - 0
business/business_itopbegins.class.inc.php

@@ -0,0 +1,261 @@
+<?php
+
+require_once('../core/MyHelpers.class.inc.php');
+require_once('../core/cmdbobject.class.inc.php');
+
+/**
+ * business_itopbegins.class.inc.php
+ * User defined objects, for unit testing 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Business implementation demo
+///////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbContact extends CMDBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassContact",
+			"description" => "klass contact description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_contact_name",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_contact_name"),
+			"db_table" => "contact",
+			"db_key_field" => "contactid",
+			"db_finalclass_field" => "actualclass",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("att_contact_name", array("label"=>"name of the contact", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeInteger("att_contact_availability", array("label"=>"degree of availability in percent", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"availability")));
+		MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("att_contact_name");
+		MetaModel::Init_AddFilterFromAttribute("att_contact_availability");
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbPerson extends cmdbContact
+{
+	public static function Init()
+	{
+		$oValsDunsNumber = new ValueSetObjects("cmdbCompany: att_company_dunsnumber Begins with '$[duns_prm::]'", "att_company_dunsnumber", array("att_company_dunsnumber"=>true));
+
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassPerson",
+			"description" => "klass person description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_contact_name",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_contact_name"),
+			"db_table" => "person",
+			"db_key_field" => "personid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("att_person_email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>$oValsDunsNumber, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"email")));
+		MetaModel::Init_AddAttribute(new AttributeString("att_person_name", array("label"=>"secName", "description"=>"secondary name", "allowed_values"=>new ValueSetEnum(array("nom1", "nom2", "nom10", "no", "noms", "")), "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"name")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("att_person_email");
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbSubcontractor extends cmdbPerson
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassSubcontractor",
+			"description" => "klass subcontractor description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_contact_name",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_contact_name"),
+			"db_table" => "subcontractor",
+			"db_key_field" => "subcontractorid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("att_contractinfo", array("label"=>"contract info", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"contractinfo")));
+
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_provider", array("label"=>"ssii", "description"=>"blah", "allowed_values"=>null, "sql"=>"provider", "targetclass"=>"cmdbProvider", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_provider_ref", array("label"=>"ref", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_provider", "target_attcode"=>"att_provider_ref")));
+
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ext_subcontractor_tutor", array("label"=>"tutor", "description"=>"blah", "allowed_values"=>null, "sql"=>"tutor", "targetclass"=>"cmdbPerson", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_email", array("label"=>"tutor email", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_email")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("extatt_subcontractor_tutor_secondname", array("label"=>"2ndname (ext field)", "description"=>"blah", "allowed_values"=>null, "extkey_attcode"=>"ext_subcontractor_tutor", "target_attcode"=>"att_person_name")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("extatt_subcontractor_tutor_secondname");
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbCrowd extends cmdbObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassCrowd",
+			"description" => "klass crowd description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_crowd_peoplecount",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_crowd_peoplecount"),
+			"db_table" => "crowd",
+			"db_key_field" => "crowdid",
+			"db_finalclass_field" => "crowdclass",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeInteger("att_crowd_peoplecount", array("label"=>"people count", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"peoplecount")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("att_crowd_peoplecount");
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbCompany extends cmdbCrowd
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassCompany",
+			"description" => "klass company description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_company_dunsnumber",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_company_dunsnumber"),
+			"db_table" => "company",
+			"db_key_field" => "companyid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("att_company_dunsnumber", array("label"=>"duns number", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"dunsnumber")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("att_company_dunsnumber");
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbProvider extends cmdbCompany
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "klassProvider",
+			"description" => "klass provider description",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "att_provider_ref",
+			"state_attcode" => "",
+			"reconc_keys" => array("att_provider_ref"),
+			"db_table" => "provider",
+			"db_key_field" => "providerid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeInteger("att_provider_ref", array("label"=>"provider ref", "description"=>"blah", "allowed_values"=>null, "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array(), "sql"=>"providerref")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("att_provider_ref");
+	}
+}
+
+
+?>

+ 366 - 0
business/business_test.class.inc.php

@@ -0,0 +1,366 @@
+<?php
+
+require_once('../core/MyHelpers.class.inc.php');
+require_once('../core/cmdbobject.class.inc.php');
+
+/**
+ * business_test.class.inc.php
+ * User defined objects, for unit testing 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+///////////////////////////////////////////////////////////////////////////////
+// Business implementation demo
+///////////////////////////////////////////////////////////////////////////////
+
+MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de"));
+
+
+MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes"));
+MetaModel::RegisterZList("list2", array("description"=>"la secunda e meliora", "type"=>"attributes"));
+MetaModel::RegisterZList("list3", array("description"=>"la variante qui tue", "type"=>"filters"));
+
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbObjectHomeMade extends cmdbObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "anyObject",
+			"description" => "std object",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(""),
+			"db_table" => "",
+			"db_key_field" => "",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		//trigger_error("GetRelationQueries: cmdbObjectHomeMade");
+		switch ($sRelCode)
+		{
+		case "Potes":
+			$aRels = array("xxxx" => array("sQuery"=>"cmdbContact: pkey = 40", "bPropagate"=>true, "iDistance"=>3));
+			return $aRels;
+		}
+	}
+}
+
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbContact extends cmdbObjectHomeMade
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Contact",
+			"description" => "Un object que l'on peut communiquer avec",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "etat",
+			"reconc_keys" => array("name"),
+			"db_table" => "contact",
+			"db_key_field" => "contactid",
+			"db_finalclass_field" => "actualclass",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("etat", array("label"=>"l'etat", "description"=>"les etats d'ame d'eric", "allowed_values"=>new ValueSetEnum('justborn, 15, 21'), "sql"=>"etat", "default_value"=>"justborn", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"nom", "description"=>"ze equipe", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"iMaile", "description"=>"imelle", "allowed_values"=>null, "sql"=>"email", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("owner", array("label"=>"owned by", "description"=>"organization owning the team", "allowed_values"=>null, "sql"=>"ownerorg", "targetclass"=>"cmdbOrga", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ownername", array("label"=>"owned by", "description"=>"name of organization owning the team", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_name_")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ownertnut", array("label"=>"owntnut", "description"=>"blah tnut blah", "allowed_values"=>null, "extkey_attcode"=>"owner", "target_attcode"=>"_dunsnumber_")));
+
+		MetaModel::Init_AddAttribute(new AttributeLinkedSet("myworkshops", array("label"=>"held workshops", "description"=>"blah tnut blah", "depends_on"=>array(), "linked_class"=>"cmdbLiens", "ext_key_to_me"=>"tocontact", "count_min"=>1, "count_max"=>10, "allowed_values"=>null)));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("owner");
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("ownername");
+
+		MetaModel::Init_SetZListItems("list1", array("name", "email"));
+		MetaModel::Init_SetZListItems("list2", array());
+		MetaModel::Init_SetZListItems("list3", array("ownername"));
+
+		MetaModel::Init_DefineState("justborn", array("label"=>"just born", "description"=>"too young to die", "attribute_inherit"=>null, "attribute_list"=>array("owner"=>OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("15", array("label"=>"student", "description"=>"stupid age", "attribute_inherit"=>"justborn", "attribute_list"=>array("owner"=>OPT_ATT_MUSTPROMPT, "email"=>OPT_ATT_MUSTPROMPT)));
+		MetaModel::Init_DefineState("21", array("label"=>"old", "description"=>"one foot in the grave", "attribute_inherit"=>"15", "attribute_list"=>array("email"=>OPT_ATT_READONLY|OPT_ATT_MUSTCHANGE)));
+
+		MetaModel::Init_DefineStimulus("toschool", new StimulusUserAction(array("label"=>"go to school", "description"=>"start learning stupid things")));
+		MetaModel::Init_DefineStimulus("raise", new StimulusUserAction(array("label"=>"grow!", "description"=>"eat tons of BigMACs")));
+
+		MetaModel::Init_DefineTransition("justborn", "toschool", array("target_state"=>"15", "actions"=>array('MyLifecycleHandler', 'MyLifecycleHandler2'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("15", "raise", array("target_state"=>"21", "actions"=>null, "user_restriction"=>null));
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		//trigger_error("GetRelationQueries: cmdbContact");
+		switch ($sRelCode)
+		{
+		case "Potes":
+			$aRels = array(
+				"zz1" => array("sQuery"=>"cmdbContact: name Begins with '\$[this.name::]' AND pkey != \$[this.pkey::]", "bPropagate"=>false, "iDistance"=>3),
+				"zz2" => array("sQuery"=>"cmdbContact: owner = \$[this.owner::] AND owner != 2", "bPropagate"=>false, "iDistance"=>3),
+			);
+			return array_merge($aRels, parent::GetRelationQueries($sRelCode));
+		}
+	}
+
+	public function MyLifecycleHandler($sStimulusCode)
+	{
+		echo "<p>youhou!</p>";
+		return true;
+	}
+	public function MyLifecycleHandler2($sStimulusCode)
+	{
+		echo "<p>... les papous...</p>";
+		return true;
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbTeam extends cmdbContact
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Equipado",
+			"description" => "Un regroupement de gens",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "email",
+			"state_attcode" => "",
+			"reconc_keys" => array("email"),
+			"db_table" => "team",
+			"db_key_field" => "teamid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_OverloadAttributeParams("email", array("label"=>"email2", "description"=>"emailleu22"));
+		MetaModel::Init_AddAttribute(new AttributeInteger("headcount", array("label"=>"nombre", "description"=>"combien ils sont", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>654321, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("headcount");
+
+		MetaModel::Init_SetZListItems("noneditable", array("name"));
+	}
+
+	public function ComputeValues()
+	{
+		//echo "Set(), function ComputeValues has been found for ".get_class($this)."<br/>\n";
+		$this->Set("name", $this->Get("email")." and ".$this->Get("headcount"));
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		//trigger_error("GetRelationQueries: cmdbTeam");
+		switch ($sRelCode)
+		{
+		case "Potes":
+			//$aRels = array("Relies on" => array("sQuery"=>"cmdbContact: name Begins with 'Louis'", "bPropagate"=>false, "iDistance"=>3));
+			return array_merge(array(), parent::GetRelationQueries($sRelCode));
+		}
+	}
+}
+
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbOrga extends cmdbObjectHomeMade
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Organization",
+			"description" => "Une entite qui possede des choses",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "_name_",
+			"state_attcode" => "",
+			"reconc_keys" => array("_name_"),
+			"db_table" => "organization",
+			"db_key_field" => "orgid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("_name_", array("label"=>"namo", "description"=>"official company name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("_status_", array("label"=>"step", "description"=>"step or status, etc.", "allowed_values"=>null, "sql"=>"status", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumber_", array("label"=>"duns debile number", "description"=>"une bonne idee a OVSD", "allowed_values"=>null, "sql"=>"dunsnumber", "default_value"=>99007, "is_null_allowed"=>false, "depends_on"=>array())));
+// not yet allowed		MetaModel::Init_AddAttribute(new AttributeInteger("_dunsnumberBY2_", array("label"=>"dummy duns", "description"=>"deux fois plus debile", "allowed_values"=>null, "sql"=>"dunsnumber * 3.141592654")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("_name_");
+
+		MetaModel::Init_SetZListItems("list1", array("_status_"));
+		MetaModel::Init_SetZListItems("list2", array());
+		MetaModel::Init_SetZListItems("list3", array("_name_"));
+	}
+
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbLiens extends cmdbObjectHomeMade
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Liens_entre_contacts_et_workshop",
+			"description" => "Une entite qui lie des contacts et workshops",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "function",
+			"state_attcode" => "",
+			"reconc_keys" => array("function"),
+			"db_table" => "role_ws",
+			"db_key_field" => "linkid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"fonction", "description"=>"la fonction...", "allowed_values"=>null, "sql"=>"function", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("a1", array("label"=>"a1", "description"=>"a1", "allowed_values"=>null, "sql"=>"a1", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("a2", array("label"=>"a1", "description"=>"a2", "allowed_values"=>null, "sql"=>"a2", "default_value"=>"XXXX", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		// What makes it being a link...
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("toworkshop", array("label"=>"participates in", "description"=>"workshop in wich the person is participating", "allowed_values"=>null, "sql"=>"ws_id", "targetclass"=>"cmdbWorkshop", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ws_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"toworkshop", "target_attcode"=>"namitus")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("tocontact", array("label"=>"is held by", "description"=>"people involved in that mess", "allowed_values"=>null, "sql"=>"contactid", "targetclass"=>"cmdbContact", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_info", array("label"=>"name", "description"=>"namedescription", "allowed_values"=>null, "extkey_attcode"=>"tocontact", "target_attcode"=>"name")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("function");
+
+		MetaModel::Init_SetZListItems("list1", array("toworkshop", "contact_info"));
+		MetaModel::Init_SetZListItems("list2", array("function"));
+		MetaModel::Init_SetZListItems("list3", array("function"));
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		throw new CoreException("GetRelationQueries: cmdbLiens");
+		return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3));
+	}
+}
+
+/**
+ * blah blah 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class cmdbWorkshop extends cmdbObjectHomeMade
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Workshop",
+			"description" => "Une entite qui pond des theories insensees",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "namitus",
+			"state_attcode" => "",
+			"reconc_keys" => array("namitus"),
+			"db_table" => "workshop",
+			"db_key_field" => "ws_id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("namitus", array("label"=>"namo", "description"=>"nom imbitique pour pondeurs de debilites", "allowed_values"=>null, "sql"=>"name", "default_value"=>"XXXX", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("namitus");
+
+		MetaModel::Init_SetZListItems("list1", array("namitus"));
+		MetaModel::Init_SetZListItems("list2", array());
+		MetaModel::Init_SetZListItems("list3", array("namitus"));
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		throw new CoreException("GetRelationQueries: cmdbWorkshop");
+		return array("Relies on" => array("sQuery"=>"", "bPropagate"=>true, "iDistance"=>3));
+	}
+}
+
+
+?>

+ 4845 - 0
business/data.samples.inc.php

@@ -0,0 +1,4845 @@
+<?php
+
+/**
+ * itop.business.class.inc.php
+ * User defined objects, for unit testing 
+ *
+ * @package     tbd
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+$aCompanies = array();
+$aCompanies[] = array('name' => "iTop", 'domain' => "itop.com", 'code' => "ITOP");
+$aCompanies[] = array('name' => "R-Skin Design", 'domain' => "rskindesign.com", 'code' => "RSKIN");
+$aCompanies[] = array('name' => "Pen Shop", 'domain' => "penshop.com", 'code' => "PENSHOP");
+$aCompanies[] = array('name' => "Joyant", 'domain' => "joyan.com", 'code' => "JOYAN");
+$aCompanies[] = array('name' => "Inhabitants", 'domain' => "inhabitants.org", 'code' => "INH");
+$aCompanies[] = array('name' => "Electric Poulp", 'domain' => "epoulp.com", 'code' => "EPOULP");
+$aCompanies[] = array('name' => "Well Steam", 'domain' => "wellsteam.com", 'code' => "WEELSTM");
+$aCompanies[] = array('name' => "Express Engine", 'domain' => "expressengine.com", 'code' => "XPRESS");
+$aCompanies[] = array('name' => "Electric Bytes", 'domain' => "ebytes.com", 'code' => "EBYTES");
+$aCompanies[] = array('name' => "Iceberg", 'domain' => "iceberg.com", 'code' => "ICEBERG");
+$aCompanies[] = array('name' => "VThird", 'domain' => "vthird.com", 'code' => "V3RD");
+$aCompanies[] = array('name' => "AAssurance", 'domain' => "aasurance.com", 'code' => "AASSUR");
+$aCompanies[] = array('name' => "Webgets", 'domain' => "webgets.com", 'code' => "WEBGETS");
+$aCompanies[] = array('name' => "Brianair", 'domain' => array("brianair.co.uk", "brianair.com", "britanair.com"), 'code' => "BRAIR");
+$aCompanies[] = array('name' => "NetCMDB", 'domain' => array("netcmdb.com", "netcmdb.net", "octopus.com"), 'code' => "NECMDB");
+$aCompanies[] = array('name' => "Zen Garden", 'domain' => "zengarden.net", 'code' => "ZEN");
+$aCompanies[] = array('name' => "T-Supply", 'domain' => "tsupply.de", 'code' => "TSUPPLY");
+$aCompanies[] = array('name' => "Berliner Druckmaschinen", 'domain' => "bdm.de", 'code' => "BDM");
+$aCompanies[] = array('name' => "ZKS", 'domain' => "zks.com", 'code' => "ZKS");
+$aCompanies[] = array('name' => "Air Amérique", 'domain' => "airamerique.fr", 'code' => "AIRAM");
+$aCompanies[] = array('name' => "Radio Mongo", 'domain' => "radio-mongo.fr", 'code' => "MONGO");
+$aCompanies[] = array('name' => "Radio Andorre", 'domain' => "radio.ad", 'code' => "RADAND");
+$aCompanies[] = array('name' => "Idahos Mutual Life Insurance", 'domain' => "idahosmutual.com", 'code' => "IMLI");
+$aCompanies[] = array('name' => "Amott Laboratories", 'domain' => "amott.com", 'code' => "AMOTT");
+$aCompanies[] = array('name' => "Hereford Financial Services Group", 'domain' => "hfsg.com", 'code' => "HFSG");
+$aCompanies[] = array('name' => "Techni Data Corp", 'domain' => array("technidata.com", "techniservices.com"), 'code' => "TECHNI");
+$aCompanies[] = array('name' => "Southwestern Mutual Life Insurance", 'domain' => array("swmutual.com", "swmutual-loans.com", "insurance.swmutual.com"), 'code' => "SWMUTUAL");
+$aCompanies[] = array('name' => "American Solar Power", 'domain' => "solarpower.com", 'code' => "ASP");
+$aCompanies[] = array('name' => "Altena", 'domain' => "altena.fr", 'code' => "ALTENA");
+$aCompanies[] = array('name' => "Groupe Commeco", 'domain' => "commeco.fr", 'code' => "COMMECO");
+$aCompanies[] = array('name' => "Virages", 'domain' => array("virages.fr", "virages.corp.net"), 'code' => "VIRAGES");
+$aCompanies[] = array('name' => "Ciments Lorrains", 'domain' => "cimlor.fr", 'code' => "CIMLOR");
+$aCompanies[] = array('name' => "Durand SA", 'domain' => "durand.fr", 'code' => "DURAND");
+$aCompanies[] = array('name' => "Transports Jeanton", 'domain' => "jeanton.fr", 'code' => "JEANTON");
+$aCompanies[] = array('name' => "Companie Générale du Bois", 'domain' => "cgb.fr", 'code' => "CGB");
+$aCompanies[] = array('name' => "Village Group", 'domain' => "village.com", 'code' => "VGP");
+
+$aCompaniesCode = array();
+foreach($aCompanies as $aCompany)
+{
+	$aCompaniesCode[$aCompany['code']] = array('name' => $aCompany['name'], 'domain' => $aCompany['domain']);
+}
+
+$aFirstNames = array();
+$aFirstNames[] = 'Enzo';
+$aFirstNames[] = 'Quentin';
+$aFirstNames[] = 'Mohamed';
+$aFirstNames[] = 'Lucas';
+$aFirstNames[] = 'Julien';
+$aFirstNames[] = 'Ethan';
+$aFirstNames[] = 'Nathan';
+$aFirstNames[] = 'Thomas';
+$aFirstNames[] = 'Nicolas';
+$aFirstNames[] = 'Alexandre';
+$aFirstNames[] = 'Theo';
+$aFirstNames[] = 'Yanis';
+$aFirstNames[] = 'Hugo';
+$aFirstNames[] = 'Rayan';
+$aFirstNames[] = 'Tom';
+$aFirstNames[] = 'Romain';
+$aFirstNames[] = 'Sacha';
+$aFirstNames[] = 'Xavier';
+$aFirstNames[] = 'Olivier';
+$aFirstNames[] = 'Antoine';
+$aFirstNames[] = 'Raphael';
+$aFirstNames[] = 'Louis';
+$aFirstNames[] = 'Clement';
+$aFirstNames[] = 'Maxime';
+$aFirstNames[] = 'Pierre';
+$aFirstNames[] = 'Jules';
+$aFirstNames[] = 'Mael';
+$aFirstNames[] = 'Axel';
+$aFirstNames[] = 'Guillaume';
+$aFirstNames[] = 'Benjamin';
+$aFirstNames[] = 'Gabriel';
+$aFirstNames[] = 'Maxence';
+$aFirstNames[] = 'Evan';
+$aFirstNames[] = 'Sebastien';
+$aFirstNames[] = 'Arthur';
+$aFirstNames[] = 'Noah';
+$aFirstNames[] = 'Kevin';
+$aFirstNames[] = 'Mathieu';
+$aFirstNames[] = 'Leo';
+$aFirstNames[] = 'Mathis';
+$aFirstNames[] = 'David';
+$aFirstNames[] = 'Florian';
+$aFirstNames[] = 'Paul';
+$aFirstNames[] = 'Tristan';
+$aFirstNames[] = 'Timeo';
+$aFirstNames[] = 'Simon';
+$aFirstNames[] = 'Matthieu';
+$aFirstNames[] = 'Cedric';
+$aFirstNames[] = 'Corentin';
+$aFirstNames[] = 'Vincent';
+$aFirstNames[] = 'Audrey';
+$aFirstNames[] = 'Aurelie';
+$aFirstNames[] = 'Manon';
+$aFirstNames[] = 'Jade';
+$aFirstNames[] = 'Lea';
+$aFirstNames[] = 'Chloe';
+$aFirstNames[] = 'Emma';
+$aFirstNames[] = 'Camille';
+$aFirstNames[] = 'Caroline';
+$aFirstNames[] = 'Marie';
+$aFirstNames[] = 'Sarah';
+$aFirstNames[] = 'Pauline';
+$aFirstNames[] = 'Lilou';
+$aFirstNames[] = 'Julie';
+$aFirstNames[] = 'Ines';
+$aFirstNames[] = 'Fanny';
+$aFirstNames[] = 'Melissa';
+$aFirstNames[] = 'Lola';
+$aFirstNames[] = 'Clara';
+$aFirstNames[] = 'Oceane';
+$aFirstNames[] = 'Sabrina';
+$aFirstNames[] = 'Mathilde';
+$aFirstNames[] = 'Laura';
+$aFirstNames[] = 'Ambre';
+$aFirstNames[] = 'Alexia';
+$aFirstNames[] = 'Elodie';
+$aFirstNames[] = 'Anais';
+$aFirstNames[] = 'Amandine';
+$aFirstNames[] = 'Sophie';
+$aFirstNames[] = 'Emilie';
+$aFirstNames[] = 'Celia';
+$aFirstNames[] = 'Malika';
+$aFirstNames[] = 'Margaux';
+$aFirstNames[] = 'Ursule';
+$aFirstNames[] = 'Louise';
+$aFirstNames[] = 'Ophelie';
+$aFirstNames[] = 'Charlotte';
+$aFirstNames[] = 'Gaetane';
+$aFirstNames[] = 'Abigail';
+$aFirstNames[] = 'Noemie';
+$aFirstNames[] = 'Coralie';
+$aFirstNames[] = 'Celine';
+$aFirstNames[] = 'Louna';
+$aFirstNames[] = 'Emeline';
+$aFirstNames[] = 'Lena';
+$aFirstNames[] = 'Marion';
+$aFirstNames[] = 'Solene';
+$aFirstNames[] = 'Alice';
+$aFirstNames[] = 'Lucie';
+$aFirstNames[] = 'Lisa';
+$aFirstNames[] = 'Jean';
+$aFirstNames[] = 'Philippe';
+$aFirstNames[] = 'Michel';
+$aFirstNames[] = 'Alain';
+$aFirstNames[] = 'Patrick';
+$aFirstNames[] = 'Pascal';
+$aFirstNames[] = 'Daniel';
+$aFirstNames[] = 'Christophe';
+$aFirstNames[] = 'Eric';
+$aFirstNames[] = 'Nicolas';
+$aFirstNames[] = 'Pierre';
+$aFirstNames[] = 'Christian';
+$aFirstNames[] = 'Laurent';
+$aFirstNames[] = 'Stephane';
+$aFirstNames[] = 'David';
+$aFirstNames[] = 'Olivier';
+$aFirstNames[] = 'Sebastien';
+$aFirstNames[] = 'Gerard';
+$aFirstNames[] = 'Frederic';
+$aFirstNames[] = 'Bernard';
+$aFirstNames[] = 'Julien';
+$aFirstNames[] = 'Dominique';
+$aFirstNames[] = 'Thierry';
+$aFirstNames[] = 'Claude';
+$aFirstNames[] = 'Francis';
+$aFirstNames[] = 'Alexandre';
+$aFirstNames[] = 'Denis';
+$aFirstNames[] = 'Francois';
+$aFirstNames[] = 'Didier';
+$aFirstNames[] = 'Vincent';
+$aFirstNames[] = 'Bruno';
+$aFirstNames[] = 'Thomas';
+$aFirstNames[] = 'Gilles';
+$aFirstNames[] = 'Jacques';
+$aFirstNames[] = 'Jerome';
+$aFirstNames[] = 'Herve';
+$aFirstNames[] = 'Marc';
+$aFirstNames[] = 'Fabrice';
+$aFirstNames[] = 'Patrice';
+$aFirstNames[] = 'Maxime';
+$aFirstNames[] = 'Anthony';
+$aFirstNames[] = 'Serge';
+$aFirstNames[] = 'Andre';
+$aFirstNames[] = 'Cedric';
+$aFirstNames[] = 'Romain';
+$aFirstNames[] = 'Guillaume';
+$aFirstNames[] = 'Arnaud';
+$aFirstNames[] = 'Franck';
+$aFirstNames[] = 'Kevin';
+$aFirstNames[] = 'Sylvain';
+$aFirstNames[] = 'Lea';
+$aFirstNames[] = 'Manon';
+$aFirstNames[] = 'Camille';
+$aFirstNames[] = 'Emma';
+$aFirstNames[] = 'Clara';
+$aFirstNames[] = 'Chloe';
+$aFirstNames[] = 'Oceane';
+$aFirstNames[] = 'Lisa';
+$aFirstNames[] = 'Laura';
+$aFirstNames[] = 'Lucie';
+$aFirstNames[] = 'Mathilde';
+$aFirstNames[] = 'Lola';
+$aFirstNames[] = 'Marie';
+$aFirstNames[] = 'Sarah';
+$aFirstNames[] = 'Celia';
+$aFirstNames[] = 'Eva';
+$aFirstNames[] = 'Julie';
+$aFirstNames[] = 'Justine';
+$aFirstNames[] = 'Emilie';
+$aFirstNames[] = 'Ines';
+$aFirstNames[] = 'Pauline';
+$aFirstNames[] = 'Anais';
+$aFirstNames[] = 'Maeva';
+$aFirstNames[] = 'Zoe';
+$aFirstNames[] = 'Juliette';
+$aFirstNames[] = 'Alice';
+$aFirstNames[] = 'Margaux';
+$aFirstNames[] = 'Louise';
+$aFirstNames[] = 'Morgane';
+$aFirstNames[] = 'Clemence';
+$aFirstNames[] = 'Lena';
+$aFirstNames[] = 'Jeanne';
+$aFirstNames[] = 'Melissa';
+$aFirstNames[] = 'Noemie';
+$aFirstNames[] = 'Charlotte';
+$aFirstNames[] = 'Elise';
+$aFirstNames[] = 'Elisa';
+$aFirstNames[] = 'Jade';
+$aFirstNames[] = 'Margot';
+$aFirstNames[] = 'Romane';
+$aFirstNames[] = 'Alexia';
+$aFirstNames[] = 'Carla';
+$aFirstNames[] = 'Alicia';
+$aFirstNames[] = 'Flavie';
+$aFirstNames[] = 'Marine';
+$aFirstNames[] = 'Lou';
+$aFirstNames[] = 'Marion';
+$aFirstNames[] = 'Claire';
+$aFirstNames[] = 'Solene';
+$aFirstNames[] = 'Amelie';
+$aFirstNames[] = 'Mary';
+$aFirstNames[] = 'Patricia';
+$aFirstNames[] = 'Linda';
+$aFirstNames[] = 'Barbara';
+$aFirstNames[] = 'Elizabeth';
+$aFirstNames[] = 'Jennifer';
+$aFirstNames[] = 'Maria';
+$aFirstNames[] = 'Susan';
+$aFirstNames[] = 'Margaret';
+$aFirstNames[] = 'Dorothy';
+$aFirstNames[] = 'Lisa';
+$aFirstNames[] = 'Nancy';
+$aFirstNames[] = 'Karen';
+$aFirstNames[] = 'Betty';
+$aFirstNames[] = 'Helen';
+$aFirstNames[] = 'Sandra';
+$aFirstNames[] = 'Donna';
+$aFirstNames[] = 'Carol';
+$aFirstNames[] = 'Ruth';
+$aFirstNames[] = 'Sharon';
+$aFirstNames[] = 'Michelle';
+$aFirstNames[] = 'Laura';
+$aFirstNames[] = 'Sarah';
+$aFirstNames[] = 'Kimberly';
+$aFirstNames[] = 'Deborah';
+$aFirstNames[] = 'Jessica';
+$aFirstNames[] = 'Shirley';
+$aFirstNames[] = 'Cynthia';
+$aFirstNames[] = 'Angela';
+$aFirstNames[] = 'Melissa';
+$aFirstNames[] = 'Brenda';
+$aFirstNames[] = 'Amy';
+$aFirstNames[] = 'Anna';
+$aFirstNames[] = 'Rebecca';
+$aFirstNames[] = 'Virginia';
+$aFirstNames[] = 'Kathleen';
+$aFirstNames[] = 'Pamela';
+$aFirstNames[] = 'Martha';
+$aFirstNames[] = 'Debra';
+$aFirstNames[] = 'Amanda';
+$aFirstNames[] = 'Stephanie';
+$aFirstNames[] = 'Carolyn';
+$aFirstNames[] = 'Christine';
+$aFirstNames[] = 'Marie';
+$aFirstNames[] = 'Janet';
+$aFirstNames[] = 'Catherine';
+$aFirstNames[] = 'Frances';
+$aFirstNames[] = 'Ann';
+$aFirstNames[] = 'Joyce';
+$aFirstNames[] = 'Diane';
+$aFirstNames[] = 'Alice';
+$aFirstNames[] = 'Julie';
+$aFirstNames[] = 'Heather';
+$aFirstNames[] = 'Teresa';
+$aFirstNames[] = 'Doris';
+$aFirstNames[] = 'Gloria';
+$aFirstNames[] = 'Evelyn';
+$aFirstNames[] = 'Jean';
+$aFirstNames[] = 'Cheryl';
+$aFirstNames[] = 'Mildred';
+$aFirstNames[] = 'Katherine';
+$aFirstNames[] = 'Joan';
+$aFirstNames[] = 'Ashley';
+$aFirstNames[] = 'Judith';
+$aFirstNames[] = 'Rose';
+$aFirstNames[] = 'Janice';
+$aFirstNames[] = 'Kelly';
+$aFirstNames[] = 'Nicole';
+$aFirstNames[] = 'Judy';
+$aFirstNames[] = 'Christina';
+$aFirstNames[] = 'Kathy';
+$aFirstNames[] = 'Theresa';
+$aFirstNames[] = 'Beverly';
+$aFirstNames[] = 'Denise';
+$aFirstNames[] = 'Tammy';
+$aFirstNames[] = 'Irene';
+$aFirstNames[] = 'Jane';
+$aFirstNames[] = 'Lori';
+$aFirstNames[] = 'Rachel';
+$aFirstNames[] = 'Marilyn';
+$aFirstNames[] = 'Andrea';
+$aFirstNames[] = 'Kathryn';
+$aFirstNames[] = 'Louise';
+$aFirstNames[] = 'Sara';
+$aFirstNames[] = 'Anne';
+$aFirstNames[] = 'Jacqueline';
+$aFirstNames[] = 'Wanda';
+$aFirstNames[] = 'Bonnie';
+$aFirstNames[] = 'Julia';
+$aFirstNames[] = 'Ruby';
+$aFirstNames[] = 'Lois';
+$aFirstNames[] = 'Tina';
+$aFirstNames[] = 'Phyllis';
+$aFirstNames[] = 'Norma';
+$aFirstNames[] = 'Paula';
+$aFirstNames[] = 'Diana';
+$aFirstNames[] = 'Annie';
+$aFirstNames[] = 'Lillian';
+$aFirstNames[] = 'Emily';
+$aFirstNames[] = 'Robin';
+$aFirstNames[] = 'Peggy';
+$aFirstNames[] = 'Crystal';
+$aFirstNames[] = 'Gladys';
+$aFirstNames[] = 'Rita';
+$aFirstNames[] = 'Dawn';
+$aFirstNames[] = 'Connie';
+$aFirstNames[] = 'Florence';
+$aFirstNames[] = 'Tracy';
+$aFirstNames[] = 'Edna';
+$aFirstNames[] = 'Tiffany';
+$aFirstNames[] = 'Carmen';
+$aFirstNames[] = 'Rosa';
+$aFirstNames[] = 'Cindy';
+$aFirstNames[] = 'Grace';
+$aFirstNames[] = 'Wendy';
+$aFirstNames[] = 'Victoria';
+$aFirstNames[] = 'Edith';
+$aFirstNames[] = 'Kim';
+$aFirstNames[] = 'Sherry';
+$aFirstNames[] = 'Sylvia';
+$aFirstNames[] = 'Josephine';
+$aFirstNames[] = 'Thelma';
+$aFirstNames[] = 'Shannon';
+$aFirstNames[] = 'Sheila';
+$aFirstNames[] = 'Ethel';
+$aFirstNames[] = 'Ellen';
+$aFirstNames[] = 'Elaine';
+$aFirstNames[] = 'Marjorie';
+$aFirstNames[] = 'Carrie';
+$aFirstNames[] = 'Charlotte';
+$aFirstNames[] = 'Monica';
+$aFirstNames[] = 'Esther';
+$aFirstNames[] = 'Pauline';
+$aFirstNames[] = 'Emma';
+$aFirstNames[] = 'Juanita';
+$aFirstNames[] = 'Anita';
+$aFirstNames[] = 'Rhonda';
+$aFirstNames[] = 'Hazel';
+$aFirstNames[] = 'Amber';
+$aFirstNames[] = 'Eva';
+$aFirstNames[] = 'Debbie';
+$aFirstNames[] = 'April';
+$aFirstNames[] = 'Leslie';
+$aFirstNames[] = 'Clara';
+$aFirstNames[] = 'Lucille';
+$aFirstNames[] = 'Jamie';
+$aFirstNames[] = 'Joanne';
+$aFirstNames[] = 'Eleanor';
+$aFirstNames[] = 'Valerie';
+$aFirstNames[] = 'Danielle';
+$aFirstNames[] = 'Megan';
+$aFirstNames[] = 'Alicia';
+$aFirstNames[] = 'Suzanne';
+$aFirstNames[] = 'Michele';
+$aFirstNames[] = 'Gail';
+$aFirstNames[] = 'Bertha';
+$aFirstNames[] = 'Darlene';
+$aFirstNames[] = 'Veronica';
+$aFirstNames[] = 'Jill';
+$aFirstNames[] = 'Erin';
+$aFirstNames[] = 'Geraldine';
+$aFirstNames[] = 'Lauren';
+$aFirstNames[] = 'Cathy';
+$aFirstNames[] = 'Joann';
+$aFirstNames[] = 'Lorraine';
+$aFirstNames[] = 'Lynn';
+$aFirstNames[] = 'Sally';
+$aFirstNames[] = 'Regina';
+$aFirstNames[] = 'Erica';
+$aFirstNames[] = 'Beatrice';
+$aFirstNames[] = 'Dolores';
+$aFirstNames[] = 'Bernice';
+$aFirstNames[] = 'Audrey';
+$aFirstNames[] = 'Yvonne';
+$aFirstNames[] = 'Annette';
+$aFirstNames[] = 'June';
+$aFirstNames[] = 'Samantha';
+$aFirstNames[] = 'Marion';
+$aFirstNames[] = 'Dana';
+$aFirstNames[] = 'Stacy';
+$aFirstNames[] = 'Ana';
+$aFirstNames[] = 'Renee';
+$aFirstNames[] = 'Ida';
+$aFirstNames[] = 'Vivian';
+$aFirstNames[] = 'Roberta';
+$aFirstNames[] = 'Holly';
+$aFirstNames[] = 'Brittany';
+$aFirstNames[] = 'Melanie';
+$aFirstNames[] = 'Loretta';
+$aFirstNames[] = 'Yolanda';
+$aFirstNames[] = 'Jeanette';
+$aFirstNames[] = 'Laurie';
+$aFirstNames[] = 'Katie';
+$aFirstNames[] = 'Kristen';
+$aFirstNames[] = 'Vanessa';
+$aFirstNames[] = 'Alma';
+$aFirstNames[] = 'Sue';
+$aFirstNames[] = 'Elsie';
+$aFirstNames[] = 'Beth';
+$aFirstNames[] = 'Jeanne';
+$aFirstNames[] = 'Vicki';
+$aFirstNames[] = 'Carla';
+$aFirstNames[] = 'Tara';
+$aFirstNames[] = 'Rosemary';
+$aFirstNames[] = 'Eileen';
+$aFirstNames[] = 'Terri';
+$aFirstNames[] = 'Gertrude';
+$aFirstNames[] = 'Lucy';
+$aFirstNames[] = 'Tonya';
+$aFirstNames[] = 'Ella';
+$aFirstNames[] = 'Stacey';
+$aFirstNames[] = 'Wilma';
+$aFirstNames[] = 'Gina';
+$aFirstNames[] = 'Kristin';
+$aFirstNames[] = 'Jessie';
+$aFirstNames[] = 'Natalie';
+$aFirstNames[] = 'Agnes';
+$aFirstNames[] = 'Vera';
+$aFirstNames[] = 'Willie';
+$aFirstNames[] = 'Charlene';
+$aFirstNames[] = 'Bessie';
+$aFirstNames[] = 'Delores';
+$aFirstNames[] = 'Melinda';
+$aFirstNames[] = 'Pearl';
+$aFirstNames[] = 'Arlene';
+$aFirstNames[] = 'Maureen';
+$aFirstNames[] = 'Colleen';
+$aFirstNames[] = 'Allison';
+$aFirstNames[] = 'Tamara';
+$aFirstNames[] = 'Joy';
+$aFirstNames[] = 'Georgia';
+$aFirstNames[] = 'Constance';
+$aFirstNames[] = 'Lillie';
+$aFirstNames[] = 'Claudia';
+$aFirstNames[] = 'Jackie';
+$aFirstNames[] = 'Marcia';
+$aFirstNames[] = 'Tanya';
+$aFirstNames[] = 'Nellie';
+$aFirstNames[] = 'Minnie';
+$aFirstNames[] = 'Marlene';
+$aFirstNames[] = 'Heidi';
+$aFirstNames[] = 'Glenda';
+$aFirstNames[] = 'Lydia';
+$aFirstNames[] = 'Viola';
+$aFirstNames[] = 'Courtney';
+$aFirstNames[] = 'Marian';
+$aFirstNames[] = 'Stella';
+$aFirstNames[] = 'Caroline';
+$aFirstNames[] = 'Dora';
+$aFirstNames[] = 'Jo';
+$aFirstNames[] = 'Vickie';
+$aFirstNames[] = 'Mattie';
+$aFirstNames[] = 'Terry';
+$aFirstNames[] = 'Maxine';
+$aFirstNames[] = 'Irma';
+$aFirstNames[] = 'Mabel';
+$aFirstNames[] = 'Marsha';
+$aFirstNames[] = 'Myrtle';
+$aFirstNames[] = 'Lena';
+$aFirstNames[] = 'Christy';
+$aFirstNames[] = 'Deanna';
+$aFirstNames[] = 'Patsy';
+$aFirstNames[] = 'Hilda';
+$aFirstNames[] = 'Gwendolyn';
+$aFirstNames[] = 'Jennie';
+$aFirstNames[] = 'Nora';
+$aFirstNames[] = 'Margie';
+$aFirstNames[] = 'Nina';
+$aFirstNames[] = 'Cassandra';
+$aFirstNames[] = 'Leah';
+$aFirstNames[] = 'Penny';
+$aFirstNames[] = 'Kay';
+$aFirstNames[] = 'Priscilla';
+$aFirstNames[] = 'Naomi';
+$aFirstNames[] = 'Carole';
+$aFirstNames[] = 'Brandy';
+$aFirstNames[] = 'Olga';
+$aFirstNames[] = 'Billie';
+$aFirstNames[] = 'Dianne';
+$aFirstNames[] = 'Tracey';
+$aFirstNames[] = 'Leona';
+$aFirstNames[] = 'Jenny';
+$aFirstNames[] = 'Felicia';
+$aFirstNames[] = 'Sonia';
+$aFirstNames[] = 'Miriam';
+$aFirstNames[] = 'Velma';
+$aFirstNames[] = 'Becky';
+$aFirstNames[] = 'Bobbie';
+$aFirstNames[] = 'Violet';
+$aFirstNames[] = 'Kristina';
+$aFirstNames[] = 'Toni';
+$aFirstNames[] = 'Misty';
+$aFirstNames[] = 'Mae';
+$aFirstNames[] = 'Shelly';
+$aFirstNames[] = 'Daisy';
+$aFirstNames[] = 'Ramona';
+$aFirstNames[] = 'Sherri';
+$aFirstNames[] = 'Erika';
+$aFirstNames[] = 'Katrina';
+$aFirstNames[] = 'Claire';
+$aFirstNames[] = 'Lindsey';
+$aFirstNames[] = 'Lindsay';
+$aFirstNames[] = 'Geneva';
+$aFirstNames[] = 'Guadalupe';
+$aFirstNames[] = 'Belinda';
+$aFirstNames[] = 'Margarita';
+$aFirstNames[] = 'Sheryl';
+$aFirstNames[] = 'Cora';
+$aFirstNames[] = 'Faye';
+$aFirstNames[] = 'Ada';
+$aFirstNames[] = 'Natasha';
+$aFirstNames[] = 'Sabrina';
+$aFirstNames[] = 'Isabel';
+$aFirstNames[] = 'Marguerite';
+$aFirstNames[] = 'Hattie';
+$aFirstNames[] = 'Harriet';
+$aFirstNames[] = 'Molly';
+$aFirstNames[] = 'Cecilia';
+$aFirstNames[] = 'Kristi';
+$aFirstNames[] = 'Brandi';
+$aFirstNames[] = 'Blanche';
+$aFirstNames[] = 'Sandy';
+$aFirstNames[] = 'Rosie';
+$aFirstNames[] = 'Joanna';
+$aFirstNames[] = 'Iris';
+$aFirstNames[] = 'Eunice';
+$aFirstNames[] = 'Angie';
+$aFirstNames[] = 'Inez';
+$aFirstNames[] = 'Lynda';
+$aFirstNames[] = 'Madeline';
+$aFirstNames[] = 'Amelia';
+$aFirstNames[] = 'Alberta';
+$aFirstNames[] = 'Genevieve';
+$aFirstNames[] = 'Monique';
+$aFirstNames[] = 'Jodi';
+$aFirstNames[] = 'Janie';
+$aFirstNames[] = 'Maggie';
+$aFirstNames[] = 'Kayla';
+$aFirstNames[] = 'Sonya';
+$aFirstNames[] = 'Jan';
+$aFirstNames[] = 'Lee';
+$aFirstNames[] = 'Kristine';
+$aFirstNames[] = 'Candace';
+$aFirstNames[] = 'Fannie';
+$aFirstNames[] = 'Maryann';
+$aFirstNames[] = 'Opal';
+$aFirstNames[] = 'Alison';
+$aFirstNames[] = 'Yvette';
+$aFirstNames[] = 'Melody';
+$aFirstNames[] = 'Luz';
+$aFirstNames[] = 'Susie';
+$aFirstNames[] = 'Olivia';
+$aFirstNames[] = 'Flora';
+$aFirstNames[] = 'Shelley';
+$aFirstNames[] = 'Kristy';
+$aFirstNames[] = 'Mamie';
+$aFirstNames[] = 'Lula';
+$aFirstNames[] = 'Lola';
+$aFirstNames[] = 'Verna';
+$aFirstNames[] = 'Beulah';
+$aFirstNames[] = 'Antoinette';
+$aFirstNames[] = 'Candice';
+$aFirstNames[] = 'Juana';
+$aFirstNames[] = 'Jeannette';
+$aFirstNames[] = 'Pam';
+$aFirstNames[] = 'Kelli';
+$aFirstNames[] = 'Hannah';
+$aFirstNames[] = 'Whitney';
+$aFirstNames[] = 'Bridget';
+$aFirstNames[] = 'Karla';
+$aFirstNames[] = 'Celia';
+$aFirstNames[] = 'Latoya';
+$aFirstNames[] = 'Patty';
+$aFirstNames[] = 'Shelia';
+$aFirstNames[] = 'Gayle';
+$aFirstNames[] = 'Della';
+$aFirstNames[] = 'Vicky';
+$aFirstNames[] = 'Lynne';
+$aFirstNames[] = 'Sheri';
+$aFirstNames[] = 'Marianne';
+$aFirstNames[] = 'Kara';
+$aFirstNames[] = 'Jacquelyn';
+$aFirstNames[] = 'Erma';
+$aFirstNames[] = 'Blanca';
+$aFirstNames[] = 'Myra';
+$aFirstNames[] = 'Leticia';
+$aFirstNames[] = 'Pat';
+$aFirstNames[] = 'Krista';
+$aFirstNames[] = 'Roxanne';
+$aFirstNames[] = 'Angelica';
+$aFirstNames[] = 'Johnnie';
+$aFirstNames[] = 'Robyn';
+$aFirstNames[] = 'Francis';
+$aFirstNames[] = 'Adrienne';
+$aFirstNames[] = 'Rosalie';
+$aFirstNames[] = 'Alexandra';
+$aFirstNames[] = 'Brooke';
+$aFirstNames[] = 'Bethany';
+$aFirstNames[] = 'Sadie';
+$aFirstNames[] = 'Bernadette';
+$aFirstNames[] = 'Traci';
+$aFirstNames[] = 'Jody';
+$aFirstNames[] = 'Kendra';
+$aFirstNames[] = 'Jasmine';
+$aFirstNames[] = 'Nichole';
+$aFirstNames[] = 'Rachael';
+$aFirstNames[] = 'Chelsea';
+$aFirstNames[] = 'Mable';
+$aFirstNames[] = 'Ernestine';
+$aFirstNames[] = 'Muriel';
+$aFirstNames[] = 'Marcella';
+$aFirstNames[] = 'Elena';
+$aFirstNames[] = 'Krystal';
+$aFirstNames[] = 'Angelina';
+$aFirstNames[] = 'Nadine';
+$aFirstNames[] = 'Kari';
+$aFirstNames[] = 'Estelle';
+$aFirstNames[] = 'Dianna';
+$aFirstNames[] = 'Paulette';
+$aFirstNames[] = 'Lora';
+$aFirstNames[] = 'Mona';
+$aFirstNames[] = 'Doreen';
+$aFirstNames[] = 'Rosemarie';
+$aFirstNames[] = 'Angel';
+$aFirstNames[] = 'Desiree';
+$aFirstNames[] = 'Antonia';
+$aFirstNames[] = 'Hope';
+$aFirstNames[] = 'Ginger';
+$aFirstNames[] = 'Janis';
+$aFirstNames[] = 'Betsy';
+$aFirstNames[] = 'Christie';
+$aFirstNames[] = 'Freda';
+$aFirstNames[] = 'Mercedes';
+$aFirstNames[] = 'Meredith';
+$aFirstNames[] = 'Lynette';
+$aFirstNames[] = 'Teri';
+$aFirstNames[] = 'Cristina';
+$aFirstNames[] = 'Eula';
+$aFirstNames[] = 'Leigh';
+$aFirstNames[] = 'Meghan';
+$aFirstNames[] = 'Sophia';
+$aFirstNames[] = 'Eloise';
+$aFirstNames[] = 'Rochelle';
+$aFirstNames[] = 'Gretchen';
+$aFirstNames[] = 'Cecelia';
+$aFirstNames[] = 'Raquel';
+$aFirstNames[] = 'Henrietta';
+$aFirstNames[] = 'Alyssa';
+$aFirstNames[] = 'Jana';
+$aFirstNames[] = 'Kelley';
+$aFirstNames[] = 'Gwen';
+$aFirstNames[] = 'Kerry';
+$aFirstNames[] = 'Jenna';
+$aFirstNames[] = 'Tricia';
+$aFirstNames[] = 'Laverne';
+$aFirstNames[] = 'Olive';
+$aFirstNames[] = 'Alexis';
+$aFirstNames[] = 'Tasha';
+$aFirstNames[] = 'Silvia';
+$aFirstNames[] = 'Elvira';
+$aFirstNames[] = 'Casey';
+$aFirstNames[] = 'Delia';
+$aFirstNames[] = 'Sophie';
+$aFirstNames[] = 'Kate';
+$aFirstNames[] = 'Patti';
+$aFirstNames[] = 'Lorena';
+$aFirstNames[] = 'Kellie';
+$aFirstNames[] = 'Sonja';
+$aFirstNames[] = 'Lila';
+$aFirstNames[] = 'Lana';
+$aFirstNames[] = 'Darla';
+$aFirstNames[] = 'May';
+$aFirstNames[] = 'Mindy';
+$aFirstNames[] = 'Essie';
+$aFirstNames[] = 'Mandy';
+$aFirstNames[] = 'Lorene';
+$aFirstNames[] = 'Elsa';
+$aFirstNames[] = 'Josefina';
+$aFirstNames[] = 'Jeannie';
+$aFirstNames[] = 'Miranda';
+$aFirstNames[] = 'Dixie';
+$aFirstNames[] = 'Lucia';
+$aFirstNames[] = 'Marta';
+$aFirstNames[] = 'Faith';
+$aFirstNames[] = 'Lela';
+$aFirstNames[] = 'Johanna';
+$aFirstNames[] = 'Shari';
+$aFirstNames[] = 'Camille';
+$aFirstNames[] = 'Tami';
+$aFirstNames[] = 'Shawna';
+$aFirstNames[] = 'Elisa';
+$aFirstNames[] = 'Ebony';
+$aFirstNames[] = 'Melba';
+$aFirstNames[] = 'Ora';
+$aFirstNames[] = 'Nettie';
+$aFirstNames[] = 'Tabitha';
+$aFirstNames[] = 'Ollie';
+$aFirstNames[] = 'Jaime';
+$aFirstNames[] = 'Winifred';
+$aFirstNames[] = 'Kristie';
+$aFirstNames[] = 'Marina';
+$aFirstNames[] = 'Alisha';
+$aFirstNames[] = 'Aimee';
+$aFirstNames[] = 'Rena';
+$aFirstNames[] = 'Myrna';
+$aFirstNames[] = 'Marla';
+$aFirstNames[] = 'Tammie';
+$aFirstNames[] = 'Latasha';
+$aFirstNames[] = 'Bonita';
+$aFirstNames[] = 'Patrice';
+$aFirstNames[] = 'Ronda';
+$aFirstNames[] = 'Sherrie';
+$aFirstNames[] = 'Addie';
+$aFirstNames[] = 'Francine';
+$aFirstNames[] = 'Deloris';
+$aFirstNames[] = 'Stacie';
+$aFirstNames[] = 'Adriana';
+$aFirstNames[] = 'Cheri';
+$aFirstNames[] = 'Shelby';
+$aFirstNames[] = 'Abigail';
+$aFirstNames[] = 'Celeste';
+$aFirstNames[] = 'Jewel';
+$aFirstNames[] = 'Cara';
+$aFirstNames[] = 'Adele';
+$aFirstNames[] = 'Rebekah';
+$aFirstNames[] = 'Lucinda';
+$aFirstNames[] = 'Dorthy';
+$aFirstNames[] = 'Chris';
+$aFirstNames[] = 'Effie';
+$aFirstNames[] = 'Trina';
+$aFirstNames[] = 'Reba';
+$aFirstNames[] = 'Shawn';
+$aFirstNames[] = 'Sallie';
+$aFirstNames[] = 'Aurora';
+$aFirstNames[] = 'Lenora';
+$aFirstNames[] = 'Etta';
+$aFirstNames[] = 'Lottie';
+$aFirstNames[] = 'Kerri';
+$aFirstNames[] = 'Trisha';
+$aFirstNames[] = 'Nikki';
+$aFirstNames[] = 'Estella';
+$aFirstNames[] = 'Francisca';
+$aFirstNames[] = 'Josie';
+$aFirstNames[] = 'Tracie';
+$aFirstNames[] = 'Marissa';
+$aFirstNames[] = 'Karin';
+$aFirstNames[] = 'Brittney';
+$aFirstNames[] = 'Janelle';
+
+$aFirstNames[] = 'Lourdes';
+$aFirstNames[] = 'Laurel';
+$aFirstNames[] = 'Helene';
+$aFirstNames[] = 'Fern';
+$aFirstNames[] = 'Elva';
+$aFirstNames[] = 'Corinne';
+$aFirstNames[] = 'Kelsey';
+$aFirstNames[] = 'Ina';
+$aFirstNames[] = 'Bettie';
+$aFirstNames[] = 'Elisabeth';
+$aFirstNames[] = 'Aida';
+$aFirstNames[] = 'Caitlin';
+$aFirstNames[] = 'Ingrid';
+$aFirstNames[] = 'Iva';
+$aFirstNames[] = 'Eugenia';
+$aFirstNames[] = 'Christa';
+$aFirstNames[] = 'Goldie';
+$aFirstNames[] = 'Cassie';
+$aFirstNames[] = 'Maude';
+$aFirstNames[] = 'Jenifer';
+$aFirstNames[] = 'Therese';
+$aFirstNames[] = 'Frankie';
+$aFirstNames[] = 'Dena';
+$aFirstNames[] = 'Lorna';
+$aFirstNames[] = 'Janette';
+$aFirstNames[] = 'Latonya';
+$aFirstNames[] = 'Candy';
+$aFirstNames[] = 'Morgan';
+$aFirstNames[] = 'Consuelo';
+$aFirstNames[] = 'Tamika';
+$aFirstNames[] = 'Rosetta';
+$aFirstNames[] = 'Debora';
+$aFirstNames[] = 'Cherie';
+$aFirstNames[] = 'Polly';
+$aFirstNames[] = 'Dina';
+$aFirstNames[] = 'Jewell';
+$aFirstNames[] = 'Fay';
+$aFirstNames[] = 'Jillian';
+$aFirstNames[] = 'Dorothea';
+$aFirstNames[] = 'Nell';
+$aFirstNames[] = 'Trudy';
+$aFirstNames[] = 'Esperanza';
+$aFirstNames[] = 'Patrica';
+$aFirstNames[] = 'Kimberley';
+$aFirstNames[] = 'Shanna';
+$aFirstNames[] = 'Helena';
+$aFirstNames[] = 'Carolina';
+$aFirstNames[] = 'Cleo';
+$aFirstNames[] = 'Stefanie';
+$aFirstNames[] = 'Rosario';
+$aFirstNames[] = 'Ola';
+$aFirstNames[] = 'Janine';
+$aFirstNames[] = 'Mollie';
+$aFirstNames[] = 'Lupe';
+$aFirstNames[] = 'Alisa';
+$aFirstNames[] = 'Lou';
+$aFirstNames[] = 'Maribel';
+$aFirstNames[] = 'Susanne';
+$aFirstNames[] = 'Bette';
+$aFirstNames[] = 'Susana';
+$aFirstNames[] = 'Elise';
+$aFirstNames[] = 'Cecile';
+$aFirstNames[] = 'Isabelle';
+$aFirstNames[] = 'Lesley';
+$aFirstNames[] = 'Jocelyn';
+$aFirstNames[] = 'Paige';
+$aFirstNames[] = 'Joni';
+$aFirstNames[] = 'Rachelle';
+$aFirstNames[] = 'Leola';
+$aFirstNames[] = 'Daphne';
+$aFirstNames[] = 'Alta';
+$aFirstNames[] = 'Ester';
+$aFirstNames[] = 'Petra';
+$aFirstNames[] = 'Graciela';
+$aFirstNames[] = 'Imogene';
+$aFirstNames[] = 'Jolene';
+$aFirstNames[] = 'Keisha';
+$aFirstNames[] = 'Lacey';
+$aFirstNames[] = 'Glenna';
+$aFirstNames[] = 'Gabriela';
+$aFirstNames[] = 'Keri';
+$aFirstNames[] = 'Ursula';
+$aFirstNames[] = 'Lizzie';
+$aFirstNames[] = 'Kirsten';
+$aFirstNames[] = 'Shana';
+$aFirstNames[] = 'Adeline';
+$aFirstNames[] = 'Mayra';
+$aFirstNames[] = 'Jayne';
+$aFirstNames[] = 'Jaclyn';
+$aFirstNames[] = 'Gracie';
+$aFirstNames[] = 'Sondra';
+$aFirstNames[] = 'Carmela';
+$aFirstNames[] = 'Marisa';
+$aFirstNames[] = 'Rosalind';
+$aFirstNames[] = 'Charity';
+$aFirstNames[] = 'Tonia';
+$aFirstNames[] = 'Beatriz';
+$aFirstNames[] = 'Marisol';
+$aFirstNames[] = 'Clarice';
+$aFirstNames[] = 'Jeanine';
+$aFirstNames[] = 'Sheena';
+$aFirstNames[] = 'Angeline';
+$aFirstNames[] = 'Frieda';
+$aFirstNames[] = 'Lily';
+$aFirstNames[] = 'Robbie';
+$aFirstNames[] = 'Shauna';
+$aFirstNames[] = 'Millie';
+$aFirstNames[] = 'Claudette';
+$aFirstNames[] = 'Cathleen';
+$aFirstNames[] = 'Angelia';
+$aFirstNames[] = 'Gabrielle';
+$aFirstNames[] = 'Autumn';
+$aFirstNames[] = 'Katharine';
+$aFirstNames[] = 'Summer';
+$aFirstNames[] = 'Jodie';
+$aFirstNames[] = 'Staci';
+$aFirstNames[] = 'Lea';
+$aFirstNames[] = 'Christi';
+$aFirstNames[] = 'Jimmie';
+$aFirstNames[] = 'Justine';
+$aFirstNames[] = 'Elma';
+$aFirstNames[] = 'Luella';
+$aFirstNames[] = 'Margret';
+$aFirstNames[] = 'Dominique';
+$aFirstNames[] = 'Socorro';
+$aFirstNames[] = 'Rene';
+$aFirstNames[] = 'Martina';
+$aFirstNames[] = 'Margo';
+$aFirstNames[] = 'Mavis';
+$aFirstNames[] = 'Callie';
+$aFirstNames[] = 'Bobbi';
+$aFirstNames[] = 'Maritza';
+$aFirstNames[] = 'Lucile';
+$aFirstNames[] = 'Leanne';
+$aFirstNames[] = 'Jeannine';
+$aFirstNames[] = 'Deana';
+$aFirstNames[] = 'Aileen';
+$aFirstNames[] = 'Lorie';
+$aFirstNames[] = 'Ladonna';
+$aFirstNames[] = 'Willa';
+$aFirstNames[] = 'Manuela';
+$aFirstNames[] = 'Gale';
+$aFirstNames[] = 'Selma';
+$aFirstNames[] = 'Dolly';
+$aFirstNames[] = 'Sybil';
+$aFirstNames[] = 'Abby';
+$aFirstNames[] = 'Lara';
+$aFirstNames[] = 'Dale';
+$aFirstNames[] = 'Ivy';
+$aFirstNames[] = 'Dee';
+$aFirstNames[] = 'Winnie';
+$aFirstNames[] = 'Marcy';
+$aFirstNames[] = 'Luisa';
+$aFirstNames[] = 'Jeri';
+$aFirstNames[] = 'Magdalena';
+$aFirstNames[] = 'Ofelia';
+$aFirstNames[] = 'Meagan';
+$aFirstNames[] = 'Audra';
+$aFirstNames[] = 'Matilda';
+$aFirstNames[] = 'Leila';
+$aFirstNames[] = 'Cornelia';
+$aFirstNames[] = 'Bianca';
+$aFirstNames[] = 'Simone';
+$aFirstNames[] = 'Bettye';
+$aFirstNames[] = 'Randi';
+$aFirstNames[] = 'Virgie';
+$aFirstNames[] = 'Latisha';
+$aFirstNames[] = 'Barbra';
+$aFirstNames[] = 'Georgina';
+$aFirstNames[] = 'Eliza';
+$aFirstNames[] = 'Leann';
+$aFirstNames[] = 'Bridgette';
+$aFirstNames[] = 'Rhoda';
+$aFirstNames[] = 'Haley';
+$aFirstNames[] = 'Adela';
+$aFirstNames[] = 'Nola';
+$aFirstNames[] = 'Bernadine';
+$aFirstNames[] = 'Flossie';
+$aFirstNames[] = 'Ila';
+$aFirstNames[] = 'Greta';
+$aFirstNames[] = 'Ruthie';
+$aFirstNames[] = 'Nelda';
+$aFirstNames[] = 'Minerva';
+$aFirstNames[] = 'Lilly';
+$aFirstNames[] = 'Terrie';
+$aFirstNames[] = 'Letha';
+$aFirstNames[] = 'Hilary';
+$aFirstNames[] = 'Estela';
+$aFirstNames[] = 'Valarie';
+$aFirstNames[] = 'Brianna';
+$aFirstNames[] = 'Rosalyn';
+$aFirstNames[] = 'Earline';
+$aFirstNames[] = 'Catalina';
+$aFirstNames[] = 'Ava';
+$aFirstNames[] = 'Mia';
+$aFirstNames[] = 'Clarissa';
+$aFirstNames[] = 'Lidia';
+$aFirstNames[] = 'Corrine';
+$aFirstNames[] = 'Alexandria';
+$aFirstNames[] = 'Concepcion';
+$aFirstNames[] = 'Tia';
+$aFirstNames[] = 'Sharron';
+$aFirstNames[] = 'Rae';
+$aFirstNames[] = 'Dona';
+$aFirstNames[] = 'Ericka';
+$aFirstNames[] = 'Jami';
+$aFirstNames[] = 'Elnora';
+$aFirstNames[] = 'Chandra';
+$aFirstNames[] = 'Lenore';
+$aFirstNames[] = 'Neva';
+$aFirstNames[] = 'Marylou';
+$aFirstNames[] = 'Melisa';
+$aFirstNames[] = 'Tabatha';
+$aFirstNames[] = 'Serena';
+$aFirstNames[] = 'Avis';
+$aFirstNames[] = 'Allie';
+$aFirstNames[] = 'Sofia';
+$aFirstNames[] = 'Jeanie';
+$aFirstNames[] = 'Odessa';
+$aFirstNames[] = 'Nannie';
+$aFirstNames[] = 'Harriett';
+$aFirstNames[] = 'James';
+$aFirstNames[] = 'John';
+$aFirstNames[] = 'Robert';
+$aFirstNames[] = 'Michael';
+$aFirstNames[] = 'William';
+$aFirstNames[] = 'David';
+$aFirstNames[] = 'Richard';
+$aFirstNames[] = 'Charles';
+$aFirstNames[] = 'Joseph';
+$aFirstNames[] = 'Thomas';
+$aFirstNames[] = 'Christopher';
+$aFirstNames[] = 'Daniel';
+$aFirstNames[] = 'Paul';
+$aFirstNames[] = 'Mark';
+$aFirstNames[] = 'Donald';
+$aFirstNames[] = 'George';
+$aFirstNames[] = 'Kenneth';
+$aFirstNames[] = 'Steven';
+$aFirstNames[] = 'Edward';
+$aFirstNames[] = 'Brian';
+$aFirstNames[] = 'Ronald';
+$aFirstNames[] = 'Anthony';
+$aFirstNames[] = 'Kevin';
+$aFirstNames[] = 'Jason';
+$aFirstNames[] = 'Matthew';
+$aFirstNames[] = 'Gary';
+$aFirstNames[] = 'Timothy';
+$aFirstNames[] = 'Jose';
+$aFirstNames[] = 'Larry';
+$aFirstNames[] = 'Jeffrey';
+$aFirstNames[] = 'Frank';
+$aFirstNames[] = 'Scott';
+$aFirstNames[] = 'Eric';
+$aFirstNames[] = 'Stephen';
+$aFirstNames[] = 'Andrew';
+$aFirstNames[] = 'Raymond';
+$aFirstNames[] = 'Gregory';
+$aFirstNames[] = 'Joshua';
+$aFirstNames[] = 'Jerry';
+$aFirstNames[] = 'Dennis';
+$aFirstNames[] = 'Walter';
+$aFirstNames[] = 'Patrick';
+$aFirstNames[] = 'Peter';
+$aFirstNames[] = 'Harold';
+$aFirstNames[] = 'Douglas';
+$aFirstNames[] = 'Henry';
+$aFirstNames[] = 'Carl';
+$aFirstNames[] = 'Arthur';
+$aFirstNames[] = 'Ryan';
+$aFirstNames[] = 'Roger';
+$aFirstNames[] = 'Joe';
+$aFirstNames[] = 'Juan';
+$aFirstNames[] = 'Jack';
+$aFirstNames[] = 'Albert';
+$aFirstNames[] = 'Jonathan';
+$aFirstNames[] = 'Justin';
+$aFirstNames[] = 'Terry';
+$aFirstNames[] = 'Gerald';
+$aFirstNames[] = 'Keith';
+$aFirstNames[] = 'Samuel';
+$aFirstNames[] = 'Willie';
+$aFirstNames[] = 'Ralph';
+$aFirstNames[] = 'Lawrence';
+$aFirstNames[] = 'Nicholas';
+$aFirstNames[] = 'Roy';
+$aFirstNames[] = 'Benjamin';
+$aFirstNames[] = 'Bruce';
+$aFirstNames[] = 'Brandon';
+$aFirstNames[] = 'Adam';
+$aFirstNames[] = 'Harry';
+$aFirstNames[] = 'Fred';
+$aFirstNames[] = 'Wayne';
+$aFirstNames[] = 'Billy';
+$aFirstNames[] = 'Steve';
+$aFirstNames[] = 'Louis';
+$aFirstNames[] = 'Jeremy';
+$aFirstNames[] = 'Aaron';
+$aFirstNames[] = 'Randy';
+$aFirstNames[] = 'Howard';
+$aFirstNames[] = 'Eugene';
+$aFirstNames[] = 'Carlos';
+$aFirstNames[] = 'Russell';
+$aFirstNames[] = 'Bobby';
+$aFirstNames[] = 'Victor';
+$aFirstNames[] = 'Martin';
+$aFirstNames[] = 'Ernest';
+$aFirstNames[] = 'Phillip';
+$aFirstNames[] = 'Todd';
+$aFirstNames[] = 'Jesse';
+$aFirstNames[] = 'Craig';
+$aFirstNames[] = 'Alan';
+$aFirstNames[] = 'Shawn';
+$aFirstNames[] = 'Clarence';
+$aFirstNames[] = 'Sean';
+$aFirstNames[] = 'Philip';
+$aFirstNames[] = 'Chris';
+$aFirstNames[] = 'Johnny';
+$aFirstNames[] = 'Earl';
+$aFirstNames[] = 'Jimmy';
+$aFirstNames[] = 'Antonio';
+$aFirstNames[] = 'Danny';
+$aFirstNames[] = 'Bryan';
+$aFirstNames[] = 'Tony';
+$aFirstNames[] = 'Luis';
+$aFirstNames[] = 'Mike';
+$aFirstNames[] = 'Stanley';
+$aFirstNames[] = 'Leonard';
+$aFirstNames[] = 'Nathan';
+$aFirstNames[] = 'Dale';
+$aFirstNames[] = 'Manuel';
+$aFirstNames[] = 'Rodney';
+$aFirstNames[] = 'Curtis';
+$aFirstNames[] = 'Norman';
+$aFirstNames[] = 'Allen';
+$aFirstNames[] = 'Marvin';
+$aFirstNames[] = 'Vincent';
+$aFirstNames[] = 'Glenn';
+$aFirstNames[] = 'Jeffery';
+$aFirstNames[] = 'Travis';
+$aFirstNames[] = 'Jeff';
+$aFirstNames[] = 'Chad';
+$aFirstNames[] = 'Jacob';
+$aFirstNames[] = 'Lee';
+$aFirstNames[] = 'Melvin';
+$aFirstNames[] = 'Alfred';
+$aFirstNames[] = 'Kyle';
+$aFirstNames[] = 'Francis';
+$aFirstNames[] = 'Bradley';
+$aFirstNames[] = 'Jesus';
+$aFirstNames[] = 'Herbert';
+$aFirstNames[] = 'Frederick';
+$aFirstNames[] = 'Ray';
+$aFirstNames[] = 'Joel';
+$aFirstNames[] = 'Edwin';
+$aFirstNames[] = 'Don';
+$aFirstNames[] = 'Eddie';
+$aFirstNames[] = 'Ricky';
+$aFirstNames[] = 'Troy';
+$aFirstNames[] = 'Randall';
+$aFirstNames[] = 'Barry';
+$aFirstNames[] = 'Alexander';
+$aFirstNames[] = 'Bernard';
+$aFirstNames[] = 'Mario';
+$aFirstNames[] = 'Leroy';
+$aFirstNames[] = 'Francisco';
+$aFirstNames[] = 'Marcus';
+$aFirstNames[] = 'Micheal';
+$aFirstNames[] = 'Theodore';
+$aFirstNames[] = 'Clifford';
+$aFirstNames[] = 'Miguel';
+$aFirstNames[] = 'Oscar';
+$aFirstNames[] = 'Jay';
+$aFirstNames[] = 'Jim';
+$aFirstNames[] = 'Tom';
+$aFirstNames[] = 'Calvin';
+$aFirstNames[] = 'Alex';
+$aFirstNames[] = 'Jon';
+$aFirstNames[] = 'Ronnie';
+$aFirstNames[] = 'Bill';
+$aFirstNames[] = 'Lloyd';
+$aFirstNames[] = 'Tommy';
+$aFirstNames[] = 'Leon';
+$aFirstNames[] = 'Derek';
+$aFirstNames[] = 'Warren';
+$aFirstNames[] = 'Darrell';
+$aFirstNames[] = 'Jerome';
+$aFirstNames[] = 'Floyd';
+$aFirstNames[] = 'Leo';
+$aFirstNames[] = 'Alvin';
+$aFirstNames[] = 'Tim';
+$aFirstNames[] = 'Wesley';
+$aFirstNames[] = 'Gordon';
+$aFirstNames[] = 'Dean';
+$aFirstNames[] = 'Greg';
+$aFirstNames[] = 'Jorge';
+$aFirstNames[] = 'Dustin';
+$aFirstNames[] = 'Pedro';
+$aFirstNames[] = 'Derrick';
+$aFirstNames[] = 'Dan';
+$aFirstNames[] = 'Lewis';
+$aFirstNames[] = 'Zachary';
+$aFirstNames[] = 'Corey';
+$aFirstNames[] = 'Herman';
+$aFirstNames[] = 'Maurice';
+$aFirstNames[] = 'Vernon';
+$aFirstNames[] = 'Roberto';
+$aFirstNames[] = 'Clyde';
+$aFirstNames[] = 'Glen';
+$aFirstNames[] = 'Hector';
+$aFirstNames[] = 'Shane';
+$aFirstNames[] = 'Ricardo';
+$aFirstNames[] = 'Sam';
+$aFirstNames[] = 'Rick';
+$aFirstNames[] = 'Lester';
+$aFirstNames[] = 'Brent';
+$aFirstNames[] = 'Ramon';
+$aFirstNames[] = 'Charlie';
+$aFirstNames[] = 'Tyler';
+$aFirstNames[] = 'Gilbert';
+$aFirstNames[] = 'Gene';
+$aFirstNames[] = 'Marc';
+$aFirstNames[] = 'Reginald';
+$aFirstNames[] = 'Ruben';
+$aFirstNames[] = 'Brett';
+$aFirstNames[] = 'Angel';
+$aFirstNames[] = 'Nathaniel';
+$aFirstNames[] = 'Rafael';
+$aFirstNames[] = 'Leslie';
+$aFirstNames[] = 'Edgar';
+$aFirstNames[] = 'Milton';
+$aFirstNames[] = 'Raul';
+$aFirstNames[] = 'Ben';
+$aFirstNames[] = 'Chester';
+$aFirstNames[] = 'Cecil';
+$aFirstNames[] = 'Duane';
+$aFirstNames[] = 'Franklin';
+$aFirstNames[] = 'Andre';
+$aFirstNames[] = 'Elmer';
+$aFirstNames[] = 'Brad';
+$aFirstNames[] = 'Gabriel';
+$aFirstNames[] = 'Ron';
+$aFirstNames[] = 'Mitchell';
+$aFirstNames[] = 'Roland';
+$aFirstNames[] = 'Arnold';
+$aFirstNames[] = 'Harvey';
+$aFirstNames[] = 'Jared';
+$aFirstNames[] = 'Adrian';
+$aFirstNames[] = 'Karl';
+$aFirstNames[] = 'Cory';
+$aFirstNames[] = 'Claude';
+$aFirstNames[] = 'Erik';
+$aFirstNames[] = 'Darryl';
+$aFirstNames[] = 'Jamie';
+$aFirstNames[] = 'Neil';
+$aFirstNames[] = 'Jessie';
+$aFirstNames[] = 'Christian';
+$aFirstNames[] = 'Javier';
+$aFirstNames[] = 'Fernando';
+$aFirstNames[] = 'Clinton';
+$aFirstNames[] = 'Ted';
+$aFirstNames[] = 'Mathew';
+$aFirstNames[] = 'Tyrone';
+$aFirstNames[] = 'Darren';
+$aFirstNames[] = 'Lonnie';
+$aFirstNames[] = 'Lance';
+$aFirstNames[] = 'Cody';
+$aFirstNames[] = 'Julio';
+$aFirstNames[] = 'Kelly';
+$aFirstNames[] = 'Kurt';
+$aFirstNames[] = 'Allan';
+$aFirstNames[] = 'Nelson';
+$aFirstNames[] = 'Guy';
+$aFirstNames[] = 'Clayton';
+$aFirstNames[] = 'Hugh';
+$aFirstNames[] = 'Max';
+$aFirstNames[] = 'Dwayne';
+$aFirstNames[] = 'Dwight';
+$aFirstNames[] = 'Armando';
+$aFirstNames[] = 'Felix';
+$aFirstNames[] = 'Jimmie';
+$aFirstNames[] = 'Everett';
+$aFirstNames[] = 'Jordan';
+$aFirstNames[] = 'Ian';
+$aFirstNames[] = 'Wallace';
+$aFirstNames[] = 'Ken';
+$aFirstNames[] = 'Bob';
+$aFirstNames[] = 'Jaime';
+$aFirstNames[] = 'Casey';
+$aFirstNames[] = 'Alfredo';
+$aFirstNames[] = 'Alberto';
+$aFirstNames[] = 'Dave';
+$aFirstNames[] = 'Ivan';
+$aFirstNames[] = 'Johnnie';
+$aFirstNames[] = 'Sidney';
+$aFirstNames[] = 'Byron';
+$aFirstNames[] = 'Julian';
+$aFirstNames[] = 'Isaac';
+$aFirstNames[] = 'Morris';
+$aFirstNames[] = 'Clifton';
+$aFirstNames[] = 'Willard';
+$aFirstNames[] = 'Daryl';
+$aFirstNames[] = 'Ross';
+$aFirstNames[] = 'Virgil';
+$aFirstNames[] = 'Andy';
+$aFirstNames[] = 'Marshall';
+$aFirstNames[] = 'Salvador';
+$aFirstNames[] = 'Perry';
+$aFirstNames[] = 'Kirk';
+$aFirstNames[] = 'Sergio';
+$aFirstNames[] = 'Marion';
+$aFirstNames[] = 'Tracy';
+$aFirstNames[] = 'Seth';
+$aFirstNames[] = 'Kent';
+$aFirstNames[] = 'Terrance';
+$aFirstNames[] = 'Rene';
+$aFirstNames[] = 'Eduardo';
+$aFirstNames[] = 'Terrence';
+$aFirstNames[] = 'Enrique';
+$aFirstNames[] = 'Freddie';
+$aFirstNames[] = 'Wade';
+$aFirstNames[] = 'Austin';
+$aFirstNames[] = 'Stuart';
+$aFirstNames[] = 'Fredrick';
+$aFirstNames[] = 'Arturo';
+$aFirstNames[] = 'Alejandro';
+$aFirstNames[] = 'Jackie';
+$aFirstNames[] = 'Joey';
+$aFirstNames[] = 'Nick';
+$aFirstNames[] = 'Luther';
+$aFirstNames[] = 'Wendell';
+$aFirstNames[] = 'Jeremiah';
+$aFirstNames[] = 'Evan';
+$aFirstNames[] = 'Julius';
+$aFirstNames[] = 'Dana';
+$aFirstNames[] = 'Donnie';
+$aFirstNames[] = 'Otis';
+$aFirstNames[] = 'Shannon';
+$aFirstNames[] = 'Trevor';
+$aFirstNames[] = 'Oliver';
+$aFirstNames[] = 'Luke';
+$aFirstNames[] = 'Homer';
+$aFirstNames[] = 'Gerard';
+$aFirstNames[] = 'Doug';
+$aFirstNames[] = 'Kenny';
+$aFirstNames[] = 'Hubert';
+$aFirstNames[] = 'Angelo';
+$aFirstNames[] = 'Shaun';
+$aFirstNames[] = 'Lyle';
+$aFirstNames[] = 'Matt';
+$aFirstNames[] = 'Lynn';
+$aFirstNames[] = 'Alfonso';
+$aFirstNames[] = 'Orlando';
+$aFirstNames[] = 'Rex';
+$aFirstNames[] = 'Carlton';
+$aFirstNames[] = 'Ernesto';
+$aFirstNames[] = 'Cameron';
+$aFirstNames[] = 'Neal';
+$aFirstNames[] = 'Pablo';
+$aFirstNames[] = 'Lorenzo';
+$aFirstNames[] = 'Omar';
+$aFirstNames[] = 'Wilbur';
+$aFirstNames[] = 'Blake';
+$aFirstNames[] = 'Grant';
+$aFirstNames[] = 'Horace';
+$aFirstNames[] = 'Roderick';
+$aFirstNames[] = 'Kerry';
+$aFirstNames[] = 'Abraham';
+$aFirstNames[] = 'Willis';
+$aFirstNames[] = 'Rickey';
+$aFirstNames[] = 'Jean';
+$aFirstNames[] = 'Ira';
+$aFirstNames[] = 'Andres';
+$aFirstNames[] = 'Cesar';
+$aFirstNames[] = 'Johnathan';
+$aFirstNames[] = 'Malcolm';
+$aFirstNames[] = 'Rudolph';
+$aFirstNames[] = 'Damon';
+$aFirstNames[] = 'Kelvin';
+$aFirstNames[] = 'Rudy';
+$aFirstNames[] = 'Preston';
+$aFirstNames[] = 'Alton';
+$aFirstNames[] = 'Archie';
+$aFirstNames[] = 'Marco';
+$aFirstNames[] = 'Wm';
+$aFirstNames[] = 'Pete';
+$aFirstNames[] = 'Randolph';
+$aFirstNames[] = 'Garry';
+$aFirstNames[] = 'Geoffrey';
+$aFirstNames[] = 'Jonathon';
+$aFirstNames[] = 'Felipe';
+$aFirstNames[] = 'Bennie';
+$aFirstNames[] = 'Gerardo';
+$aFirstNames[] = 'Ed';
+$aFirstNames[] = 'Dominic';
+$aFirstNames[] = 'Robin';
+$aFirstNames[] = 'Loren';
+$aFirstNames[] = 'Delbert';
+$aFirstNames[] = 'Colin';
+$aFirstNames[] = 'Guillermo';
+$aFirstNames[] = 'Earnest';
+$aFirstNames[] = 'Lucas';
+$aFirstNames[] = 'Benny';
+$aFirstNames[] = 'Noel';
+$aFirstNames[] = 'Spencer';
+$aFirstNames[] = 'Rodolfo';
+$aFirstNames[] = 'Myron';
+$aFirstNames[] = 'Edmund';
+$aFirstNames[] = 'Garrett';
+$aFirstNames[] = 'Salvatore';
+$aFirstNames[] = 'Cedric';
+$aFirstNames[] = 'Lowell';
+$aFirstNames[] = 'Gregg';
+$aFirstNames[] = 'Sherman';
+$aFirstNames[] = 'Wilson';
+$aFirstNames[] = 'Devin';
+$aFirstNames[] = 'Sylvester';
+$aFirstNames[] = 'Kim';
+$aFirstNames[] = 'Roosevelt';
+$aFirstNames[] = 'Israel';
+$aFirstNames[] = 'Jermaine';
+$aFirstNames[] = 'Forrest';
+$aFirstNames[] = 'Wilbert';
+$aFirstNames[] = 'Leland';
+$aFirstNames[] = 'Simon';
+$aFirstNames[] = 'Guadalupe';
+$aFirstNames[] = 'Clark';
+$aFirstNames[] = 'Irving';
+$aFirstNames[] = 'Carroll';
+$aFirstNames[] = 'Bryant';
+$aFirstNames[] = 'Owen';
+$aFirstNames[] = 'Rufus';
+$aFirstNames[] = 'Woodrow';
+$aFirstNames[] = 'Sammy';
+$aFirstNames[] = 'Kristopher';
+$aFirstNames[] = 'Mack';
+$aFirstNames[] = 'Levi';
+$aFirstNames[] = 'Marcos';
+$aFirstNames[] = 'Gustavo';
+$aFirstNames[] = 'Jake';
+$aFirstNames[] = 'Lionel';
+$aFirstNames[] = 'Marty';
+$aFirstNames[] = 'Taylor';
+$aFirstNames[] = 'Ellis';
+$aFirstNames[] = 'Dallas';
+$aFirstNames[] = 'Gilberto';
+$aFirstNames[] = 'Clint';
+$aFirstNames[] = 'Nicolas';
+$aFirstNames[] = 'Laurence';
+$aFirstNames[] = 'Ismael';
+$aFirstNames[] = 'Orville';
+$aFirstNames[] = 'Drew';
+$aFirstNames[] = 'Jody';
+$aFirstNames[] = 'Ervin';
+$aFirstNames[] = 'Dewey';
+$aFirstNames[] = 'Al';
+$aFirstNames[] = 'Wilfred';
+$aFirstNames[] = 'Josh';
+$aFirstNames[] = 'Hugo';
+$aFirstNames[] = 'Ignacio';
+$aFirstNames[] = 'Caleb';
+$aFirstNames[] = 'Tomas';
+$aFirstNames[] = 'Sheldon';
+$aFirstNames[] = 'Erick';
+$aFirstNames[] = 'Frankie';
+$aFirstNames[] = 'Stewart';
+$aFirstNames[] = 'Doyle';
+$aFirstNames[] = 'Darrel';
+$aFirstNames[] = 'Rogelio';
+$aFirstNames[] = 'Terence';
+$aFirstNames[] = 'Santiago';
+$aFirstNames[] = 'Alonzo';
+$aFirstNames[] = 'Elias';
+$aFirstNames[] = 'Bert';
+$aFirstNames[] = 'Elbert';
+$aFirstNames[] = 'Ramiro';
+$aFirstNames[] = 'Conrad';
+$aFirstNames[] = 'Pat';
+$aFirstNames[] = 'Noah';
+$aFirstNames[] = 'Grady';
+$aFirstNames[] = 'Phil';
+$aFirstNames[] = 'Cornelius';
+$aFirstNames[] = 'Lamar';
+$aFirstNames[] = 'Rolando';
+$aFirstNames[] = 'Clay';
+$aFirstNames[] = 'Percy';
+$aFirstNames[] = 'Dexter';
+$aFirstNames[] = 'Bradford';
+$aFirstNames[] = 'Merle';
+$aFirstNames[] = 'Darin';
+$aFirstNames[] = 'Amos';
+$aFirstNames[] = 'Terrell';
+$aFirstNames[] = 'Moses';
+$aFirstNames[] = 'Irvin';
+$aFirstNames[] = 'Saul';
+$aFirstNames[] = 'Roman';
+$aFirstNames[] = 'Darnell';
+$aFirstNames[] = 'Randal';
+$aFirstNames[] = 'Tommie';
+$aFirstNames[] = 'Timmy';
+$aFirstNames[] = 'Darrin';
+$aFirstNames[] = 'Winston';
+$aFirstNames[] = 'Brendan';
+$aFirstNames[] = 'Toby';
+$aFirstNames[] = 'Van';
+$aFirstNames[] = 'Abel';
+$aFirstNames[] = 'Dominick';
+$aFirstNames[] = 'Boyd';
+$aFirstNames[] = 'Courtney';
+$aFirstNames[] = 'Jan';
+$aFirstNames[] = 'Emilio';
+$aFirstNames[] = 'Elijah';
+$aFirstNames[] = 'Cary';
+$aFirstNames[] = 'Domingo';
+$aFirstNames[] = 'Santos';
+$aFirstNames[] = 'Aubrey';
+$aFirstNames[] = 'Emmett';
+$aFirstNames[] = 'Marlon';
+$aFirstNames[] = 'Emanuel';
+$aFirstNames[] = 'Jerald';
+$aFirstNames[] = 'Edmond';
+$aFirstNames[] = 'Jan Jozef ';
+$aFirstNames[] = 'Konrad';
+$aFirstNames[] = 'Lidia';
+$aFirstNames[] = 'Lorenz';
+$aFirstNames[] = 'Otto';
+$aFirstNames[] = 'Piotr';
+$aFirstNames[] = 'Wladyslaw';
+
+$aNames = array();
+$aNames[] = 'Smith';
+$aNames[] = 'Johnson';
+$aNames[] = 'Williams';
+$aNames[] = 'Jones';
+$aNames[] = 'Brown';
+$aNames[] = 'Davis';
+$aNames[] = 'Miller';
+$aNames[] = 'Wilson';
+$aNames[] = 'Moore';
+$aNames[] = 'Taylor';
+$aNames[] = 'Anderson';
+$aNames[] = 'Thomas';
+$aNames[] = 'Jackson';
+$aNames[] = 'White';
+$aNames[] = 'Harris';
+$aNames[] = 'Martin';
+$aNames[] = 'Thompson';
+$aNames[] = 'Garcia';
+$aNames[] = 'Martinez';
+$aNames[] = 'Robinson';
+$aNames[] = 'Clark';
+$aNames[] = 'Rodriguez';
+$aNames[] = 'Lewis';
+$aNames[] = 'Lee';
+$aNames[] = 'Walker';
+$aNames[] = 'Hall';
+$aNames[] = 'Allen';
+$aNames[] = 'Young';
+$aNames[] = 'Hernandez';
+$aNames[] = 'King';
+$aNames[] = 'Wright';
+$aNames[] = 'Lopez';
+$aNames[] = 'Hill';
+$aNames[] = 'Scott';
+$aNames[] = 'Green';
+$aNames[] = 'Adams';
+$aNames[] = 'Baker';
+$aNames[] = 'Gonzalez';
+$aNames[] = 'Nelson';
+$aNames[] = 'Carter';
+$aNames[] = 'Mitchell';
+$aNames[] = 'Perez';
+$aNames[] = 'Roberts';
+$aNames[] = 'Turner';
+$aNames[] = 'Phillips';
+$aNames[] = 'Campbell';
+$aNames[] = 'Parker';
+$aNames[] = 'Evans';
+$aNames[] = 'Edwards';
+$aNames[] = 'Collins';
+$aNames[] = 'Stewart';
+$aNames[] = 'Sanchez';
+$aNames[] = 'Morris';
+$aNames[] = 'Rogers';
+$aNames[] = 'Reed';
+$aNames[] = 'Cook';
+$aNames[] = 'Morgan';
+$aNames[] = 'Bell';
+$aNames[] = 'Murphy';
+$aNames[] = 'Bailey';
+$aNames[] = 'Rivera';
+$aNames[] = 'Cooper';
+$aNames[] = 'Richardson';
+$aNames[] = 'Cox';
+$aNames[] = 'Howard';
+$aNames[] = 'Ward';
+$aNames[] = 'Torres';
+$aNames[] = 'Peterson';
+$aNames[] = 'Gray';
+$aNames[] = 'Ramirez';
+$aNames[] = 'James';
+$aNames[] = 'Watson';
+$aNames[] = 'Brooks';
+$aNames[] = 'Kelly';
+$aNames[] = 'Sanders';
+$aNames[] = 'Price';
+$aNames[] = 'Bennett';
+$aNames[] = 'Wood';
+$aNames[] = 'Barnes';
+$aNames[] = 'Ross';
+$aNames[] = 'Henderson';
+$aNames[] = 'Coleman';
+$aNames[] = 'Jenkins';
+$aNames[] = 'Perry';
+$aNames[] = 'Powell';
+$aNames[] = 'Long';
+$aNames[] = 'Patterson';
+$aNames[] = 'Hughes';
+$aNames[] = 'Flores';
+$aNames[] = 'Washington';
+$aNames[] = 'Butler';
+$aNames[] = 'Simmons';
+$aNames[] = 'Foster';
+$aNames[] = 'Gonzales';
+$aNames[] = 'Bryant';
+$aNames[] = 'Alexander';
+$aNames[] = 'Russell';
+$aNames[] = 'Griffin';
+$aNames[] = 'Diaz';
+$aNames[] = 'Hayes';
+$aNames[] = 'Myers';
+$aNames[] = 'Ford';
+$aNames[] = 'Hamilton';
+$aNames[] = 'Graham';
+$aNames[] = 'Sullivan';
+$aNames[] = 'Wallace';
+$aNames[] = 'Woods';
+$aNames[] = 'Cole';
+$aNames[] = 'West';
+$aNames[] = 'Jordan';
+$aNames[] = 'Owens';
+$aNames[] = 'Reynolds';
+$aNames[] = 'Fisher';
+$aNames[] = 'Ellis';
+$aNames[] = 'Harrison';
+$aNames[] = 'Gibson';
+$aNames[] = 'Mcdonald';
+$aNames[] = 'Cruz';
+$aNames[] = 'Marshall';
+$aNames[] = 'Ortiz';
+$aNames[] = 'Gomez';
+$aNames[] = 'Murray';
+$aNames[] = 'Freeman';
+$aNames[] = 'Wells';
+$aNames[] = 'Webb';
+$aNames[] = 'Simpson';
+$aNames[] = 'Stevens';
+$aNames[] = 'Tucker';
+$aNames[] = 'Porter';
+$aNames[] = 'Hunter';
+$aNames[] = 'Hicks';
+$aNames[] = 'Crawford';
+$aNames[] = 'Henry';
+$aNames[] = 'Boyd';
+$aNames[] = 'Mason';
+$aNames[] = 'Morales';
+$aNames[] = 'Kennedy';
+$aNames[] = 'Warren';
+$aNames[] = 'Dixon';
+$aNames[] = 'Ramos';
+$aNames[] = 'Reyes';
+$aNames[] = 'Burns';
+$aNames[] = 'Gordon';
+$aNames[] = 'Shaw';
+$aNames[] = 'Holmes';
+$aNames[] = 'Rice';
+$aNames[] = 'Robertson';
+$aNames[] = 'Hunt';
+$aNames[] = 'Black';
+$aNames[] = 'Daniels';
+$aNames[] = 'Palmer';
+$aNames[] = 'Mills';
+$aNames[] = 'Nichols';
+$aNames[] = 'Grant';
+$aNames[] = 'Knight';
+$aNames[] = 'Ferguson';
+$aNames[] = 'Rose';
+$aNames[] = 'Stone';
+$aNames[] = 'Hawkins';
+$aNames[] = 'Dunn';
+$aNames[] = 'Perkins';
+$aNames[] = 'Hudson';
+$aNames[] = 'Spencer';
+$aNames[] = 'Gardner';
+$aNames[] = 'Stephens';
+$aNames[] = 'Payne';
+$aNames[] = 'Pierce';
+$aNames[] = 'Berry';
+$aNames[] = 'Matthews';
+$aNames[] = 'Arnold';
+$aNames[] = 'Wagner';
+$aNames[] = 'Willis';
+$aNames[] = 'Ray';
+$aNames[] = 'Watkins';
+$aNames[] = 'Olson';
+$aNames[] = 'Carroll';
+$aNames[] = 'Duncan';
+$aNames[] = 'Snyder';
+$aNames[] = 'Hart';
+$aNames[] = 'Cunningham';
+$aNames[] = 'Bradley';
+$aNames[] = 'Lane';
+$aNames[] = 'Andrews';
+$aNames[] = 'Ruiz';
+$aNames[] = 'Harper';
+$aNames[] = 'Fox';
+$aNames[] = 'Riley';
+$aNames[] = 'Armstrong';
+$aNames[] = 'Carpenter';
+$aNames[] = 'Weaver';
+$aNames[] = 'Greene';
+$aNames[] = 'Lawrence';
+$aNames[] = 'Elliott';
+$aNames[] = 'Chavez';
+$aNames[] = 'Sims';
+$aNames[] = 'Austin';
+$aNames[] = 'Peters';
+$aNames[] = 'Kelley';
+$aNames[] = 'Franklin';
+$aNames[] = 'Lawson';
+$aNames[] = 'Fields';
+$aNames[] = 'Gutierrez';
+$aNames[] = 'Ryan';
+$aNames[] = 'Schmidt';
+$aNames[] = 'Carr';
+$aNames[] = 'Vasquez';
+$aNames[] = 'Castillo';
+$aNames[] = 'Wheeler';
+$aNames[] = 'Chapman';
+$aNames[] = 'Oliver';
+$aNames[] = 'Montgomery';
+$aNames[] = 'Richards';
+$aNames[] = 'Williamson';
+$aNames[] = 'Johnston';
+$aNames[] = 'Banks';
+$aNames[] = 'Meyer';
+$aNames[] = 'Bishop';
+$aNames[] = 'Mccoy';
+$aNames[] = 'Howell';
+$aNames[] = 'Alvarez';
+$aNames[] = 'Morrison';
+$aNames[] = 'Hansen';
+$aNames[] = 'Fernandez';
+$aNames[] = 'Garza';
+$aNames[] = 'Harvey';
+$aNames[] = 'Little';
+$aNames[] = 'Burton';
+$aNames[] = 'Stanley';
+$aNames[] = 'Nguyen';
+$aNames[] = 'George';
+$aNames[] = 'Jacobs';
+$aNames[] = 'Reid';
+$aNames[] = 'Kim';
+$aNames[] = 'Fuller';
+$aNames[] = 'Lynch';
+$aNames[] = 'Dean';
+$aNames[] = 'Gilbert';
+$aNames[] = 'Garrett';
+$aNames[] = 'Romero';
+$aNames[] = 'Welch';
+$aNames[] = 'Larson';
+$aNames[] = 'Frazier';
+$aNames[] = 'Burke';
+$aNames[] = 'Hanson';
+$aNames[] = 'Day';
+$aNames[] = 'Mendoza';
+$aNames[] = 'Moreno';
+$aNames[] = 'Bowman';
+$aNames[] = 'Medina';
+$aNames[] = 'Fowler';
+$aNames[] = 'Brewer';
+$aNames[] = 'Hoffman';
+$aNames[] = 'Carlson';
+$aNames[] = 'Silva';
+$aNames[] = 'Pearson';
+$aNames[] = 'Holland';
+$aNames[] = 'Douglas';
+$aNames[] = 'Fleming';
+$aNames[] = 'Jensen';
+$aNames[] = 'Vargas';
+$aNames[] = 'Byrd';
+$aNames[] = 'Davidson';
+$aNames[] = 'Hopkins';
+$aNames[] = 'May';
+$aNames[] = 'Terry';
+$aNames[] = 'Herrera';
+$aNames[] = 'Wade';
+$aNames[] = 'Soto';
+$aNames[] = 'Walters';
+$aNames[] = 'Curtis';
+$aNames[] = 'Neal';
+$aNames[] = 'Caldwell';
+$aNames[] = 'Lowe';
+$aNames[] = 'Jennings';
+$aNames[] = 'Barnett';
+$aNames[] = 'Graves';
+$aNames[] = 'Jimenez';
+$aNames[] = 'Horton';
+$aNames[] = 'Shelton';
+$aNames[] = 'Barrett';
+$aNames[] = 'Obrien';
+$aNames[] = 'Castro';
+$aNames[] = 'Sutton';
+$aNames[] = 'Gregory';
+$aNames[] = 'Mckinney';
+$aNames[] = 'Lucas';
+$aNames[] = 'Miles';
+$aNames[] = 'Craig';
+$aNames[] = 'Rodriquez';
+$aNames[] = 'Chambers';
+$aNames[] = 'Holt';
+$aNames[] = 'Lambert';
+$aNames[] = 'Fletcher';
+$aNames[] = 'Watts';
+$aNames[] = 'Bates';
+$aNames[] = 'Hale';
+$aNames[] = 'Rhodes';
+$aNames[] = 'Pena';
+$aNames[] = 'Beck';
+$aNames[] = 'Newman';
+$aNames[] = 'Haynes';
+$aNames[] = 'Mcdaniel';
+$aNames[] = 'Mendez';
+$aNames[] = 'Bush';
+$aNames[] = 'Vaughn';
+$aNames[] = 'Parks';
+$aNames[] = 'Dawson';
+$aNames[] = 'Santiago';
+$aNames[] = 'Norris';
+$aNames[] = 'Hardy';
+$aNames[] = 'Love';
+$aNames[] = 'Steele';
+$aNames[] = 'Curry';
+$aNames[] = 'Powers';
+$aNames[] = 'Schultz';
+$aNames[] = 'Barker';
+$aNames[] = 'Guzman';
+$aNames[] = 'Page';
+$aNames[] = 'Munoz';
+$aNames[] = 'Ball';
+$aNames[] = 'Keller';
+$aNames[] = 'Chandler';
+$aNames[] = 'Weber';
+$aNames[] = 'Leonard';
+$aNames[] = 'Walsh';
+$aNames[] = 'Lyons';
+$aNames[] = 'Ramsey';
+$aNames[] = 'Wolfe';
+$aNames[] = 'Schneider';
+$aNames[] = 'Mullins';
+$aNames[] = 'Benson';
+$aNames[] = 'Sharp';
+$aNames[] = 'Bowen';
+$aNames[] = 'Daniel';
+$aNames[] = 'Barber';
+$aNames[] = 'Cummings';
+$aNames[] = 'Hines';
+$aNames[] = 'Baldwin';
+$aNames[] = 'Griffith';
+$aNames[] = 'Valdez';
+$aNames[] = 'Hubbard';
+$aNames[] = 'Salazar';
+$aNames[] = 'Reeves';
+$aNames[] = 'Warner';
+$aNames[] = 'Stevenson';
+$aNames[] = 'Burgess';
+$aNames[] = 'Santos';
+$aNames[] = 'Tate';
+$aNames[] = 'Cross';
+$aNames[] = 'Garner';
+$aNames[] = 'Mann';
+$aNames[] = 'Mack';
+$aNames[] = 'Moss';
+$aNames[] = 'Thornton';
+$aNames[] = 'Dennis';
+$aNames[] = 'Mcgee';
+$aNames[] = 'Farmer';
+$aNames[] = 'Delgado';
+$aNames[] = 'Aguilar';
+$aNames[] = 'Vega';
+$aNames[] = 'Glover';
+$aNames[] = 'Manning';
+$aNames[] = 'Cohen';
+$aNames[] = 'Harmon';
+$aNames[] = 'Rodgers';
+$aNames[] = 'Robbins';
+$aNames[] = 'Newton';
+$aNames[] = 'Todd';
+$aNames[] = 'Blair';
+$aNames[] = 'Higgins';
+$aNames[] = 'Ingram';
+$aNames[] = 'Reese';
+$aNames[] = 'Cannon';
+$aNames[] = 'Strickland';
+$aNames[] = 'Townsend';
+$aNames[] = 'Potter';
+$aNames[] = 'Goodwin';
+$aNames[] = 'Walton';
+$aNames[] = 'Rowe';
+$aNames[] = 'Hampton';
+$aNames[] = 'Ortega';
+$aNames[] = 'Patton';
+$aNames[] = 'Swanson';
+$aNames[] = 'Joseph';
+$aNames[] = 'Francis';
+$aNames[] = 'Goodman';
+$aNames[] = 'Maldonado';
+$aNames[] = 'Yates';
+$aNames[] = 'Becker';
+$aNames[] = 'Erickson';
+$aNames[] = 'Hodges';
+$aNames[] = 'Rios';
+$aNames[] = 'Conner';
+$aNames[] = 'Adkins';
+$aNames[] = 'Webster';
+$aNames[] = 'Norman';
+$aNames[] = 'Malone';
+$aNames[] = 'Hammond';
+$aNames[] = 'Flowers';
+$aNames[] = 'Cobb';
+$aNames[] = 'Moody';
+$aNames[] = 'Quinn';
+$aNames[] = 'Blake';
+$aNames[] = 'Maxwell';
+$aNames[] = 'Pope';
+$aNames[] = 'Floyd';
+$aNames[] = 'Osborne';
+$aNames[] = 'Paul';
+$aNames[] = 'Mccarthy';
+$aNames[] = 'Guerrero';
+$aNames[] = 'Lindsey';
+$aNames[] = 'Estrada';
+$aNames[] = 'Sandoval';
+$aNames[] = 'Gibbs';
+$aNames[] = 'Tyler';
+$aNames[] = 'Gross';
+$aNames[] = 'Fitzgerald';
+$aNames[] = 'Stokes';
+$aNames[] = 'Doyle';
+$aNames[] = 'Sherman';
+$aNames[] = 'Saunders';
+$aNames[] = 'Wise';
+$aNames[] = 'Colon';
+$aNames[] = 'Gill';
+$aNames[] = 'Alvarado';
+$aNames[] = 'Greer';
+$aNames[] = 'Padilla';
+$aNames[] = 'Simon';
+$aNames[] = 'Waters';
+$aNames[] = 'Nunez';
+$aNames[] = 'Ballard';
+$aNames[] = 'Schwartz';
+$aNames[] = 'Mcbride';
+$aNames[] = 'Houston';
+$aNames[] = 'Christensen';
+$aNames[] = 'Klein';
+$aNames[] = 'Pratt';
+$aNames[] = 'Briggs';
+$aNames[] = 'Parsons';
+$aNames[] = 'Mclaughlin';
+$aNames[] = 'Zimmerman';
+$aNames[] = 'French';
+$aNames[] = 'Buchanan';
+$aNames[] = 'Moran';
+$aNames[] = 'Copeland';
+$aNames[] = 'Roy';
+$aNames[] = 'Pittman';
+$aNames[] = 'Brady';
+$aNames[] = 'Mccormick';
+$aNames[] = 'Holloway';
+$aNames[] = 'Brock';
+$aNames[] = 'Poole';
+$aNames[] = 'Frank';
+$aNames[] = 'Logan';
+$aNames[] = 'Owen';
+$aNames[] = 'Bass';
+$aNames[] = 'Marsh';
+$aNames[] = 'Drake';
+$aNames[] = 'Wong';
+$aNames[] = 'Jefferson';
+$aNames[] = 'Park';
+$aNames[] = 'Morton';
+$aNames[] = 'Abbott';
+$aNames[] = 'Sparks';
+$aNames[] = 'Patrick';
+$aNames[] = 'Norton';
+$aNames[] = 'Huff';
+$aNames[] = 'Clayton';
+$aNames[] = 'Massey';
+$aNames[] = 'Lloyd';
+$aNames[] = 'Figueroa';
+$aNames[] = 'Carson';
+$aNames[] = 'Bowers';
+$aNames[] = 'Roberson';
+$aNames[] = 'Barton';
+$aNames[] = 'Tran';
+$aNames[] = 'Lamb';
+$aNames[] = 'Harrington';
+$aNames[] = 'Casey';
+$aNames[] = 'Boone';
+$aNames[] = 'Cortez';
+$aNames[] = 'Clarke';
+$aNames[] = 'Mathis';
+$aNames[] = 'Singleton';
+$aNames[] = 'Wilkins';
+$aNames[] = 'Cain';
+$aNames[] = 'Bryan';
+$aNames[] = 'Underwood';
+$aNames[] = 'Hogan';
+$aNames[] = 'Mckenzie';
+$aNames[] = 'Collier';
+$aNames[] = 'Luna';
+$aNames[] = 'Phelps';
+$aNames[] = 'Mcguire';
+$aNames[] = 'Allison';
+$aNames[] = 'Bridges';
+$aNames[] = 'Wilkerson';
+$aNames[] = 'Nash';
+$aNames[] = 'Summers';
+$aNames[] = 'Atkins';
+$aNames[] = 'Wilcox';
+$aNames[] = 'Pitts';
+$aNames[] = 'Conley';
+$aNames[] = 'Marquez';
+$aNames[] = 'Burnett';
+$aNames[] = 'Richard';
+$aNames[] = 'Cochran';
+$aNames[] = 'Chase';
+$aNames[] = 'Davenport';
+$aNames[] = 'Hood';
+$aNames[] = 'Gates';
+$aNames[] = 'Clay';
+$aNames[] = 'Ayala';
+$aNames[] = 'Sawyer';
+$aNames[] = 'Roman';
+$aNames[] = 'Vazquez';
+$aNames[] = 'Dickerson';
+$aNames[] = 'Hodge';
+$aNames[] = 'Acosta';
+$aNames[] = 'Flynn';
+$aNames[] = 'Espinoza';
+$aNames[] = 'Nicholson';
+$aNames[] = 'Monroe';
+$aNames[] = 'Wolf';
+$aNames[] = 'Morrow';
+$aNames[] = 'Kirk';
+$aNames[] = 'Randall';
+$aNames[] = 'Anthony';
+$aNames[] = 'Whitaker';
+$aNames[] = 'Oconnor';
+$aNames[] = 'Skinner';
+$aNames[] = 'Ware';
+$aNames[] = 'Molina';
+$aNames[] = 'Kirby';
+$aNames[] = 'Huffman';
+$aNames[] = 'Bradford';
+$aNames[] = 'Charles';
+$aNames[] = 'Gilmore';
+$aNames[] = 'Dominguez';
+$aNames[] = 'Oneal';
+$aNames[] = 'Bruce';
+$aNames[] = 'Lang';
+$aNames[] = 'Combs';
+$aNames[] = 'Kramer';
+$aNames[] = 'Heath';
+$aNames[] = 'Hancock';
+$aNames[] = 'Gallagher';
+$aNames[] = 'Gaines';
+$aNames[] = 'Shaffer';
+$aNames[] = 'Short';
+$aNames[] = 'Wiggins';
+$aNames[] = 'Mathews';
+$aNames[] = 'Mcclain';
+$aNames[] = 'Fischer';
+$aNames[] = 'Wall';
+$aNames[] = 'Small';
+$aNames[] = 'Melton';
+$aNames[] = 'Hensley';
+$aNames[] = 'Bond';
+$aNames[] = 'Dyer';
+$aNames[] = 'Cameron';
+$aNames[] = 'Grimes';
+$aNames[] = 'Contreras';
+$aNames[] = 'Christian';
+$aNames[] = 'Wyatt';
+$aNames[] = 'Baxter';
+$aNames[] = 'Snow';
+$aNames[] = 'Mosley';
+$aNames[] = 'Shepherd';
+$aNames[] = 'Larsen';
+$aNames[] = 'Hoover';
+$aNames[] = 'Beasley';
+$aNames[] = 'Glenn';
+$aNames[] = 'Petersen';
+$aNames[] = 'Whitehead';
+$aNames[] = 'Meyers';
+$aNames[] = 'Keith';
+$aNames[] = 'Garrison';
+$aNames[] = 'Vincent';
+$aNames[] = 'Shields';
+$aNames[] = 'Horn';
+$aNames[] = 'Savage';
+$aNames[] = 'Olsen';
+$aNames[] = 'Schroeder';
+$aNames[] = 'Hartman';
+$aNames[] = 'Woodard';
+$aNames[] = 'Mueller';
+$aNames[] = 'Kemp';
+$aNames[] = 'Deleon';
+$aNames[] = 'Booth';
+$aNames[] = 'Patel';
+$aNames[] = 'Calhoun';
+$aNames[] = 'Wiley';
+$aNames[] = 'Eaton';
+$aNames[] = 'Cline';
+$aNames[] = 'Navarro';
+$aNames[] = 'Harrell';
+$aNames[] = 'Lester';
+$aNames[] = 'Humphrey';
+$aNames[] = 'Parrish';
+$aNames[] = 'Duran';
+$aNames[] = 'Hutchinson';
+$aNames[] = 'Hess';
+$aNames[] = 'Dorsey';
+$aNames[] = 'Bullock';
+$aNames[] = 'Robles';
+$aNames[] = 'Beard';
+$aNames[] = 'Dalton';
+$aNames[] = 'Avila';
+$aNames[] = 'Vance';
+$aNames[] = 'Rich';
+$aNames[] = 'Blackwell';
+$aNames[] = 'York';
+$aNames[] = 'Johns';
+$aNames[] = 'Blankenship';
+$aNames[] = 'Trevino';
+$aNames[] = 'Salinas';
+$aNames[] = 'Campos';
+$aNames[] = 'Pruitt';
+$aNames[] = 'Moses';
+$aNames[] = 'Callahan';
+$aNames[] = 'Golden';
+$aNames[] = 'Montoya';
+$aNames[] = 'Hardin';
+$aNames[] = 'Guerra';
+$aNames[] = 'Mcdowell';
+$aNames[] = 'Carey';
+$aNames[] = 'Stafford';
+$aNames[] = 'Gallegos';
+$aNames[] = 'Henson';
+$aNames[] = 'Wilkinson';
+$aNames[] = 'Booker';
+$aNames[] = 'Merritt';
+$aNames[] = 'Miranda';
+$aNames[] = 'Atkinson';
+$aNames[] = 'Orr';
+$aNames[] = 'Decker';
+$aNames[] = 'Hobbs';
+$aNames[] = 'Preston';
+$aNames[] = 'Tanner';
+$aNames[] = 'Knox';
+$aNames[] = 'Pacheco';
+$aNames[] = 'Stephenson';
+$aNames[] = 'Glass';
+$aNames[] = 'Rojas';
+$aNames[] = 'Serrano';
+$aNames[] = 'Marks';
+$aNames[] = 'Hickman';
+$aNames[] = 'English';
+$aNames[] = 'Sweeney';
+$aNames[] = 'Strong';
+$aNames[] = 'Prince';
+$aNames[] = 'Mcclure';
+$aNames[] = 'Conway';
+$aNames[] = 'Walter';
+$aNames[] = 'Roth';
+$aNames[] = 'Maynard';
+$aNames[] = 'Farrell';
+$aNames[] = 'Lowery';
+$aNames[] = 'Hurst';
+$aNames[] = 'Nixon';
+$aNames[] = 'Weiss';
+$aNames[] = 'Trujillo';
+$aNames[] = 'Ellison';
+$aNames[] = 'Sloan';
+$aNames[] = 'Juarez';
+$aNames[] = 'Winters';
+$aNames[] = 'Mclean';
+$aNames[] = 'Randolph';
+$aNames[] = 'Leon';
+$aNames[] = 'Boyer';
+$aNames[] = 'Villarreal';
+$aNames[] = 'Mccall';
+$aNames[] = 'Gentry';
+$aNames[] = 'Carrillo';
+$aNames[] = 'Kent';
+$aNames[] = 'Ayers';
+$aNames[] = 'Lara';
+$aNames[] = 'Shannon';
+$aNames[] = 'Sexton';
+$aNames[] = 'Pace';
+$aNames[] = 'Hull';
+$aNames[] = 'Leblanc';
+$aNames[] = 'Browning';
+$aNames[] = 'Velasquez';
+$aNames[] = 'Leach';
+$aNames[] = 'Chang';
+$aNames[] = 'House';
+$aNames[] = 'Sellers';
+$aNames[] = 'Herring';
+$aNames[] = 'Noble';
+$aNames[] = 'Foley';
+$aNames[] = 'Bartlett';
+$aNames[] = 'Mercado';
+$aNames[] = 'Landry';
+$aNames[] = 'Durham';
+$aNames[] = 'Walls';
+$aNames[] = 'Barr';
+$aNames[] = 'Mckee';
+$aNames[] = 'Bauer';
+$aNames[] = 'Rivers';
+$aNames[] = 'Everett';
+$aNames[] = 'Bradshaw';
+$aNames[] = 'Pugh';
+$aNames[] = 'Velez';
+$aNames[] = 'Rush';
+$aNames[] = 'Estes';
+$aNames[] = 'Dodson';
+$aNames[] = 'Morse';
+$aNames[] = 'Sheppard';
+$aNames[] = 'Weeks';
+$aNames[] = 'Camacho';
+$aNames[] = 'Bean';
+$aNames[] = 'Barron';
+$aNames[] = 'Livingston';
+$aNames[] = 'Middleton';
+$aNames[] = 'Spears';
+$aNames[] = 'Branch';
+$aNames[] = 'Blevins';
+$aNames[] = 'Chen';
+$aNames[] = 'Kerr';
+$aNames[] = 'Mcconnell';
+$aNames[] = 'Hatfield';
+$aNames[] = 'Harding';
+$aNames[] = 'Ashley';
+$aNames[] = 'Solis';
+$aNames[] = 'Herman';
+$aNames[] = 'Frost';
+$aNames[] = 'Giles';
+$aNames[] = 'Blackburn';
+$aNames[] = 'William';
+$aNames[] = 'Pennington';
+$aNames[] = 'Woodward';
+$aNames[] = 'Finley';
+$aNames[] = 'Mcintosh';
+$aNames[] = 'Koch';
+$aNames[] = 'Best';
+$aNames[] = 'Solomon';
+$aNames[] = 'Mccullough';
+$aNames[] = 'Dudley';
+$aNames[] = 'Nolan';
+$aNames[] = 'Blanchard';
+$aNames[] = 'Rivas';
+$aNames[] = 'Brennan';
+$aNames[] = 'Mejia';
+$aNames[] = 'Kane';
+$aNames[] = 'Benton';
+$aNames[] = 'Joyce';
+$aNames[] = 'Buckley';
+$aNames[] = 'Haley';
+$aNames[] = 'Valentine';
+$aNames[] = 'Maddox';
+$aNames[] = 'Russo';
+$aNames[] = 'Mcknight';
+$aNames[] = 'Buck';
+$aNames[] = 'Moon';
+$aNames[] = 'Mcmillan';
+$aNames[] = 'Crosby';
+$aNames[] = 'Berg';
+$aNames[] = 'Dotson';
+$aNames[] = 'Mays';
+$aNames[] = 'Roach';
+$aNames[] = 'Church';
+$aNames[] = 'Chan';
+$aNames[] = 'Richmond';
+$aNames[] = 'Meadows';
+$aNames[] = 'Faulkner';
+$aNames[] = 'Oneill';
+$aNames[] = 'Knapp';
+$aNames[] = 'Kline';
+$aNames[] = 'Barry';
+$aNames[] = 'Ochoa';
+$aNames[] = 'Jacobson';
+$aNames[] = 'Gay';
+$aNames[] = 'Avery';
+$aNames[] = 'Hendricks';
+$aNames[] = 'Horne';
+$aNames[] = 'Shepard';
+$aNames[] = 'Hebert';
+$aNames[] = 'Cherry';
+$aNames[] = 'Cardenas';
+$aNames[] = 'Mcintyre';
+$aNames[] = 'Whitney';
+$aNames[] = 'Waller';
+$aNames[] = 'Holman';
+$aNames[] = 'Donaldson';
+$aNames[] = 'Cantu';
+$aNames[] = 'Terrell';
+$aNames[] = 'Morin';
+$aNames[] = 'Gillespie';
+$aNames[] = 'Fuentes';
+$aNames[] = 'Tillman';
+$aNames[] = 'Sanford';
+$aNames[] = 'Bentley';
+$aNames[] = 'Peck';
+$aNames[] = 'Key';
+$aNames[] = 'Salas';
+$aNames[] = 'Rollins';
+$aNames[] = 'Gamble';
+$aNames[] = 'Dickson';
+$aNames[] = 'Battle';
+$aNames[] = 'Santana';
+$aNames[] = 'Cabrera';
+$aNames[] = 'Cervantes';
+$aNames[] = 'Howe';
+$aNames[] = 'Hinton';
+$aNames[] = 'Hurley';
+$aNames[] = 'Spence';
+$aNames[] = 'Zamora';
+$aNames[] = 'Yang';
+$aNames[] = 'Mcneil';
+$aNames[] = 'Suarez';
+$aNames[] = 'Case';
+$aNames[] = 'Petty';
+$aNames[] = 'Gould';
+$aNames[] = 'Mcfarland';
+$aNames[] = 'Sampson';
+$aNames[] = 'Carver';
+$aNames[] = 'Bray';
+$aNames[] = 'Rosario';
+$aNames[] = 'Macdonald';
+$aNames[] = 'Stout';
+$aNames[] = 'Hester';
+$aNames[] = 'Melendez';
+$aNames[] = 'Dillon';
+$aNames[] = 'Farley';
+$aNames[] = 'Hopper';
+$aNames[] = 'Galloway';
+$aNames[] = 'Potts';
+$aNames[] = 'Bernard';
+$aNames[] = 'Joyner';
+$aNames[] = 'Stein';
+$aNames[] = 'Aguirre';
+$aNames[] = 'Osborn';
+$aNames[] = 'Mercer';
+$aNames[] = 'Bender';
+$aNames[] = 'Franco';
+$aNames[] = 'Rowland';
+$aNames[] = 'Sykes';
+$aNames[] = 'Benjamin';
+$aNames[] = 'Travis';
+$aNames[] = 'Pickett';
+$aNames[] = 'Crane';
+$aNames[] = 'Sears';
+$aNames[] = 'Mayo';
+$aNames[] = 'Dunlap';
+$aNames[] = 'Hayden';
+$aNames[] = 'Wilder';
+$aNames[] = 'Mckay';
+$aNames[] = 'Coffey';
+$aNames[] = 'Mccarty';
+$aNames[] = 'Ewing';
+$aNames[] = 'Cooley';
+$aNames[] = 'Vaughan';
+$aNames[] = 'Bonner';
+$aNames[] = 'Cotton';
+$aNames[] = 'Holder';
+$aNames[] = 'Stark';
+$aNames[] = 'Ferrell';
+$aNames[] = 'Cantrell';
+$aNames[] = 'Fulton';
+$aNames[] = 'Lynn';
+$aNames[] = 'Lott';
+$aNames[] = 'Calderon';
+$aNames[] = 'Rosa';
+$aNames[] = 'Pollard';
+$aNames[] = 'Hooper';
+$aNames[] = 'Burch';
+$aNames[] = 'Mullen';
+$aNames[] = 'Fry';
+$aNames[] = 'Riddle';
+$aNames[] = 'Levy';
+$aNames[] = 'David';
+$aNames[] = 'Duke';
+$aNames[] = 'Odonnell';
+$aNames[] = 'Guy';
+$aNames[] = 'Michael';
+$aNames[] = 'Britt';
+$aNames[] = 'Frederick';
+$aNames[] = 'Daugherty';
+$aNames[] = 'Berger';
+$aNames[] = 'Dillard';
+$aNames[] = 'Alston';
+$aNames[] = 'Jarvis';
+$aNames[] = 'Frye';
+$aNames[] = 'Riggs';
+$aNames[] = 'Chaney';
+$aNames[] = 'Odom';
+$aNames[] = 'Duffy';
+$aNames[] = 'Fitzpatrick';
+$aNames[] = 'Valenzuela';
+$aNames[] = 'Merrill';
+$aNames[] = 'Mayer';
+$aNames[] = 'Alford';
+$aNames[] = 'Mcpherson';
+$aNames[] = 'Acevedo';
+$aNames[] = 'Donovan';
+$aNames[] = 'Barrera';
+$aNames[] = 'Albert';
+$aNames[] = 'Cote';
+$aNames[] = 'Reilly';
+$aNames[] = 'Compton';
+$aNames[] = 'Raymond';
+$aNames[] = 'Mooney';
+$aNames[] = 'Mcgowan';
+$aNames[] = 'Craft';
+$aNames[] = 'Cleveland';
+$aNames[] = 'Clemons';
+$aNames[] = 'Wynn';
+$aNames[] = 'Nielsen';
+$aNames[] = 'Baird';
+$aNames[] = 'Stanton';
+$aNames[] = 'Snider';
+$aNames[] = 'Rosales';
+$aNames[] = 'Bright';
+$aNames[] = 'Witt';
+$aNames[] = 'Stuart';
+$aNames[] = 'Hays';
+$aNames[] = 'Holden';
+$aNames[] = 'Rutledge';
+$aNames[] = 'Kinney';
+$aNames[] = 'Clements';
+$aNames[] = 'Castaneda';
+$aNames[] = 'Slater';
+$aNames[] = 'Hahn';
+$aNames[] = 'Emerson';
+$aNames[] = 'Conrad';
+$aNames[] = 'Burks';
+$aNames[] = 'Delaney';
+$aNames[] = 'Pate';
+$aNames[] = 'Lancaster';
+$aNames[] = 'Sweet';
+$aNames[] = 'Justice';
+$aNames[] = 'Tyson';
+$aNames[] = 'Sharpe';
+$aNames[] = 'Whitfield';
+$aNames[] = 'Talley';
+$aNames[] = 'Macias';
+$aNames[] = 'Irwin';
+$aNames[] = 'Burris';
+$aNames[] = 'Ratliff';
+$aNames[] = 'Mccray';
+$aNames[] = 'Madden';
+$aNames[] = 'Kaufman';
+$aNames[] = 'Beach';
+$aNames[] = 'Goff';
+$aNames[] = 'Cash';
+$aNames[] = 'Bolton';
+$aNames[] = 'Mcfadden';
+$aNames[] = 'Levine';
+$aNames[] = 'Good';
+$aNames[] = 'Byers';
+$aNames[] = 'Kirkland';
+$aNames[] = 'Kidd';
+$aNames[] = 'Workman';
+$aNames[] = 'Carney';
+$aNames[] = 'Dale';
+$aNames[] = 'Mcleod';
+$aNames[] = 'Holcomb';
+$aNames[] = 'England';
+$aNames[] = 'Finch';
+$aNames[] = 'Head';
+$aNames[] = 'Burt';
+$aNames[] = 'Hendrix';
+$aNames[] = 'Sosa';
+$aNames[] = 'Haney';
+$aNames[] = 'Franks';
+$aNames[] = 'Sargent';
+$aNames[] = 'Nieves';
+$aNames[] = 'Downs';
+$aNames[] = 'Rasmussen';
+$aNames[] = 'Bird';
+$aNames[] = 'Hewitt';
+$aNames[] = 'Lindsay';
+$aNames[] = 'Le';
+$aNames[] = 'Foreman';
+$aNames[] = 'Valencia';
+$aNames[] = 'Oneil';
+$aNames[] = 'Delacruz';
+$aNames[] = 'Vinson';
+$aNames[] = 'Dejesus';
+$aNames[] = 'Hyde';
+$aNames[] = 'Forbes';
+$aNames[] = 'Gilliam';
+$aNames[] = 'Guthrie';
+$aNames[] = 'Wooten';
+$aNames[] = 'Huber';
+$aNames[] = 'Barlow';
+$aNames[] = 'Boyle';
+$aNames[] = 'Mcmahon';
+$aNames[] = 'Buckner';
+$aNames[] = 'Rocha';
+$aNames[] = 'Puckett';
+$aNames[] = 'Langley';
+$aNames[] = 'Knowles';
+$aNames[] = 'Cooke';
+$aNames[] = 'Velazquez';
+$aNames[] = 'Whitley';
+$aNames[] = 'Noel';
+$aNames[] = 'Vang';
+$aNames[] = 'Shea';
+$aNames[] = 'Rouse';
+$aNames[] = 'Hartley';
+$aNames[] = 'Mayfield';
+$aNames[] = 'Elder';
+$aNames[] = 'Rankin';
+$aNames[] = 'Hanna';
+$aNames[] = 'Cowan';
+$aNames[] = 'Lucero';
+$aNames[] = 'Arroyo';
+$aNames[] = 'Slaughter';
+$aNames[] = 'Haas';
+$aNames[] = 'Oconnell';
+$aNames[] = 'Minor';
+$aNames[] = 'Kendrick';
+$aNames[] = 'Shirley';
+$aNames[] = 'Kendall';
+$aNames[] = 'Boucher';
+$aNames[] = 'Archer';
+$aNames[] = 'Boggs';
+$aNames[] = 'Odell';
+$aNames[] = 'Dougherty';
+$aNames[] = 'Andersen';
+$aNames[] = 'Newell';
+$aNames[] = 'Crowe';
+$aNames[] = 'Wang';
+$aNames[] = 'Friedman';
+$aNames[] = 'Bland';
+$aNames[] = 'Swain';
+$aNames[] = 'Holley';
+$aNames[] = 'Adam';
+$aNames[] = 'Adami';
+$aNames[] = 'Adriaen';
+$aNames[] = 'Adriaensen';
+$aNames[] = 'Adriaenssen';
+$aNames[] = 'Adriaenssens';
+$aNames[] = 'Adriencense';
+$aNames[] = 'Adriensence';
+$aNames[] = 'Adrienssens';
+$aNames[] = 'Aelter';
+$aNames[] = 'Aelterman';
+$aNames[] = 'Aelters';
+$aNames[] = 'Aerens';
+$aNames[] = 'Aerts';
+$aNames[] = 'Aertsens';
+$aNames[] = 'Albumazard';
+$aNames[] = 'Alloo';
+$aNames[] = 'Alsteen';
+$aNames[] = 'Andersson';
+$aNames[] = 'André';
+$aNames[] = 'Andries';
+$aNames[] = 'Andriessen';
+$aNames[] = 'Anthon';
+$aNames[] = 'Antoine';
+$aNames[] = 'Appelbaum';
+$aNames[] = 'Applaer';
+$aNames[] = 'Arimont';
+$aNames[] = 'Arquin';
+$aNames[] = 'Arteman';
+$aNames[] = 'Baert';
+$aNames[] = 'Bartholomeeus';
+$aNames[] = 'Bastien';
+$aNames[] = 'Bastin';
+$aNames[] = 'Baugnet';
+$aNames[] = 'Baugniet';
+$aNames[] = 'Baugniez';
+$aNames[] = 'Bauwens';
+$aNames[] = 'Beauve';
+$aNames[] = 'Beck';
+$aNames[] = 'Beckers';
+$aNames[] = 'Bernard';
+$aNames[] = 'Bertrand';
+$aNames[] = 'Bietmé';
+$aNames[] = 'Blaas';
+$aNames[] = 'Blankaert';
+$aNames[] = 'Blanquaert';
+$aNames[] = 'Blondeel';
+$aNames[] = 'Blondeeuw';
+$aNames[] = 'Blondoo';
+$aNames[] = 'Bodart';
+$aNames[] = 'Bodson';
+$aNames[] = 'Boeck';
+$aNames[] = 'Boesmans';
+$aNames[] = 'Bogaert';
+$aNames[] = 'Bogaerts';
+$aNames[] = 'Bogemans';
+$aNames[] = 'Booghmans';
+$aNames[] = 'Borremans';
+$aNames[] = 'Borsu';
+$aNames[] = 'Borsus';
+$aNames[] = 'Borsut';
+$aNames[] = 'Bosmans';
+$aNames[] = 'Bouch';
+$aNames[] = 'Bouchhout';
+$aNames[] = 'Bouillère';
+$aNames[] = 'Bouillet';
+$aNames[] = 'Boulanger';
+$aNames[] = 'Bourton';
+$aNames[] = 'Bouxin';
+$aNames[] = 'Brasseur';
+$aNames[] = 'Brouck';
+$aNames[] = 'Broucke';
+$aNames[] = 'Broucq';
+$aNames[] = 'Broucque';
+$aNames[] = 'Brouhier';
+$aNames[] = 'Brug';
+$aNames[] = 'Bruggesman';
+$aNames[] = 'Bruynseel';
+$aNames[] = 'Bruynseels';
+$aNames[] = 'Burger';
+$aNames[] = 'Burghgraeve';
+$aNames[] = 'Burgmeester';
+$aNames[] = 'Burton';
+$aNames[] = 'Burtont';
+$aNames[] = 'Buyle';
+$aNames[] = 'Calbert';
+$aNames[] = 'Callebaut';
+$aNames[] = 'Callebert';
+$aNames[] = 'Callebout';
+$aNames[] = 'Camby';
+$aNames[] = 'Cappelaere';
+$aNames[] = 'Cappelaire';
+$aNames[] = 'Cappelier';
+$aNames[] = 'Cappeliez';
+$aNames[] = 'Cappellier';
+$aNames[] = 'Carbonez';
+$aNames[] = 'Carbonnez';
+$aNames[] = 'Carlier';
+$aNames[] = 'Casteau';
+$aNames[] = 'Castel';
+$aNames[] = 'Castiaux';
+$aNames[] = 'Cauderlier';
+$aNames[] = 'Caudron';
+$aNames[] = 'Cauvel';
+$aNames[] = 'Cauvet';
+$aNames[] = 'Cauvin';
+$aNames[] = 'Cavard';
+$aNames[] = 'Ceulemans';
+$aNames[] = 'Chantry';
+$aNames[] = 'Charlier';
+$aNames[] = 'Chêneboit';
+$aNames[] = 'Chestay';
+$aNames[] = 'Chestia';
+$aNames[] = 'Chrispeels';
+$aNames[] = 'Christiaens';
+$aNames[] = 'Christoffel';
+$aNames[] = 'Claes';
+$aNames[] = 'Claessens';
+$aNames[] = 'Claeys';
+$aNames[] = 'Claus';
+$aNames[] = 'Cléban';
+$aNames[] = 'Clébant';
+$aNames[] = 'Clerx';
+$aNames[] = 'Colinus';
+$aNames[] = 'Collard';
+$aNames[] = 'Colleye';
+$aNames[] = 'Collignon';
+$aNames[] = 'Collin';
+$aNames[] = 'Colson';
+$aNames[] = 'Cool';
+$aNames[] = 'Cools';
+$aNames[] = 'Coppens';
+$aNames[] = 'Corain';
+$aNames[] = 'Corijn';
+$aNames[] = 'Corin';
+$aNames[] = 'Cornelis';
+$aNames[] = 'Cornet';
+$aNames[] = 'Corrin';
+$aNames[] = 'Corring';
+$aNames[] = 'Corringer';
+$aNames[] = 'Coryn';
+$aNames[] = 'Coudyser';
+$aNames[] = 'Couhysder';
+$aNames[] = 'Coutijser';
+$aNames[] = 'Coutiser';
+$aNames[] = 'Crab';
+$aNames[] = 'Crabbe';
+$aNames[] = 'Crama';
+$aNames[] = 'Crépez';
+$aNames[] = 'Crespel';
+$aNames[] = 'Crevisse';
+$aNames[] = 'Crevits';
+$aNames[] = 'Crispeel';
+$aNames[] = 'Crispeels';
+$aNames[] = 'Crispel';
+$aNames[] = 'Crispiels';
+$aNames[] = 'Cuvelier';
+$aNames[] = 'Cuypers';
+$aNames[] = 'Daan';
+$aNames[] = 'Daels';
+$aNames[] = 'Daems';
+$aNames[] = 'Dalmans';
+$aNames[] = 'Damard';
+$aNames[] = 'Damart';
+$aNames[] = 'Danis';
+$aNames[] = 'Dany';
+$aNames[] = 'Danys';
+$aNames[] = 'Dapvril';
+$aNames[] = 'Daufresne';
+$aNames[] = 'Dawance';
+$aNames[] = 'Debacker';
+$aNames[] = 'Debaere';
+$aNames[] = 'Debakker';
+$aNames[] = 'Debaut';
+$aNames[] = 'Debecker';
+$aNames[] = 'Debekker';
+$aNames[] = 'Debled';
+$aNames[] = 'Deboschere';
+$aNames[] = 'Deboscker';
+$aNames[] = 'Deboskre';
+$aNames[] = 'Debosscher';
+$aNames[] = 'Debosschere';
+$aNames[] = 'Debusschere';
+$aNames[] = 'Debuyst';
+$aNames[] = 'Declerck';
+$aNames[] = 'Declercq';
+$aNames[] = 'Decock';
+$aNames[] = 'Decocq';
+$aNames[] = 'Decrucq';
+$aNames[] = 'Decruyenaere';
+$aNames[] = 'Defaux';
+$aNames[] = 'Defawe';
+$aNames[] = 'Degroote';
+$aNames[] = 'Dehoorne';
+$aNames[] = 'Dehorne';
+$aNames[] = 'Dehornes';
+$aNames[] = 'Deilgat';
+$aNames[] = 'Dejong';
+$aNames[] = 'Dejonghe';
+$aNames[] = 'Dekale';
+$aNames[] = 'Dekimpe';
+$aNames[] = 'Dekoch';
+$aNames[] = 'Dekuiper';
+$aNames[] = 'Dekyndt';
+$aNames[] = 'Delacuvellerie';
+$aNames[] = 'Delafosse';
+$aNames[] = 'Delahaye';
+$aNames[] = 'Delahayes';
+$aNames[] = 'Delbouille';
+$aNames[] = 'Delboulle';
+$aNames[] = 'Delcorps';
+$aNames[] = 'Delflache';
+$aNames[] = 'Delfosse';
+$aNames[] = 'Delgat';
+$aNames[] = 'Delhaye';
+$aNames[] = 'Delhoste';
+$aNames[] = 'Delhotte';
+$aNames[] = 'Delmare';
+$aNames[] = 'Delmer';
+$aNames[] = 'Delobbe';
+$aNames[] = 'Delobe';
+$aNames[] = 'Delobes';
+$aNames[] = 'Delplace';
+$aNames[] = 'Delvaux';
+$aNames[] = 'Demain';
+$aNames[] = 'Demeiere';
+$aNames[] = 'Demeyer';
+$aNames[] = 'Demoor';
+$aNames[] = 'Demoore';
+$aNames[] = 'Demunck';
+$aNames[] = 'Demuynck';
+$aNames[] = 'Den';
+$aNames[] = 'Denaeyer';
+$aNames[] = 'Denayer';
+$aNames[] = 'Deneyer';
+$aNames[] = 'Denis';
+$aNames[] = 'Denoor';
+$aNames[] = 'Depannemaecker';
+$aNames[] = 'Depelsemacker';
+$aNames[] = 'Depelsemaeker';
+$aNames[] = 'Depelsenaire';
+$aNames[] = 'Depelseneer';
+$aNames[] = 'Depercenaire';
+$aNames[] = 'Depester';
+$aNames[] = 'Depiéreux';
+$aNames[] = 'Depierreux';
+$aNames[] = 'Depireux';
+$aNames[] = 'Depoorter';
+$aNames[] = 'Depoortere';
+$aNames[] = 'Depooter';
+$aNames[] = 'Depootere';
+$aNames[] = 'Deporter';
+$aNames[] = 'Deportere';
+$aNames[] = 'Depoterre';
+$aNames[] = 'Deprez';
+$aNames[] = 'Deramaix';
+$aNames[] = 'Deroosse';
+$aNames[] = 'Desandrouins';
+$aNames[] = 'Descamps';
+$aNames[] = 'Deschepper';
+$aNames[] = 'Desmedt';
+$aNames[] = 'Desmet';
+$aNames[] = 'Desmets';
+$aNames[] = 'Desmeytere';
+$aNames[] = 'Desmidt';
+$aNames[] = 'Desmidts';
+$aNames[] = 'Desmit';
+$aNames[] = 'Desmyter';
+$aNames[] = 'Desmytter';
+$aNames[] = 'Desmyttere';
+$aNames[] = 'Després';
+$aNames[] = 'Despret';
+$aNames[] = 'Desprets';
+$aNames[] = 'Despretz';
+$aNames[] = 'Desprey';
+$aNames[] = 'Desprez';
+$aNames[] = 'Destoute';
+$aNames[] = 'Deswart';
+$aNames[] = 'Deswarte';
+$aNames[] = 'Dethier';
+$aNames[] = 'Deur';
+$aNames[] = 'Deurwaerder';
+$aNames[] = 'Devis';
+$aNames[] = 'Devloo';
+$aNames[] = 'Devos';
+$aNames[] = 'Devriend';
+$aNames[] = 'Dewever';
+$aNames[] = 'Dewit';
+$aNames[] = 'Dewitte';
+$aNames[] = 'Dewyse';
+$aNames[] = 'Dhaeyer';
+$aNames[] = "D'Haeyer";
+$aNames[] = 'Dhoeraen';
+$aNames[] = "D'Hoeraen";
+$aNames[] = "D'Hoolaege";
+$aNames[] = 'Dierckx';
+$aNames[] = 'Dierik';
+$aNames[] = 'Doeraene';
+$aNames[] = 'Dolhaeghe';
+$aNames[] = 'Domiens';
+$aNames[] = 'Dominicus';
+$aNames[] = 'Dondaine';
+$aNames[] = 'Dondeine';
+$aNames[] = 'Dondenne';
+$aNames[] = 'Dondeyne';
+$aNames[] = 'Doolaeghe';
+$aNames[] = 'Doolaegue';
+$aNames[] = 'Doolage';
+$aNames[] = 'Doorn';
+$aNames[] = 'Doorne';
+$aNames[] = 'Doorneman';
+$aNames[] = 'Draier';
+$aNames[] = 'Dresselaers';
+$aNames[] = 'Dubled';
+$aNames[] = 'Dubois';
+$aNames[] = 'Dumont';
+$aNames[] = 'Dupont';
+$aNames[] = 'Duquesnay';
+$aNames[] = 'Duquesne';
+$aNames[] = 'Duquesnoy';
+$aNames[] = 'Ebrard';
+$aNames[] = 'Eeckeman';
+$aNames[] = 'Eerkens';
+$aNames[] = 'Erckens';
+$aNames[] = 'Erk';
+$aNames[] = 'Erken';
+$aNames[] = 'Erkens';
+$aNames[] = 'Etienne';
+$aNames[] = 'Euvrard';
+$aNames[] = 'Evert';
+$aNames[] = 'Evrard';
+$aNames[] = 'Evras';
+$aNames[] = 'Evrat';
+$aNames[] = 'Eyck';
+$aNames[] = 'Eysermans';
+$aNames[] = 'Fawat';
+$aNames[] = 'Faweux';
+$aNames[] = 'Fee';
+$aNames[] = 'Felix';
+$aNames[] = 'Flamenck';
+$aNames[] = 'Floche';
+$aNames[] = 'Floquet';
+$aNames[] = 'Fontaine';
+$aNames[] = 'Fonteyne';
+$aNames[] = 'Fraigany';
+$aNames[] = 'Fraigneux';
+$aNames[] = 'Francoeur';
+$aNames[] = 'François';
+$aNames[] = 'Francon';
+$aNames[] = 'Frankel';
+$aNames[] = 'Franken';
+$aNames[] = 'Frankeur';
+$aNames[] = 'Frans';
+$aNames[] = 'Fransman';
+$aNames[] = 'Fransolet';
+$aNames[] = 'Franzman';
+$aNames[] = 'Frijer';
+$aNames[] = 'Gabriels';
+$aNames[] = 'Gadisseur';
+$aNames[] = 'Gadisseux';
+$aNames[] = 'Gasthuys';
+$aNames[] = 'Gaudisseu';
+$aNames[] = 'Geeregat';
+$aNames[] = 'Geerts';
+$aNames[] = 'Geerts';
+$aNames[] = 'Geets';
+$aNames[] = 'Gehucht';
+$aNames[] = 'Geiregat';
+$aNames[] = 'Gendebien';
+$aNames[] = 'Genot';
+$aNames[] = 'Georges';
+$aNames[] = 'Gérard';
+$aNames[] = 'Gerlache';
+$aNames[] = 'Gerlaxhe';
+$aNames[] = 'Germay';
+$aNames[] = 'Germéa';
+$aNames[] = 'Germeau';
+$aNames[] = 'Ghiste';
+$aNames[] = 'Gidts';
+$aNames[] = 'Giets';
+$aNames[] = 'Gilles';
+$aNames[] = 'Gillet';
+$aNames[] = 'Gilson';
+$aNames[] = 'Gits';
+$aNames[] = 'Glaze';
+$aNames[] = 'Glazeman';
+$aNames[] = 'Goethals';
+$aNames[] = 'Goffin';
+$aNames[] = 'Gomaert';
+$aNames[] = 'Gomardt';
+$aNames[] = 'Goor';
+$aNames[] = 'Goossens';
+$aNames[] = 'Goud';
+$aNames[] = 'Goudman';
+$aNames[] = 'Goudsmith';
+$aNames[] = 'Gourdet';
+$aNames[] = 'Gousson';
+$aNames[] = 'Graas';
+$aNames[] = 'Greggs';
+$aNames[] = 'Gregh';
+$aNames[] = 'Grégoire';
+$aNames[] = 'Gregoor';
+$aNames[] = 'Grewis';
+$aNames[] = 'Groot';
+$aNames[] = 'Groote';
+$aNames[] = 'Grotaers';
+$aNames[] = 'Guillaume';
+$aNames[] = 'Guyaux';
+$aNames[] = 'Haesen';
+$aNames[] = 'Haesevoets';
+$aNames[] = 'Halasi';
+$aNames[] = 'Halazy';
+$aNames[] = 'Hamers';
+$aNames[] = 'Hanssens';
+$aNames[] = 'Hardas';
+$aNames[] = 'Hardat';
+$aNames[] = 'Hardy';
+$aNames[] = 'Heerbrant';
+$aNames[] = 'Hendrick';
+$aNames[] = 'Hendrickx';
+$aNames[] = 'Hendriks';
+$aNames[] = 'Henry';
+$aNames[] = 'Herbrand';
+$aNames[] = 'Herbrandt';
+$aNames[] = 'Herbrant';
+$aNames[] = 'Herman';
+$aNames[] = 'Hermann';
+$aNames[] = 'Hermans';
+$aNames[] = 'Herten';
+$aNames[] = 'Hertogs';
+$aNames[] = 'Hertogue';
+$aNames[] = 'Heylen';
+$aNames[] = 'Heymans';
+$aNames[] = 'Heynemans';
+$aNames[] = 'Heyrman';
+$aNames[] = 'Hinck';
+$aNames[] = 'Hinckel';
+$aNames[] = 'Hincker';
+$aNames[] = 'Hinkel';
+$aNames[] = 'Hinkels';
+$aNames[] = 'Hinkens';
+$aNames[] = 'Hinker';
+$aNames[] = 'Hinkle';
+$aNames[] = 'Hoefnagel';
+$aNames[] = 'Hoefnagels';
+$aNames[] = 'Holemans';
+$aNames[] = 'Honnay';
+$aNames[] = 'Horlin';
+$aNames[] = 'Houvenaghel';
+$aNames[] = 'Hoyois';
+$aNames[] = 'Hubert';
+$aNames[] = 'Huig';
+$aNames[] = 'Ickx';
+$aNames[] = 'Istace';
+$aNames[] = 'Istasse';
+$aNames[] = 'Jaak';
+$aNames[] = 'Jaap';
+$aNames[] = 'Jacob';
+$aNames[] = 'Jacobs';
+$aNames[] = 'Jacques';
+$aNames[] = 'Jacquet';
+$aNames[] = 'Jan';
+$aNames[] = 'Janhes';
+$aNames[] = 'Jansen';
+$aNames[] = 'Janssen';
+$aNames[] = 'Janssens';
+$aNames[] = 'Jef';
+$aNames[] = 'Jenot';
+$aNames[] = 'Jeuniaux';
+$aNames[] = 'Joire';
+$aNames[] = 'Jone';
+$aNames[] = 'Joneau';
+$aNames[] = 'Jonet';
+$aNames[] = 'Jonet';
+$aNames[] = 'Jongers';
+$aNames[] = 'Jonné';
+$aNames[] = 'Jonnet';
+$aNames[] = 'Jordaens';
+$aNames[] = 'Jorez';
+$aNames[] = 'Joris';
+$aNames[] = 'Jorissen';
+$aNames[] = 'Jozef';
+$aNames[] = 'Julianus';
+$aNames[] = 'Julius';
+$aNames[] = 'Jurgen';
+$aNames[] = 'Kaalman';
+$aNames[] = 'Kaisin';
+$aNames[] = 'Keetels';
+$aNames[] = 'Kenens';
+$aNames[] = 'Kenes';
+$aNames[] = 'Kenis';
+$aNames[] = 'Kennens';
+$aNames[] = 'Kennes';
+$aNames[] = 'Kennis';
+$aNames[] = 'Kesteloot';
+$aNames[] = 'Ketel';
+$aNames[] = 'Ketelsmit';
+$aNames[] = 'Kiecken';
+$aNames[] = 'Kimpe';
+$aNames[] = 'Kinnen';
+$aNames[] = 'Klein';
+$aNames[] = 'Kleineman';
+$aNames[] = 'Kleiner';
+$aNames[] = 'Kleinerman';
+$aNames[] = 'Kleinman';
+$aNames[] = 'Klerk';
+$aNames[] = 'Kleynen';
+$aNames[] = 'Klingeleers';
+$aNames[] = 'Kobus';
+$aNames[] = 'Koeck';
+$aNames[] = 'Konninckx';
+$aNames[] = 'Koolman';
+$aNames[] = 'Korring';
+$aNames[] = 'Kramers';
+$aNames[] = 'Kreemers';
+$aNames[] = 'Kuipers';
+$aNames[] = 'Labbez';
+$aNames[] = 'Lacroix';
+$aNames[] = 'Laenen';
+$aNames[] = 'Laenens';
+$aNames[] = 'Lafontaine';
+$aNames[] = 'Lambert';
+$aNames[] = 'Lambrechts';
+$aNames[] = 'Lanen';
+$aNames[] = 'Lanens';
+$aNames[] = 'Langlez';
+$aNames[] = 'Lapayre';
+$aNames[] = 'Laseur';
+$aNames[] = 'Laseure';
+$aNames[] = 'Lauffer';
+$aNames[] = 'Laurent';
+$aNames[] = 'Lauwers';
+$aNames[] = 'Le Demunck';
+$aNames[] = 'Leboutte';
+$aNames[] = 'Lebrun';
+$aNames[] = 'Leclerc';
+$aNames[] = 'Leclercq';
+$aNames[] = 'Lecocq';
+$aNames[] = 'Lecomte';
+$aNames[] = 'Ledecq';
+$aNames[] = 'Leenhard';
+$aNames[] = 'Leenhart';
+$aNames[] = 'Lefebvre';
+$aNames[] = 'Lefèvre';
+$aNames[] = 'Legrand';
+$aNames[] = 'Lejeune';
+$aNames[] = 'Lemaire';
+$aNames[] = 'Lemmens';
+$aNames[] = 'Lemonnier';
+$aNames[] = 'Lemounie';
+$aNames[] = 'Lenaerts';
+$aNames[] = 'Lénel';
+$aNames[] = 'Lénelle';
+$aNames[] = 'Lennel';
+$aNames[] = 'Léonard';
+$aNames[] = 'Lepoutre';
+$aNames[] = 'Leprette';
+$aNames[] = 'Lepropre';
+$aNames[] = 'Leroy';
+$aNames[] = 'Lescohy';
+$aNames[] = 'Lesoil';
+$aNames[] = 'Lesoile';
+$aNames[] = 'Lesoille';
+$aNames[] = 'Levecq';
+$aNames[] = 'Lewek';
+$aNames[] = 'Libert';
+$aNames[] = 'Liens';
+$aNames[] = 'Liephoudt';
+$aNames[] = 'Liepot';
+$aNames[] = 'Liepout';
+$aNames[] = 'Lieseborghs';
+$aNames[] = 'Liesenborghs';
+$aNames[] = 'Lietaer';
+$aNames[] = 'Lietaert';
+$aNames[] = 'Lietar';
+$aNames[] = 'Liétar';
+$aNames[] = 'Liétard';
+$aNames[] = 'Liétart';
+$aNames[] = 'Lievens';
+$aNames[] = 'Lievesoons';
+$aNames[] = 'Lievevrouw';
+$aNames[] = 'Lievrouw';
+$aNames[] = 'Liévrouw';
+$aNames[] = 'Lievrow';
+$aNames[] = 'Linglay';
+$aNames[] = 'Linglet';
+$aNames[] = 'Liphout';
+$aNames[] = 'Lisenborgh';
+$aNames[] = 'Lisenborgs';
+$aNames[] = 'Locreille';
+$aNames[] = 'Locrel';
+$aNames[] = 'Locrelle';
+$aNames[] = 'Lode';
+$aNames[] = 'Loo';
+$aNames[] = 'Lorfèvre';
+$aNames[] = 'Lorphêvre';
+$aNames[] = 'Losseau';
+$aNames[] = 'Losset';
+$aNames[] = 'Louis';
+$aNames[] = 'Louzeau';
+$aNames[] = 'Lowie';
+$aNames[] = 'Ludovicus';
+$aNames[] = 'Lugen';
+$aNames[] = 'Lugens';
+$aNames[] = 'Lust';
+$aNames[] = 'Lustig';
+$aNames[] = 'Luyer';
+$aNames[] = 'Luyrik';
+$aNames[] = 'Luyten';
+$aNames[] = 'Lyphoudt';
+$aNames[] = 'Lyphout';
+$aNames[] = 'Maca';
+$aNames[] = 'Maertens';
+$aNames[] = 'Maes';
+$aNames[] = 'Maessen';
+$aNames[] = 'Mahieu';
+$aNames[] = 'Maka';
+$aNames[] = 'Malchamp';
+$aNames[] = 'Malchamps';
+$aNames[] = 'Malmedier';
+$aNames[] = 'Malmedy';
+$aNames[] = 'Malmendier';
+$aNames[] = 'Mangon';
+$aNames[] = 'Maqua';
+$aNames[] = 'Marchal';
+$aNames[] = 'Marckx';
+$aNames[] = 'Marcus';
+$aNames[] = 'Mardaga';
+$aNames[] = 'Maréchal';
+$aNames[] = 'Maria';
+$aNames[] = 'Mark';
+$aNames[] = 'Markgraff';
+$aNames[] = 'Martens';
+$aNames[] = 'Martin';
+$aNames[] = 'Martins';
+$aNames[] = 'Massart';
+$aNames[] = 'Masson';
+$aNames[] = 'Mathieu';
+$aNames[] = 'Mathissen';
+$aNames[] = 'Mathy';
+$aNames[] = 'Matthys';
+$aNames[] = 'Mauchamp';
+$aNames[] = 'Mauchamps';
+$aNames[] = 'Maurichon';
+$aNames[] = 'Maurissen';
+$aNames[] = 'Maurits';
+$aNames[] = 'Mayeur';
+$aNames[] = 'Mayeux';
+$aNames[] = 'Mechelaere';
+$aNames[] = 'Meert';
+$aNames[] = 'Meertens';
+$aNames[] = 'Meester';
+$aNames[] = 'Meeus';
+$aNames[] = 'Melaerts';
+$aNames[] = 'Mellaerts';
+$aNames[] = 'Merchié';
+$aNames[] = 'Merchier';
+$aNames[] = 'Mergeai';
+$aNames[] = 'Mergeay';
+$aNames[] = 'Merjai';
+$aNames[] = 'Merjay';
+$aNames[] = 'Mertens';
+$aNames[] = 'Mertes';
+$aNames[] = 'Merts';
+$aNames[] = 'Mertz';
+$aNames[] = 'Meulemans';
+$aNames[] = 'Meulemeesters';
+$aNames[] = 'Meunier';
+$aNames[] = 'Meurice';
+$aNames[] = 'Mewis';
+$aNames[] = 'Mewissen';
+$aNames[] = 'Michaël';
+$aNames[] = 'Michaux';
+$aNames[] = 'Michel';
+$aNames[] = 'Michiels';
+$aNames[] = 'Mixhel';
+$aNames[] = 'Mochamps';
+$aNames[] = 'Moens';
+$aNames[] = 'Moeyaert';
+$aNames[] = 'Moiling';
+$aNames[] = 'Moinil';
+$aNames[] = 'Molemans';
+$aNames[] = 'Molenaers';
+$aNames[] = 'Monceau';
+$aNames[] = 'Moncia';
+$aNames[] = 'Monciaux';
+$aNames[] = 'Monsay';
+$aNames[] = 'Monteyne';
+$aNames[] = 'Moreau';
+$aNames[] = 'Mouyart';
+$aNames[] = 'Moyaert';
+$aNames[] = 'Mullenders';
+$aNames[] = 'Munck';
+$aNames[] = 'Muynck';
+$aNames[] = 'Nachtegael';
+$aNames[] = 'Nagelmaekers';
+$aNames[] = 'Nagels';
+$aNames[] = 'Natus';
+$aNames[] = 'Neel';
+$aNames[] = 'Neels';
+$aNames[] = 'Neuray';
+$aNames[] = 'Neureau';
+$aNames[] = 'Neuret';
+$aNames[] = 'Neurot';
+$aNames[] = 'Neuts';
+$aNames[] = 'Neuven';
+$aNames[] = 'Neven';
+$aNames[] = 'Nguyen';
+$aNames[] = 'Nicolas';
+$aNames[] = 'Nicolaus';
+$aNames[] = 'Nicolus';
+$aNames[] = 'Nijs';
+$aNames[] = 'Niklaas';
+$aNames[] = 'Noël';
+$aNames[] = 'Nuts';
+$aNames[] = 'Nuttin';
+$aNames[] = 'Ochin';
+$aNames[] = 'Olivier';
+$aNames[] = 'Olyff';
+$aNames[] = 'Paindavaine';
+$aNames[] = 'Pannaye';
+$aNames[] = 'Parmentier';
+$aNames[] = 'Pas';
+$aNames[] = 'Pauss';
+$aNames[] = 'Pauwels';
+$aNames[] = 'Peeters';
+$aNames[] = 'Pelser';
+$aNames[] = 'Pelsmaeker';
+$aNames[] = 'Peschon';
+$aNames[] = 'Peschoniez';
+$aNames[] = 'Pester';
+$aNames[] = 'Petersen';
+$aNames[] = 'Petit';
+$aNames[] = 'Pierre';
+$aNames[] = 'Piet';
+$aNames[] = 'Pieters';
+$aNames[] = 'Pietersen';
+$aNames[] = 'Piette';
+$aNames[] = 'Pirard';
+$aNames[] = 'Piron';
+$aNames[] = 'Pirotte';
+$aNames[] = 'Plaats';
+$aNames[] = 'Poels';
+$aNames[] = 'Poelsmans';
+$aNames[] = 'Poncelet';
+$aNames[] = 'Pools';
+$aNames[] = 'Posson';
+$aNames[] = 'Potstainer';
+$aNames[] = 'Potter';
+$aNames[] = 'Pottiaux';
+$aNames[] = 'Pottié';
+$aNames[] = 'Potty';
+$aNames[] = 'Poyon';
+$aNames[] = 'Praat';
+$aNames[] = 'Premereur';
+$aNames[] = 'Premmereur';
+$aNames[] = 'Prevostel';
+$aNames[] = 'Priesse';
+$aNames[] = 'Prisse';
+$aNames[] = 'Proost';
+$aNames[] = 'Prost';
+$aNames[] = 'Proust';
+$aNames[] = 'Putman';
+$aNames[] = 'Putmans';
+$aNames[] = 'Putmans';
+$aNames[] = 'Puttemans';
+$aNames[] = 'Puttemans';
+$aNames[] = 'Quaisin';
+$aNames[] = 'Quesnay';
+$aNames[] = 'Quesne';
+$aNames[] = 'Quesneau';
+$aNames[] = 'Quesnel';
+$aNames[] = 'Quesney';
+$aNames[] = 'Quesnoy';
+$aNames[] = 'Queval';
+$aNames[] = 'Raes';
+$aNames[] = 'Ramael';
+$aNames[] = 'Raucent';
+$aNames[] = 'Rauscent';
+$aNames[] = 'Rausin';
+$aNames[] = 'Raussain';
+$aNames[] = 'Raussent';
+$aNames[] = 'Raussin';
+$aNames[] = 'Raveydts';
+$aNames[] = 'Ravignat';
+$aNames[] = 'Remy';
+$aNames[] = 'Renard';
+$aNames[] = 'Retelet';
+$aNames[] = 'Ricaart';
+$aNames[] = 'Ricaert';
+$aNames[] = 'Ricard';
+$aNames[] = 'Robaert';
+$aNames[] = 'Robbert';
+$aNames[] = 'Robert';
+$aNames[] = 'Roels';
+$aNames[] = 'Roland';
+$aNames[] = 'Rooseels';
+$aNames[] = 'Roosengardt';
+$aNames[] = 'Rosseel';
+$aNames[] = 'Rousseau';
+$aNames[] = 'Saintmaux';
+$aNames[] = 'Saint-Maux';
+$aNames[] = 'Sanctorum';
+$aNames[] = 'Santilman';
+$aNames[] = 'Schmitz';
+$aNames[] = 'Schnock';
+$aNames[] = 'Schoenmakers';
+$aNames[] = 'Schoenman';
+$aNames[] = 'Schoone';
+$aNames[] = 'Scorier';
+$aNames[] = 'Scuvie';
+$aNames[] = 'Scuvie';
+$aNames[] = 'Segers';
+$aNames[] = 'Seghers';
+$aNames[] = 'Seppen';
+$aNames[] = 'Servais';
+$aNames[] = 'Shoen';
+$aNames[] = 'Sijmen';
+$aNames[] = 'Simoens';
+$aNames[] = 'Simon';
+$aNames[] = 'Simons';
+$aNames[] = 'Sinnesaël';
+$aNames[] = 'Sinnesal';
+$aNames[] = 'Slagmolder';
+$aNames[] = 'Slagmulder';
+$aNames[] = 'Slamulder';
+$aNames[] = 'Smal';
+$aNames[] = 'Smeets';
+$aNames[] = 'Smet';
+$aNames[] = 'Smets';
+$aNames[] = 'Smit';
+$aNames[] = 'Smolders';
+$aNames[] = 'Smulders';
+$aNames[] = 'Somers';
+$aNames[] = 'Sottiaux';
+$aNames[] = 'Spinette';
+$aNames[] = 'Sprecher';
+$aNames[] = 'Stas';
+$aNames[] = 'Stass';
+$aNames[] = 'Stassaert';
+$aNames[] = 'Stassar';
+$aNames[] = 'Stassard';
+$aNames[] = 'Stassart';
+$aNames[] = 'Stasse';
+$aNames[] = 'Stassiaux';
+$aNames[] = 'Stassin';
+$aNames[] = 'Stassinet';
+$aNames[] = 'Statius';
+$aNames[] = 'Steculorum';
+$aNames[] = 'Stefaans';
+$aNames[] = 'Stercken';
+$aNames[] = 'Sterckmans';
+$aNames[] = 'Sterckx';
+$aNames[] = 'Stevens';
+$aNames[] = 'Stier';
+$aNames[] = 'Stiers';
+$aNames[] = 'Stievens';
+$aNames[] = 'Stine';
+$aNames[] = 'Stoffel';
+$aNames[] = 'Stordair';
+$aNames[] = 'Stordeur';
+$aNames[] = 'Stoutmans';
+$aNames[] = 'Swart';
+$aNames[] = 'Swarte';
+$aNames[] = 'Tack';
+$aNames[] = 'Taverner';
+$aNames[] = 'Teissant';
+$aNames[] = 'Terreur';
+$aNames[] = 'Thijs';
+$aNames[] = 'Thiry';
+$aNames[] = 'Thissen';
+$aNames[] = 'Thomas';
+$aNames[] = 'Thonnisen';
+$aNames[] = 'Thuiliau';
+$aNames[] = 'Thuiliaux';
+$aNames[] = 'Thuiliet';
+$aNames[] = 'Thys';
+$aNames[] = 'Tibaut';
+$aNames[] = 'Timmerman';
+$aNames[] = 'Timmermans';
+$aNames[] = 'Tjampens';
+$aNames[] = "T'Jampens";
+$aNames[] = 'Toussaint';
+$aNames[] = 'Trausch';
+$aNames[] = 'Tuiliau';
+$aNames[] = 'Tuiliaux';
+$aNames[] = 'Tuilliet';
+$aNames[] = 'Tuin';
+$aNames[] = 'Tumson';
+$aNames[] = 'Tweelinckx';
+$aNames[] = 'Urbain';
+$aNames[] = 'Urting';
+$aNames[] = 'Vanbattel';
+$aNames[] = 'Vanbergh';
+$aNames[] = 'Vandamme';
+$aNames[] = 'Vandenberghe';
+$aNames[] = 'Vandenbossche';
+$aNames[] = 'Vandenbussche';
+$aNames[] = 'Vandendorpe';
+$aNames[] = 'Vandeputte';
+$aNames[] = 'Vanderhorst';
+$aNames[] = 'Vanderlinden';
+$aNames[] = 'Vanderplaetsen';
+$aNames[] = 'Vandevelde';
+$aNames[] = 'Vandoolaeghe';
+$aNames[] = 'Vandorpe';
+$aNames[] = 'Vanlierde';
+$aNames[] = 'Vanpé';
+$aNames[] = 'Vanpede';
+$aNames[] = 'Vanpée';
+$aNames[] = 'Vansteertegem';
+$aNames[] = 'Vecq';
+$aNames[] = 'Veld';
+$aNames[] = 'Veldmann';
+$aNames[] = 'Vellemans';
+$aNames[] = 'Veraghe';
+$aNames[] = 'Veraghen';
+$aNames[] = 'Verbeeck';
+$aNames[] = 'Verbeke';
+$aNames[] = 'Verbruggen';
+$aNames[] = 'Vercammen';
+$aNames[] = 'Vercheval';
+$aNames[] = 'Verdoolaeg(H)E';
+$aNames[] = 'Verhaege';
+$aNames[] = 'Verhaegen';
+$aNames[] = 'Verhaeghe';
+$aNames[] = 'Verhaeghen';
+$aNames[] = 'Verhaegue';
+$aNames[] = 'Verhage';
+$aNames[] = 'Verhagen';
+$aNames[] = 'Verhaghe';
+$aNames[] = 'Verhelst';
+$aNames[] = 'Verheyen';
+$aNames[] = 'Verhoeven';
+$aNames[] = 'Verlinden';
+$aNames[] = 'Vermeer';
+$aNames[] = 'Vermeersch';
+$aNames[] = 'Vermeiren';
+$aNames[] = 'Vermeren';
+$aNames[] = 'Vermeulen';
+$aNames[] = 'Vermotte';
+$aNames[] = 'Verplaetse';
+$aNames[] = 'Verplancke';
+$aNames[] = 'Verplancken';
+$aNames[] = 'Verschueren';
+$aNames[] = 'Verslijke';
+$aNames[] = 'Verslycke';
+$aNames[] = 'Verstraete';
+$aNames[] = 'Verstraeten';
+$aNames[] = 'Vervoort';
+$aNames[] = 'Vet';
+$aNames[] = 'Vette';
+$aNames[] = 'Viatour';
+$aNames[] = 'Vieutems';
+$aNames[] = 'Vieuxtemps';
+$aNames[] = 'Vilain';
+$aNames[] = 'Vincent';
+$aNames[] = 'Vinchent';
+$aNames[] = 'Visje';
+$aNames[] = 'Vlaamsche';
+$aNames[] = 'Vlaeminck';
+$aNames[] = 'Vlaemynck';
+$aNames[] = 'Vlaminck';
+$aNames[] = 'Vlamynck';
+$aNames[] = 'Vlemincks';
+$aNames[] = 'Vleminckx';
+$aNames[] = 'Vleminx';
+$aNames[] = 'Vlemynckx';
+$aNames[] = 'Vogels';
+$aNames[] = 'Volckaert';
+$aNames[] = 'Volkaert';
+$aNames[] = 'Volkaerts';
+$aNames[] = 'Volkart';
+$aNames[] = 'Volkert';
+$aNames[] = 'Voller';
+$aNames[] = 'Vos';
+$aNames[] = 'Vossen';
+$aNames[] = 'Vrank';
+$aNames[] = 'Vrindt';
+$aNames[] = 'Vrolijt';
+$aNames[] = 'Vrolyck';
+$aNames[] = 'Vullers';
+$aNames[] = 'Wagemans';
+$aNames[] = 'Wagenmann';
+$aNames[] = 'Waghon';
+$aNames[] = 'Wagon';
+$aNames[] = 'Walle';
+$aNames[] = 'Wastiaux';
+$aNames[] = 'Watrigant';
+$aNames[] = 'Watriquant';
+$aNames[] = 'Watteau';
+$aNames[] = 'Watteau';
+$aNames[] = 'Watteaux';
+$aNames[] = 'Watteaux';
+$aNames[] = 'Wattecamp';
+$aNames[] = 'Wattecamps';
+$aNames[] = 'Wattecant';
+$aNames[] = 'Watteel';
+$aNames[] = 'Wattel';
+$aNames[] = 'Wattelle';
+$aNames[] = 'Wattiau';
+$aNames[] = 'Wattiaux';
+$aNames[] = 'Wattieaux';
+$aNames[] = 'Wauters';
+$aNames[] = 'Weers';
+$aNames[] = 'Weerts';
+$aNames[] = 'Wek';
+$aNames[] = 'Wevers';
+$aNames[] = 'Weynen';
+$aNames[] = 'Wilbaert';
+$aNames[] = 'Wilfart';
+$aNames[] = 'Willems';
+$aNames[] = 'Willock';
+$aNames[] = 'Willocq';
+$aNames[] = 'Wilock';
+$aNames[] = 'Wintgens';
+$aNames[] = 'Wouter';
+$aNames[] = 'Wouters';
+$aNames[] = 'Wuyts';
+$aNames[] = 'Wylock';
+$aNames[] = 'Wylocke';
+$aNames[] = 'Yildirim';
+$aNames[] = 'Yilmaz';
+$aNames[] = 'Zadelaar';
+$aNames[] = 'Zegers';
+$aNames[] = 'Zeggers';
+$aNames[] = 'Zègres';
+$aNames[] = 'Bernard';
+$aNames[] = 'Bertrand';
+$aNames[] = 'Blanc';
+$aNames[] = 'Bonnet';
+$aNames[] = 'David';
+$aNames[] = 'Dubois';
+$aNames[] = 'Durand';
+$aNames[] = 'Fournier';
+$aNames[] = 'Garcia';
+$aNames[] = 'Girard';
+$aNames[] = 'Lambert';
+$aNames[] = 'Laurent';
+$aNames[] = 'Lefebvre';
+$aNames[] = 'Leroy';
+$aNames[] = 'Martin';
+$aNames[] = 'Michel';
+$aNames[] = 'Moreau';
+$aNames[] = 'Morel';
+$aNames[] = 'Petit';
+$aNames[] = 'Rang';
+$aNames[] = 'Richard';
+$aNames[] = 'Robert';
+$aNames[] = 'Rousseau';
+$aNames[] = 'Roux';
+$aNames[] = 'Simon';
+$aNames[] = 'Thomas';
+$aNames[] = 'André';
+$aNames[] = 'Chevalier';
+$aNames[] = 'Clément';
+$aNames[] = 'Dupont';
+$aNames[] = 'Faure';
+$aNames[] = 'François';
+$aNames[] = 'Garnier';
+$aNames[] = 'Gauthier';
+$aNames[] = 'Gautier';
+$aNames[] = 'Guerin';
+$aNames[] = 'Henry';
+$aNames[] = 'Lefèvre';
+$aNames[] = 'Legrand';
+$aNames[] = 'Lopez';
+$aNames[] = 'Martinez';
+$aNames[] = 'Masson';
+$aNames[] = 'Mathieu';
+$aNames[] = 'Mercier';
+$aNames[] = 'Morin';
+$aNames[] = 'Muller';
+$aNames[] = 'Nicolas';
+$aNames[] = 'Perrin';
+$aNames[] = 'Rang';
+$aNames[] = 'Robin';
+$aNames[] = 'Roussel';
+$aNames[] = 'Vincent';
+$aNames[] = 'Arnaud';
+$aNames[] = 'Blanchand';
+$aNames[] = 'Boyer';
+$aNames[] = 'Brun';
+$aNames[] = 'Brunet';
+$aNames[] = 'Denis';
+$aNames[] = 'Dufour';
+$aNames[] = 'Dumont';
+$aNames[] = 'Duval';
+$aNames[] = 'Fabre';
+$aNames[] = 'Fontaine';
+$aNames[] = 'Gaillard';
+$aNames[] = 'Giraud';
+$aNames[] = 'Joly';
+$aNames[] = 'Lemaire';
+$aNames[] = 'Lucas';
+$aNames[] = 'Marchand';
+$aNames[] = 'Meunier';
+$aNames[] = 'Meyer';
+$aNames[] = 'Noël';
+$aNames[] = 'Perez';
+$aNames[] = 'Rang';
+$aNames[] = 'Roche';
+$aNames[] = 'Roy';
+$aNames[] = 'Sanchez';
+$aNames[] = 'Vidal';
+$aNames[] = 'Adamcki';
+$aNames[] = 'Adamczak';
+$aNames[] = 'Adamczewski';
+$aNames[] = 'Adamczyk';
+$aNames[] = 'Adamiak';
+$aNames[] = 'Adamowicz';
+$aNames[] = 'Adamowitz';
+$aNames[] = 'Adamski';
+$aNames[] = 'Adamsky';
+$aNames[] = 'Adamus';
+$aNames[] = 'Ambroziak';
+$aNames[] = 'Ambroziewicz';
+$aNames[] = 'Ambrozy';
+$aNames[] = 'Andrzeg';
+$aNames[] = 'Andrzejak';
+$aNames[] = 'Antczak';
+$aNames[] = 'Antkowiak';
+$aNames[] = 'Augustyniak';
+$aNames[] = 'Balcerzak';
+$aNames[] = 'Balcrowiak';
+$aNames[] = 'Balczarek';
+$aNames[] = 'Banaszak';
+$aNames[] = 'Baranowski';
+$aNames[] = 'Bejm';
+$aNames[] = 'Biczysko';
+$aNames[] = 'Biernaciak';
+$aNames[] = 'Biernacki';
+$aNames[] = 'Biernaczyk';
+$aNames[] = 'Biernat';
+$aNames[] = 'Blaszak';
+$aNames[] = 'Blaszczak';
+$aNames[] = 'Blaszczyk';
+$aNames[] = 'Bloch';
+$aNames[] = 'Bloszak';
+$aNames[] = 'Boguslawiak';
+$aNames[] = 'Bor';
+$aNames[] = 'Brama';
+$aNames[] = 'Bressler';
+$aNames[] = 'Brezisky';
+$aNames[] = 'Brzezinski';
+$aNames[] = 'Buczak';
+$aNames[] = 'Buczek';
+$aNames[] = 'Buczko';
+$aNames[] = 'Buczkowski';
+$aNames[] = 'Buczynski';
+$aNames[] = 'Bugajny';
+$aNames[] = 'Bukowski';
+$aNames[] = 'Bulinski';
+$aNames[] = 'Bulinsky';
+$aNames[] = 'Cepak';
+$aNames[] = 'Cepek';
+$aNames[] = 'Ceremuga';
+$aNames[] = 'Cérémuga';
+$aNames[] = 'Cernota';
+$aNames[] = 'Charbul';
+$aNames[] = 'Chlebowski';
+$aNames[] = 'Chmiel';
+$aNames[] = 'Chodkowski';
+$aNames[] = 'Ciemior';
+$aNames[] = 'Ciepluch';
+$aNames[] = 'Cieplucha';
+$aNames[] = 'Ciesielski';
+$aNames[] = 'Cieslak';
+$aNames[] = 'Cieslik';
+$aNames[] = 'Cyganek';
+$aNames[] = 'Cyganik';
+$aNames[] = 'Cygankiewicz';
+$aNames[] = 'Czajka';
+$aNames[] = 'Czajkowski';
+$aNames[] = 'Czapka';
+$aNames[] = 'Czapski';
+$aNames[] = 'Czeremcha';
+$aNames[] = 'Czeremuga';
+$aNames[] = 'Dabbor';
+$aNames[] = 'Dabik';
+$aNames[] = 'Dabrowski';
+$aNames[] = 'Dambek';
+$aNames[] = 'Dambik';
+$aNames[] = 'Damian';
+$aNames[] = 'Danielak';
+$aNames[] = 'Danielczak';
+$aNames[] = 'Danilevitch';
+$aNames[] = 'Danilewicz';
+$aNames[] = 'Dawidowicz';
+$aNames[] = 'Deka';
+$aNames[] = 'Dembski';
+$aNames[] = 'Dembsky';
+$aNames[] = 'Dobosz';
+$aNames[] = 'Dobosz';
+$aNames[] = 'Dobrowolski';
+$aNames[] = 'Domin';
+$aNames[] = 'Dominiak';
+$aNames[] = 'Dominiczak';
+$aNames[] = 'Dominik';
+$aNames[] = 'Dominikowski';
+$aNames[] = 'Doroszewski';
+$aNames[] = 'Dowbor';
+$aNames[] = 'Dudek';
+$aNames[] = 'Erazmus';
+$aNames[] = 'Feliks';
+$aNames[] = 'Fialek';
+$aNames[] = 'Fijal';
+$aNames[] = 'Fijalek';
+$aNames[] = 'Fijalkowski';
+$aNames[] = 'Filipczak';
+$aNames[] = 'Filipczuk';
+$aNames[] = 'Filipek';
+$aNames[] = 'Filipiak';
+$aNames[] = 'Filipiuk';
+$aNames[] = 'Filipkowski';
+$aNames[] = 'Filipowicz';
+$aNames[] = 'Filipowski';
+$aNames[] = 'Filipski';
+$aNames[] = 'Firlej';
+$aNames[] = 'Florack';
+$aNames[] = 'Florczak';
+$aNames[] = 'Fracczak';
+$aNames[] = 'Frackowiak';
+$aNames[] = 'Franciscek';
+$aNames[] = 'Fratczak';
+$aNames[] = 'Frydryszak';
+$aNames[] = 'Gabryelczyk';
+$aNames[] = 'Gabrysiak';
+$aNames[] = 'Gaik';
+$aNames[] = 'Galinski';
+$aNames[] = 'Galka';
+$aNames[] = 'Gasztowtt';
+$aNames[] = 'Glebocki';
+$aNames[] = 'Glinczanki';
+$aNames[] = 'Glowacki';
+$aNames[] = 'Gniewek';
+$aNames[] = 'Godes';
+$aNames[] = 'Godès';
+$aNames[] = 'Godesh';
+$aNames[] = 'Gomolka';
+$aNames[] = 'Gongal';
+$aNames[] = 'Goszczynski';
+$aNames[] = 'Grabara';
+$aNames[] = 'Grabarczyk';
+$aNames[] = 'Grabarz';
+$aNames[] = 'Grabowski';
+$aNames[] = 'Grajoszek';
+$aNames[] = 'Gregorz';
+$aNames[] = 'Gryczkowiak';
+$aNames[] = 'Grzegorczyk';
+$aNames[] = 'Grzeskowiak';
+$aNames[] = 'Grzybek';
+$aNames[] = 'Grzybowski';
+$aNames[] = 'Habiera';
+$aNames[] = 'Halborn';
+$aNames[] = 'Harbul';
+$aNames[] = 'Heilpern';
+$aNames[] = 'Heilporn';
+$aNames[] = 'Heleniak';
+$aNames[] = 'Heltowni';
+$aNames[] = 'Hercberg';
+$aNames[] = 'Ickowicz';
+$aNames[] = 'Idaszek';
+$aNames[] = 'Idkowiak';
+$aNames[] = 'Iglicki';
+$aNames[] = 'Ignasiak';
+$aNames[] = 'Ignatczak';
+$aNames[] = 'Iszezuk';
+$aNames[] = 'Izydorczyk';
+$aNames[] = 'Jablonowski';
+$aNames[] = 'Jachowicz';
+$aNames[] = 'Jackowiak';
+$aNames[] = 'Jackowska';
+$aNames[] = 'Jackowski';
+$aNames[] = 'Jan';
+$aNames[] = 'Janas';
+$aNames[] = 'Janiak';
+$aNames[] = 'Janicki';
+$aNames[] = 'Janik';
+$aNames[] = 'Jankowiak';
+$aNames[] = 'Jankowski';
+$aNames[] = 'Janow';
+$aNames[] = 'Janowczyk';
+$aNames[] = 'Janowiak';
+$aNames[] = 'Janowicz';
+$aNames[] = 'Janowiec';
+$aNames[] = 'Janowski';
+$aNames[] = 'Janusz';
+$aNames[] = 'Jarosz';
+$aNames[] = 'Jaroszek';
+$aNames[] = 'Jaroszewicz';
+$aNames[] = 'Jaroszewski';
+$aNames[] = 'Jaroszuk';
+$aNames[] = 'Jaroszynski';
+$aNames[] = 'Jasicki';
+$aNames[] = 'Jasinski';
+$aNames[] = 'Jaskowiak';
+$aNames[] = 'Jedlenska';
+$aNames[] = 'Jedruch';
+$aNames[] = 'Jedrych';
+$aNames[] = 'Jedrzej';
+$aNames[] = 'Jedrzejak';
+$aNames[] = 'Jedrzejczak';
+$aNames[] = 'Jedrzejczy';
+$aNames[] = 'Jedrzejczyk';
+$aNames[] = 'Jedrzejewski';
+$aNames[] = 'Jelenski';
+$aNames[] = 'Jerzmanowski';
+$aNames[] = 'Jezierski';
+$aNames[] = 'Jodiowski';
+$aNames[] = 'Joskowiak';
+$aNames[] = 'Jozefiak';
+$aNames[] = 'Jozwiak';
+$aNames[] = 'Julia';
+$aNames[] = 'Juliusz';
+$aNames[] = 'Jura';
+$aNames[] = 'Jurak';
+$aNames[] = 'Juras';
+$aNames[] = 'Jurasz';
+$aNames[] = 'Juraszek';
+$aNames[] = 'Jurczak';
+$aNames[] = 'Jurczuk';
+$aNames[] = 'Jurczyk';
+$aNames[] = 'Jurczynski';
+$aNames[] = 'Jurek';
+$aNames[] = 'Martin';
+$aNames[] = 'Durand';
+$aNames[] = 'Robert';
+$aNames[] = 'Dupont';
+$aNames[] = 'Bernard';
+$aNames[] = 'Dubois';
+$aNames[] = 'Petit';
+$aNames[] = 'Cohen';
+$aNames[] = 'Richard';
+$aNames[] = 'Moreau';
+$aNames[] = 'Thomas';
+$aNames[] = 'Nguyen';
+$aNames[] = 'Simon';
+$aNames[] = 'Laurent';
+$aNames[] = 'Michel';
+$aNames[] = 'Levy';
+$aNames[] = 'Vincent';
+$aNames[] = 'David';
+$aNames[] = 'Bertrand';
+$aNames[] = 'Rousseau';
+$aNames[] = 'Leroy';
+$aNames[] = 'Girard';
+$aNames[] = 'Fournier';
+$aNames[] = 'Roux';
+$aNames[] = 'Andre';
+$aNames[] = 'Garnier';
+$aNames[] = 'Mercier';
+$aNames[] = 'Morin';
+$aNames[] = 'Jean';
+$aNames[] = 'Denis';
+$aNames[] = 'Henry';
+$aNames[] = 'Duval';
+$aNames[] = 'Morel';
+$aNames[] = 'Nicolas';
+$aNames[] = 'Francois';
+$aNames[] = 'Blanc';
+$aNames[] = 'Faure';
+$aNames[] = 'Pierre';
+$aNames[] = 'Garcia';
+$aNames[] = 'Lambert';
+$aNames[] = 'Gautier';
+$aNames[] = 'Lefebvre';
+$aNames[] = 'Meunier';
+$aNames[] = 'Lefevre';
+$aNames[] = 'Perrin';
+$aNames[] = 'Legrand';
+$aNames[] = 'Clement';
+$aNames[] = 'Mathieu';
+$aNames[] = 'Bonnet';
+$aNames[] = 'Chevalier';
+$aNames[] = 'Muller';
+$aNames[] = 'Fontaine';
+$aNames[] = 'Robin';
+$aNames[] = 'Blanchard';
+$aNames[] = 'Perez';
+$aNames[] = 'Guerin';
+$aNames[] = 'Masson';
+$aNames[] = 'Roger';
+$aNames[] = 'Gauthier';
+$aNames[] = 'Dupuis';
+$aNames[] = 'Martinez';
+$aNames[] = 'Leroux';
+$aNames[] = 'Bourgeois';
+$aNames[] = 'Dumont';
+$aNames[] = 'Dupond';
+$aNames[] = 'Paul';
+$aNames[] = 'Louis';
+$aNames[] = 'Dufour';
+$aNames[] = 'Meyer';
+$aNames[] = 'Lacroix';
+$aNames[] = 'Noel';
+$aNames[] = 'Fabre';
+$aNames[] = 'Lopez';
+$aNames[] = 'Gerard';
+$aNames[] = 'Lemaire';
+$aNames[] = 'Roussel';
+$aNames[] = 'Berger';
+$aNames[] = 'Barbier';
+$aNames[] = 'Boyer';
+$aNames[] = 'Brun';
+$aNames[] = 'Vidal';
+$aNames[] = 'Giraud';
+$aNames[] = 'Fernandez';
+$aNames[] = 'Marie';
+$aNames[] = 'Roche';
+$aNames[] = 'Colin';
+$aNames[] = 'Tran';
+$aNames[] = 'Marchand';
+$aNames[] = 'Lemoine';
+$aNames[] = 'Charpentier';
+
+$aCountries = array();
+$aCountries[] = 'France';
+$aCountries[] = 'United Kingdom';
+$aCountries[] = 'Germany';
+$aCountries[] = 'United States of America';
+$aCountries[] = 'Canada';
+$aCountries[] = 'Switzerland';
+$aCountries[] = 'Belgium';
+$aCountries[] = 'Poland';
+$aCountries[] = 'Slovakia';
+$aCountries[] = 'Italy';
+$aCountries[] = 'Spain';
+$aCountries[] = 'Bulgaria';
+
+$aCities = array();
+$aCities[]="Orvault";
+$aCities[]="Oullins";
+$aCities[]="Oyonnax";
+$aCities[]="Ozoir-la-Ferrière";
+$aCities[]="Palaiseau";
+$aCities[]="Pantin";
+$aCities[]="Paris";
+$aCities[]="Pau";
+$aCities[]="Périgueux";
+$aCities[]="Perpignan";
+$aCities[]="Le Perreux-sur-Marne";
+$aCities[]="Pessac";
+$aCities[]="Le Petit-Quevilly";
+$aCities[]="Pierrefitte-sur-Seine";
+$aCities[]="Plaisir";
+$aCities[]="Le Plessis-Robinson";
+$aCities[]="Poissy";
+$aCities[]="Poitiers";
+$aCities[]="Pontault-Combault";
+$aCities[]="Pontoise";
+$aCities[]="Le Port";
+$aCities[]="La Possession";
+$aCities[]="Puteaux";
+$aCities[]="Le Puy-en-Velay";
+$aCities[]="Quimper";
+$aCities[]="Rambouillet";
+$aCities[]="Reims";
+$aCities[]="Rennes";
+$aCities[]="Rezé";
+$aCities[]="Rillieux-la-Pape";
+$aCities[]="Ris-Orangis";
+$aCities[]="Roanne";
+$aCities[]="Rochefort";
+$aCities[]="La Rochelle";
+$aCities[]="La Roche-sur-Yon";
+$aCities[]="Rodez";
+$aCities[]="Romainville";
+$aCities[]="Romans-sur-Isère";
+$aCities[]="Rosny-sous-Bois";
+$aCities[]="Roubaix";
+$aCities[]="Rouen";
+$aCities[]="Rueil-Malmaison";
+$aCities[]="Saint-André";
+$aCities[]="Saint-Benoît";
+$aCities[]="Saint-Brieuc";
+$aCities[]="Saint-Chamond";
+$aCities[]="Saint-Cloud";
+$aCities[]="Saint-Denis";
+$aCities[]="Saint-Denis";
+$aCities[]="Saint-Dié-des-Vosges";
+$aCities[]="Saint-Dizier";
+$aCities[]="Sainte-Foy-lès-Lyon";
+$aCities[]="Sainte-Geneviève-des-Bois";
+$aCities[]="Saintes";
+$aCities[]="Saint-Étienne";
+$aCities[]="Saint-Étienne-du-Rouvray";
+$aCities[]="Saint-Germain-en-Laye";
+$aCities[]="Saint-Herblain";
+$aCities[]="Saint-Joseph";
+$aCities[]="Saint-Laurent-du-Var";
+$aCities[]="Saint-Leu";
+$aCities[]="Saint-Lô";
+$aCities[]="Saint-Louis";
+$aCities[]="Saint-Louis";
+$aCities[]="Saint-Malo";
+$aCities[]="Saint-Martin-d'Hères";
+$aCities[]="Saint-Maur-des-Fossés";
+$aCities[]="Saint-Médard-en-Jalles";
+$aCities[]="Saint-Michel-sur-Orge";
+$aCities[]="Saint-Nazaire";
+$aCities[]="Saint-Ouen";
+$aCities[]="Saint-Paul";
+$aCities[]="Saint-Pierre";
+$aCities[]="Saint-Pol-sur-Mer";
+$aCities[]="Saint-Priest";
+$aCities[]="Saint-Quentin";
+$aCities[]="Saint-Raphaël";
+$aCities[]="Saint-Sébastien-sur-Loire";
+$aCities[]="Salon-de-Provence";
+$aCities[]="Sannois";
+$aCities[]="Sarcelles";
+$aCities[]="Sarreguemines";
+$aCities[]="Sartrouville";
+$aCities[]="Saumur";
+$aCities[]="Savigny-le-Temple";
+$aCities[]="Savigny-sur-Orge";
+$aCities[]="Schiltigheim";
+$aCities[]="Sedan";
+$aCities[]="Sens";
+$aCities[]="Sète";
+$aCities[]="Sevran";
+$aCities[]="Sèvres";
+$aCities[]="La Seyne-sur-Mer";
+$aCities[]="Six-Fours-les-Plages";
+$aCities[]="Soissons";
+$aCities[]="Sotteville-lès-Rouen";
+$aCities[]="Stains";
+$aCities[]="Strasbourg";
+$aCities[]="Sucy-en-Brie";
+$aCities[]="Suresnes";
+$aCities[]="Talence";
+$aCities[]="Le Tampon";
+$aCities[]="Tarbes";
+$aCities[]="Taverny";
+$aCities[]="La Teste-de-Buch";
+$aCities[]="Thiais";
+$aCities[]="Thionville";
+$aCities[]="Thonon-les-Bains";
+$aCities[]="Torcy";
+$aCities[]="Toulon";
+$aCities[]="Toulouse";
+$aCities[]="Tourcoing";
+$aCities[]="Tournefeuille";
+$aCities[]="Tours";
+$aCities[]="Trappes";
+$aCities[]="Tremblay-en-France";
+$aCities[]="Troyes";
+$aCities[]="Les Ulis";
+$aCities[]="Valence";
+$aCities[]="Valenciennes";
+$aCities[]="La Valette-du-Var";
+$aCities[]="Vallauris";
+$aCities[]="Vandœuvre-lès-Nancy";
+$aCities[]="Vannes";
+$aCities[]="Vanves";
+$aCities[]="Vaulx-en-Velin";
+$aCities[]="Vélizy-Villacoublay";
+$aCities[]="Vénissieux";
+$aCities[]="Verdun";
+$aCities[]="Vernon";
+$aCities[]="Versailles";
+$aCities[]="Vertou";
+$aCities[]="Vichy";
+$aCities[]="Vienne";
+$aCities[]="Vierzon";
+$aCities[]="Vigneux-sur-Seine";
+$aCities[]="Villefranche-sur-Saône";
+$aCities[]="Villejuif";
+$aCities[]="Villemomble";
+$aCities[]="Villenave-d'Ornon";
+$aCities[]="Villeneuve-d'Ascq";
+$aCities[]="Villeneuve-la-Garenne";
+$aCities[]="Villeneuve-Saint-Georges";
+$aCities[]="Villeneuve-sur-Lot";
+$aCities[]="Villeparisis";
+$aCities[]="Villepinte";
+$aCities[]="Villeurbanne";
+$aCities[]="Villiers-le-Bel";
+$aCities[]="Villiers-sur-Marne";
+$aCities[]="Vincennes";
+$aCities[]="Viry-Châtillon";
+$aCities[]="Vitrolles";
+$aCities[]="Vitry-sur-Seine";
+$aCities[]="Voiron";
+$aCities[]="Wattrelos";
+$aCities[]="Yerres";
+$aCities[]="Madrid";
+$aCities[]="Mataró";
+$aCities[]="Málaga";
+$aCities[]="Manzanares";
+$aCities[]="Marbella";
+$aCities[]="Marines";
+$aCities[]="Melilla";
+$aCities[]="Mijas";
+$aCities[]="Móstoles";
+$aCities[]="Murcie";
+$aCities[]="Sabadell";
+$aCities[]="Sagonte";
+$aCities[]="Salamanque";
+$aCities[]="Saint-Sébastien";
+$aCities[]="San Antonio de Benagéber";
+$aCities[]="Sant Boi de Llobregat";
+$aCities[]="Santa Cruz de Tenerife";
+$aCities[]="Santander";
+$aCities[]="Saragosse";
+$aCities[]="Ségovia";
+$aCities[]="Serra";
+$aCities[]="Séville";
+$aCities[]="Sitges";
+$aCities[]="Sóller";
+$aCities[]="Soria";
+$aCities[]="Aberdeen";
+$aCities[]="Aberystwyth";
+$aCities[]="Ashford";
+$aCities[]="Armagh";
+$aCities[]="Amersham";
+$aCities[]="Andover";
+$aCities[]="Arundel";
+$aCities[]="Abingdon";
+$aCities[]="Aylesbury";
+$aCities[]="Ayr";
+$aCities[]="Barrow-in-Furness";
+$aCities[]="Bath";
+$aCities[]="Bangor";
+$aCities[]="Bangor";
+$aCities[]="Belfast";
+$aCities[]="Birmingham";
+$aCities[]="Bournemouth";
+$aCities[]="Bradford";
+$aCities[]="Brighton";
+$aCities[]="Bristol";
+$aCities[]="Barrow-in-Furness";
+$aCities[]="Bath";
+$aCities[]="Bangor";
+$aCities[]="Bangor";
+$aCities[]="Belfast";
+$aCities[]="Birmingham";
+$aCities[]="Bournemouth";
+$aCities[]="Bradford";
+$aCities[]="Brighton";
+$aCities[]="Bristol";
+$aCities[]="Eastbourne";
+$aCities[]="Édimbourg";
+$aCities[]="Ely";
+$aCities[]="Epsom";
+$aCities[]="Eton";
+$aCities[]="Evanton";
+$aCities[]="Exeter";
+$aCities[]="Lancaster";
+$aCities[]="Larne";
+$aCities[]="Leeds";
+$aCities[]="Leicester";
+$aCities[]="Lichfield";
+$aCities[]="Lincoln";
+$aCities[]="Lisburn";
+$aCities[]="Liverpool";
+$aCities[]="Londres";
+$aCities[]="Londonderry";
+$aCities[]="Newcastle-upon-Tyne";
+$aCities[]="Newport";
+$aCities[]="Newry";
+$aCities[]="Norwich";
+$aCities[]="Nottingham";
+$aCities[]="Reading";
+$aCities[]="Ripon";
+$aCities[]="Richmond";
+$aCities[]="Ripley";
+$aCities[]="Rugby";
+$aCities[]="Wakefield";
+$aCities[]="Wells";
+$aCities[]="Westminster";
+$aCities[]="Weymouth";
+$aCities[]="Wick";
+$aCities[]="Wigan";
+$aCities[]="Winchester";
+$aCities[]="Windsor";
+$aCities[]="Wolverhampton";
+$aCities[]="Worcester";
+$aCities[]="Wye";
+$aCities[]="Abbeyville";
+$aCities[]="Abbeyville";
+$aCities[]="Aberdeen";
+$aCities[]="Abilene";
+$aCities[]="Abilene";
+$aCities[]="Acron";
+$aCities[]="Ada";
+$aCities[]="Adelanto";
+$aCities[]="Affton";
+$aCities[]="Agoura Hills";
+$aCities[]="Aitkin";
+$aCities[]="Akron";
+$aCities[]="Alabaster";
+$aCities[]="Alameda";
+$aCities[]="Alamogordo";
+$aCities[]="Albany";
+$aCities[]="Albany";
+$aCities[]="Albany";
+$aCities[]="Albuquerque";
+$aCities[]="Alexandria";
+$aCities[]="Alhambra";
+$aCities[]="Alhambra";
+$aCities[]="Aliso Viejo";
+$aCities[]="Allentown";
+$aCities[]="Alton";
+$aCities[]="Alturas";
+$aCities[]="Amador City";
+$aCities[]="Amarillo";
+$aCities[]="American Canyon";
+$aCities[]="Ames";
+$aCities[]="Amesbury";
+$aCities[]="Amherst";
+$aCities[]="Amherst";
+$aCities[]="Anaheim";
+$aCities[]="Anchorage";
+$aCities[]="Anderson";
+$aCities[]="Anderson";
+$aCities[]="Andover";
+$aCities[]="Angels Camp";
+$aCities[]="Ann Arbor";
+$aCities[]="Annapolis";
+$aCities[]="Anniston";
+$aCities[]="Antioch";
+$aCities[]="Apex";
+$aCities[]="Apple Valley";
+$aCities[]="Appleton";
+$aCities[]="Appleton";
+$aCities[]="Appleton";
+$aCities[]="Appomattox";
+$aCities[]="Arcadia";
+$aCities[]="Arcata";
+$aCities[]="Arlington";
+$aCities[]="Arlington";
+$aCities[]="Arlington";
+$aCities[]="Armonk";
+$aCities[]="Arnaudville";
+$aCities[]="Arnold";
+$aCities[]="Arroyo Grande";
+$aCities[]="Artesia";
+$aCities[]="Arvin";
+$aCities[]="Asbury Park";
+$aCities[]="Asheville";
+$aCities[]="Ashland";
+$aCities[]="Aspen";
+$aCities[]="Assonet";
+$aCities[]="Atascadero";
+$aCities[]="Athens";
+$aCities[]="Athens";
+$aCities[]="Atherton";
+$aCities[]="Atlanta";
+$aCities[]="Atlantic City";
+$aCities[]="Attica";
+$aCities[]="Atwater";
+$aCities[]="Auburn";
+$aCities[]="Auburn";
+$aCities[]="Augusta";
+$aCities[]="Augusta";
+$aCities[]="Aurora";
+$aCities[]="Austin";
+$aCities[]="Autaugaville";
+$aCities[]="Ava";
+$aCities[]="Avalon";
+$aCities[]="Ave Maria";
+$aCities[]="Avenal";
+$aCities[]="Avon";
+$aCities[]="Avondale";
+$aCities[]="Azusa";
+$aCities[]="Bakersfield";
+$aCities[]="Baltimore";
+$aCities[]="Baton Rouge";
+$aCities[]="Bay Minette";
+$aCities[]="Berkeley";
+$aCities[]="Bessemer";
+$aCities[]="Billings";
+$aCities[]="Billingsley";
+$aCities[]="Biloxi";
+$aCities[]="Birmingham";
+$aCities[]="Bismarck";
+$aCities[]="Boise";
+$aCities[]="Boston";
+$aCities[]="Branson";
+$aCities[]="Brownsville";
+$aCities[]="Buffalo";
+$aCities[]="Burlington";
+$aCities[]="Cambridge";
+$aCities[]="Canton";
+$aCities[]="Carmel";
+$aCities[]="Carson City";
+$aCities[]="Casper";
+$aCities[]="Cedar Rapids";
+$aCities[]="Champaign";
+$aCities[]="Charleston";
+$aCities[]="Charleston";
+$aCities[]="Charlotte";
+$aCities[]="Charlottesville";
+$aCities[]="Chattanooga";
+$aCities[]="Cheyenne";
+$aCities[]="Chicago";
+$aCities[]="Cicero";
+$aCities[]="Cincinnati";
+$aCities[]="Cleveland";
+$aCities[]="Colby";
+$aCities[]="Colorado Springs";
+$aCities[]="Columbia";
+$aCities[]="Columbus";
+$aCities[]="Concord";
+$aCities[]="Corpus Christi";
+$aCities[]="Cullman";
+$aCities[]="Dallas";
+$aCities[]="Daphne";
+$aCities[]="Davenport";
+$aCities[]="Dayton";
+$aCities[]="Daytona Beach";
+$aCities[]="Denver";
+$aCities[]="Des Moines";
+$aCities[]="Detroit";
+$aCities[]="Dodge City";
+$aCities[]="Dothan";
+$aCities[]="Dover";
+$aCities[]="Duluth";
+$aCities[]="El Paso";
+$aCities[]="Enterprise";
+$aCities[]="Érié";
+$aCities[]="Eugene";
+$aCities[]="Evansville";
+$aCities[]="Fairfield";
+$aCities[]="Fairbanks";
+$aCities[]="Fargo";
+$aCities[]="Fayetteville";
+$aCities[]="Fayetteville";
+$aCities[]="Flagstaff";
+$aCities[]="Flint";
+$aCities[]="Fort Collins";
+$aCities[]="Fort Lauderdale";
+$aCities[]="Fort Worth";
+$aCities[]="Frankfort";
+$aCities[]="Fresno";
+$aCities[]="Grand Rapids";
+$aCities[]="Great Falls";
+$aCities[]="Green Bay";
+$aCities[]="Greensboro";
+$aCities[]="Greenville";
+$aCities[]="Harrisburg";
+$aCities[]="Hartford";
+$aCities[]="Hays";
+$aCities[]="Helena";
+$aCities[]="Honolulu";
+$aCities[]="Hoover";
+$aCities[]="Hot Springs";
+$aCities[]="Houston";
+$aCities[]="Huntington";
+$aCities[]="Huntsville";
+$aCities[]="Jackson";
+$aCities[]="Jacksonville";
+$aCities[]="Jefferson City";
+$aCities[]="Joplin";
+$aCities[]="Juneau";
+$aCities[]="Idaho Falls";
+$aCities[]="Indianapolis";
+$aCities[]="Lafayette";
+$aCities[]="Lancaster";
+$aCities[]="Lansing";
+$aCities[]="Laredo";
+$aCities[]="Las Cruces";
+$aCities[]="Las Vegas";
+$aCities[]="Leavenworth";
+$aCities[]="Lexington";
+$aCities[]="Lincoln";
+$aCities[]="Little Rock";
+$aCities[]="Littleton";
+$aCities[]="Los Alamos";
+$aCities[]="Los Angeles";
+$aCities[]="Louisville";
+$aCities[]="Lubbock";
+$aCities[]="Madison";
+$aCities[]="Manchester";
+$aCities[]="McAllen";
+$aCities[]="Melbourne";
+$aCities[]="Memphis";
+$aCities[]="Mesa";
+$aCities[]="Miami";
+$aCities[]="Milbrook";
+$aCities[]="Milwaukee";
+$aCities[]="Minneapolis";
+$aCities[]="Missoula";
+$aCities[]="Mobile";
+$aCities[]="Modesto";
+$aCities[]="Monterey";
+$aCities[]="Montgomery";
+$aCities[]="Montpelier";
+$aCities[]="Mountain View";
+$aCities[]="Muncie";
+$aCities[]="Nashville";
+$aCities[]="New Bedford";
+$aCities[]="New Haven";
+$aCities[]="Newport";
+$aCities[]="New York";
+$aCities[]="Niagara Falls";
+$aCities[]="Norfolk";
+$aCities[]="La Nouvelle-Orléans";
+$aCities[]="Newark";
+$aCities[]="Oakland";
+$aCities[]="Oklahoma City";
+$aCities[]="Olympia";
+$aCities[]="Omaha";
+$aCities[]="Orem";
+$aCities[]="Orlando";
+$aCities[]="Palm Springs";
+$aCities[]="Palo Alto";
+$aCities[]="Pearl Harbor";
+$aCities[]="Pensacola";
+$aCities[]="Peoria";
+$aCities[]="Philadelphie";
+$aCities[]="Phoenix";
+$aCities[]="Pierre";
+$aCities[]="Pittsburgh";
+$aCities[]="Point du lac";
+$aCities[]="Portland";
+$aCities[]="Portland";
+$aCities[]="Prattville";
+$aCities[]="Providence";
+$aCities[]="Racine";
+$aCities[]="Raleigh";
+$aCities[]="Rapid City";
+$aCities[]="Redding";
+$aCities[]="Redmond";
+$aCities[]="Redwood City";
+$aCities[]="Reno";
+$aCities[]="Richmond";
+$aCities[]="Roanoke";
+$aCities[]="Rochester";
+$aCities[]="Rockford";
+$aCities[]="Sacramento";
+$aCities[]="Saint Louis";
+$aCities[]="Saint Paul";
+$aCities[]="Salem";
+$aCities[]="Salina";
+$aCities[]="Salt Lake City";
+$aCities[]="San Antonio";
+$aCities[]="San Bernardino";
+$aCities[]="San Diego";
+$aCities[]="San Francisco";
+$aCities[]="San José";
+$aCities[]="San Luis Obispo";
+$aCities[]="San Mateo";
+$aCities[]="Santa Barbara";
+$aCities[]="Santa Cruz";
+$aCities[]="Santa Fe";
+$aCities[]="Sarasota";
+$aCities[]="Saratoga";
+$aCities[]="Savannah";
+$aCities[]="Scranton";
+$aCities[]="Seattle";
+$aCities[]="Shreveport";
+$aCities[]="Sioux Falls";
+$aCities[]="South Bend";
+$aCities[]="Spokane";
+$aCities[]="Springfield";
+$aCities[]="Springfield";
+$aCities[]="Springfield";
+$aCities[]="Stamford";
+$aCities[]="Stockton";
+$aCities[]="Syracuse";
+$aCities[]="Tacoma";
+$aCities[]="Tallahassee";
+$aCities[]="Tampa";
+$aCities[]="Taunton";
+$aCities[]="Toledo";
+$aCities[]="Topeka";
+$aCities[]="Trenton";
+$aCities[]="Tucson";
+$aCities[]="Tulsa";
+$aCities[]="Tuscaloosa";
+$aCities[]="Tuskegee";
+$aCities[]="Waco";
+$aCities[]="Washington, DC";
+$aCities[]="Waterbury";
+$aCities[]="Wichita";
+$aCities[]="Williamsburg";
+$aCities[]="Wilmington";
+$aCities[]="Wilmington";
+$aCities[]="Worcester";
+$aCities[]="Babenhausen";
+$aCities[]="Bacharach";
+$aCities[]="Backnang";
+$aCities[]="Bad Aibling";
+$aCities[]="Bad Arolsen";
+$aCities[]="Bad Bentheim";
+$aCities[]="Bad Bergzabern";
+$aCities[]="Bad Berka";
+$aCities[]="Bad Berleburg";
+$aCities[]="Bad Berneck im Fichtelgebirge";
+$aCities[]="Bad Bevensen";
+$aCities[]="Bad Bibra";
+$aCities[]="Bad Blankenburg";
+$aCities[]="Bad Bramstedt";
+$aCities[]="Bad Breisig";
+$aCities[]="Bad Brückenau";
+$aCities[]="Bad Buchau";
+$aCities[]="Bad Camberg";
+$aCities[]="Bad Colberg-Heldburg";
+$aCities[]="Bad Doberan";
+$aCities[]="Bad Driburg";
+$aCities[]="Bad Düben";
+$aCities[]="Bad Dürkheim";
+$aCities[]="Bad Dürrenberg";
+$aCities[]="Bad Dürrheim";
+$aCities[]="Bad Elster";
+$aCities[]="Bad Ems";
+$aCities[]="Baden-Baden";
+$aCities[]="Bad Fallingbostel";
+$aCities[]="Bad Frankenhausen";
+$aCities[]="Bad Freienwalde";
+$aCities[]="Bad Friedrichshall";
+$aCities[]="Bad Gandersheim";
+$aCities[]="Bad Gottleuba-Berggießhübel";
+$aCities[]="Bad Griesbach im Rottal";
+$aCities[]="Bad Grund";
+$aCities[]="Bad Harzburg";
+$aCities[]="Bad Herrenalb";
+$aCities[]="Bad Hersfeld";
+$aCities[]="Bad Homburg vor der Höhe";
+$aCities[]="Bad Honnef";
+$aCities[]="Bad Hönningen";
+$aCities[]="Calau";
+$aCities[]="Calbe";
+$aCities[]="Calw";
+$aCities[]="Camburg";
+$aCities[]="Castrop-Rauxel";
+$aCities[]="Celle";
+$aCities[]="Cham";
+$aCities[]="Chemnitz";
+$aCities[]="Clausthal-Zellerfeld";
+$aCities[]="Cloppenburg";
+$aCities[]="Coburg";
+$aCities[]="Cochem";
+$aCities[]="Coesfeld";
+$aCities[]="Colditz";
+$aCities[]="Coswig";
+$aCities[]="Coswig";
+$aCities[]="Crailsheim";
+$aCities[]="Creglingen";
+$aCities[]="Creussen";
+$aCities[]="Creuzburg";
+$aCities[]="Crimmitschau";
+$aCities[]="Crivitz";
+$aCities[]="Cuxhaven";
+$aCities[]="Bad Iburg";
+$aCities[]="Bad Karlshafen";
+$aCities[]="Bad Kissingen";
+$aCities[]="Bad König";
+$aCities[]="Bad Königshofen im Grabfeld";
+$aCities[]="Bad Kösen";
+$aCities[]="Bad Köstritz";
+$aCities[]="Bad Kötzting";
+$aCities[]="Bad Kreuznach";
+$aCities[]="Bad Krozingen";
+$aCities[]="Bad Laasphe";
+$aCities[]="Bad Langensalza";
+$aCities[]="Bad Lauchstädt";
+$aCities[]="Bad Lausick";
+$aCities[]="Bad Lauterberg im Harz";
+$aCities[]="Bad Liebenstein";
+$aCities[]="Bad Liebenwerda";
+$aCities[]="Bad Liebenzell";
+$aCities[]="Bad Lippspringe";
+$aCities[]="Bad Lobenstein";
+$aCities[]="Bad Marienberg";
+$aCities[]="Bad Mergentheim";
+$aCities[]="Bad Münder am Deister";
+$aCities[]="Bad Münster am Stein-Ebernburg";
+$aCities[]="Bad Münstereifel";
+$aCities[]="Bad Muskau";
+$aCities[]="Bad Nauheim";
+$aCities[]="Bad Nenndorf";
+$aCities[]="Bad Neuenahr-Ahrweiler";
+$aCities[]="Bad Neustadt an der Saale";
+$aCities[]="Bad Oeynhausen";
+$aCities[]="Bad Oldesloe";
+$aCities[]="Bad Orb";
+$aCities[]="Bad Pyrmont";
+$aCities[]="Bad Rappenau";
+$aCities[]="Bad Reichenhall";
+$aCities[]="Bad Rodach";
+$aCities[]="Bad Sachsa";
+$aCities[]="Bad Säckingen";
+$aCities[]="Bad Salzdetfurth";
+$aCities[]="Bad Salzuflen";
+$aCities[]="Bad Salzungen";
+$aCities[]="Bad Saulgau";
+$aCities[]="Bad Schandau";
+$aCities[]="Bad Schmiedeberg";
+$aCities[]="Bad Schussenried";
+$aCities[]="Bad Schwalbach";
+$aCities[]="Bad Schwartau";
+$aCities[]="Bad Segeberg";
+$aCities[]="Bad Soden am Taunus";
+$aCities[]="Bad Soden-Salmünster";
+$aCities[]="Bad Sooden-Allendorf";
+$aCities[]="Bad Staffelstein";
+$aCities[]="Bad Sulza";
+$aCities[]="Bad Sülze";
+$aCities[]="Bad Teinach-Zavelstein";
+$aCities[]="Bad Tennstedt";
+$aCities[]="Bad Tölz";
+$aCities[]="Bad Urach";
+$aCities[]="Bad Vilbel";
+$aCities[]="Bad Waldsee";
+$aCities[]="Bad Wildbad";
+$aCities[]="Bad Wildungen";
+$aCities[]="Bad Wilsnack";
+$aCities[]="Bad Wimpfen";
+$aCities[]="Bad Windsheim";
+$aCities[]="Bad Wörishofen";
+$aCities[]="Bad Wünnenberg";
+$aCities[]="Bad Wurzach";
+$aCities[]="Baesweiler";
+$aCities[]="Baiersdorf";
+$aCities[]="Balingen";
+$aCities[]="Ballenstedt";
+$aCities[]="Balve";
+$aCities[]="Bamberg";
+$aCities[]="Barby";
+$aCities[]="Bargteheide";
+$aCities[]="Barmstedt";
+$aCities[]="Bärnau";
+$aCities[]="Barntrup";
+$aCities[]="Barsinghausen";
+$aCities[]="Barth";
+$aCities[]="Baruth/Mark";
+$aCities[]="Bassum";
+$aCities[]="Battenberg";
+$aCities[]="Baumholder";
+$aCities[]="Baunach";
+$aCities[]="Baunatal";
+$aCities[]="Bautzen";
+$aCities[]="Bayreuth";
+$aCities[]="Bebra";
+$aCities[]="Beckum";
+$aCities[]="Bedburg";
+$aCities[]="Beelitz";
+$aCities[]="Beerfelden";
+$aCities[]="Beeskow";
+$aCities[]="Beilngries";
+$aCities[]="Beilstein";
+$aCities[]="Belgern";
+$aCities[]="Belzig";
+$aCities[]="Bendorf";
+$aCities[]="Benneckenstein";
+$aCities[]="Bensheim";
+$aCities[]="Berching";
+$aCities[]="Berga/Elster";
+$aCities[]="Bergen";
+$aCities[]="Bergen auf Rügen";
+$aCities[]="Bergheim";
+$aCities[]="Bergisch Gladbach";
+$aCities[]="Bergkamen";
+$aCities[]="Bergneustadt";
+$aCities[]="Berka/Werra";
+$aCities[]="Berlin";
+$aCities[]="Bernau bei Berlin";
+$aCities[]="Bernburg";
+$aCities[]="Bernkastel-Kues";
+$aCities[]="Bernsdorf";
+$aCities[]="Bernstadt a. d. Eigen";
+$aCities[]="Bersenbrück";
+$aCities[]="Besigheim";
+$aCities[]="Betzdorf";
+$aCities[]="Betzenstein";
+$aCities[]="Beverungen";
+$aCities[]="Bexbach";
+$aCities[]="Biberach an der Riß";
+$aCities[]="Biedenkopf";
+$aCities[]="Bielefeld";
+$aCities[]="Biesenthal";
+$aCities[]="Bietigheim-Bissingen";
+$aCities[]="Billerbeck";
+$aCities[]="Bingen am Rhein";
+$aCities[]="Birkenfeld";
+$aCities[]="Bischofsheim an der Rhön";
+$aCities[]="Bischofswerda";
+$aCities[]="Bismark";
+$aCities[]="Bitburg";
+$aCities[]="Bitterfeld";
+$aCities[]="Blankenburg";
+$aCities[]="Blankenhain";
+$aCities[]="Bleckede";
+$aCities[]="Bleicherode";
+$aCities[]="Blieskastel";
+$aCities[]="Blomberg";
+$aCities[]="Blumberg";
+$aCities[]="Bobingen";
+$aCities[]="Böblingen";
+$aCities[]="Bocholt";
+$aCities[]="Bochum";
+$aCities[]="Bockenem";
+$aCities[]="Bodenwerder";
+$aCities[]="Bogen";
+$aCities[]="Böhlen";
+$aCities[]="Boizenburg/Elbe";
+$aCities[]="Bonn";
+$aCities[]="Bonndorf im Schwarzwald";
+$aCities[]="Bönnigheim";
+$aCities[]="Bopfingen";
+$aCities[]="Boppard";
+$aCities[]="Borgentreich";
+$aCities[]="Borgholzhausen";
+$aCities[]="Borken";
+$aCities[]="Borken";
+$aCities[]="Borkum";
+$aCities[]="Borna";
+$aCities[]="Bornheim";
+$aCities[]="Bottrop";
+$aCities[]="Boxberg";
+$aCities[]="Brackenheim";
+$aCities[]="Brake";
+$aCities[]="Brakel";
+$aCities[]="Bramsche";
+$aCities[]="Brandenburg an der Havel";
+$aCities[]="Brand-Erbisdorf";
+$aCities[]="Brandis";
+$aCities[]="Braubach";
+$aCities[]="Braunfels";
+$aCities[]="Braunlage";
+$aCities[]="Bräunlingen";
+$aCities[]="Braunsbedra";
+$aCities[]="Braunschweig";
+$aCities[]="Breckerfeld";
+$aCities[]="Bredstedt";
+$aCities[]="Brehna";
+$aCities[]="Breisach am Rhein";
+$aCities[]="Bremen";
+$aCities[]="Bremerhaven";
+$aCities[]="Bremervörde";
+$aCities[]="Bretten";
+$aCities[]="Breuberg";
+$aCities[]="Brilon";
+$aCities[]="Brotterode";
+$aCities[]="Bruchköbel";
+$aCities[]="Bruchsal";
+$aCities[]="Brück";
+$aCities[]="Brüel";
+$aCities[]="Brühl";
+$aCities[]="Brunsbüttel";
+$aCities[]="Brüssow";
+$aCities[]="Buchen";
+$aCities[]="Buchholz in der Nordheide";
+$aCities[]="Buchloe";
+$aCities[]="Bückeburg";
+$aCities[]="Buckow";
+$aCities[]="Büdelsdorf";
+$aCities[]="Büdingen";
+$aCities[]="Bühl";
+$aCities[]="Bünde";
+$aCities[]="Büren";
+$aCities[]="Burg";
+$aCities[]="Burgau";
+$aCities[]="Burgbernheim";
+$aCities[]="Burgdorf";
+$aCities[]="Bürgel";
+$aCities[]="Burghausen";
+$aCities[]="Burgkunstadt";
+$aCities[]="Burglengenfeld";
+$aCities[]="Burgstädt";
+$aCities[]="Burg Stargard";
+$aCities[]="Burgwedel";
+$aCities[]="Burladingen";
+$aCities[]="Burscheid";
+$aCities[]="Bürstadt";
+$aCities[]="Buttelstedt";
+$aCities[]="Buttstädt";
+$aCities[]="Butzbach";
+$aCities[]="Bützow";
+$aCities[]="Buxtehude";
+
+?>

+ 367 - 0
business/incident.business.php

@@ -0,0 +1,367 @@
+<?php
+
+
+/**
+ * incident<business<php
+ * Define business model for incident mgmt module
+ *
+ * @package     iTopBizModelSamples
+ * @author      Erwan Taloc <erwan.taloc@gmail.com>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* An Incident Ticket
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizIncidentTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Incident",
+			"description" => "Incident ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",  
+			"state_attcode" => "ticket_status",
+			"reconc_keys" => array("title"),
+			"db_table" => "incident",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/ticket.html",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"TicketID", "description"=>"Refence number ofr this incident", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("title", array("label"=>"Title", "description"=>"Overview of the Incident", "allowed_values"=>null, "sql"=>"title", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+   	
+    MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of the Incident", "allowed_values"=>new ValueSetEnum("Network,Server,Desktop,Application"), "sql"=>"type", "default_value"=>"Server", "is_null_allowed"=>false, "depends_on"=>array())));
+     MetaModel::Init_AddAttribute(new AttributeExternalKey("customer_id", array("targetclass"=>"bizOrganization", "label"=>"Customer", "description"=>"who is impacted by the ticket", "allowed_values"=>null, "sql"=>"customer", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("customer_name", array("label"=>"Customer", "description"=>"Name of the customer impacted by this ticket", "allowed_values"=>null, "extkey_attcode"=> 'customer_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeEnum("ticket_status", array("label"=>"Status", "description"=>"Status of the ticket", "allowed_values"=>new ValueSetEnum("New, Assigned, WorkInProgress, Closed"), "sql"=>"ticket_status", "default_value"=>"New", "is_null_allowed"=>false, "depends_on"=>array())));
+		// SetPossibleValues("status",array("Open","Monitored","Closed"));
+		MetaModel::Init_AddAttribute(new AttributeText("initial_situation", array("label"=>"Initial Situation", "description"=>"Initial situation of the Incident", "allowed_values"=>null, "sql"=>"initial_situation", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("current_situation", array("label"=>"Current Situation", "description"=>"Current situation of the Incident", "allowed_values"=>null, "sql"=>"current_situation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("start_date", array("label"=>"Starting date", "description"=>"Incident starting date", "allowed_values"=>null, "sql"=>"start_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+    // définir une date de défaut à maintenant, alias creation ou modification du ticket
+		MetaModel::Init_AddAttribute(new AttributeDate("last_update", array("label"=>"Last update", "description"=>"last time the Ticket was modified", "allowed_values"=>null, "sql"=>"last_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	      MetaModel::Init_AddAttribute(new AttributeDate("next_update", array("label"=>"Next update", "description"=>"next time the Ticket is expected to be  modified", "allowed_values"=>null, "sql"=>"next_update", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeDate("end_date", array("label"=>"Closed Date", "description"=>"Date when the Ticket was closed", "allowed_values"=>null, "sql"=>"closed_date", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeExternalKey("caller_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Caller", "description"=>"person that trigger incident", "allowed_values"=>null, "sql"=>"caller_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("caller_mail", array("label"=>"Caller", "description"=>"Person that trigger this incident", "allowed_values"=>null, "extkey_attcode"=> 'caller_id', "target_attcode"=>"email")));
+	
+  	MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact of the Incident", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+  	MetaModel::Init_AddAttribute(new AttributeExternalKey("workgroup_id", array("targetclass"=>"bizWorkgroup", "jointype"=> "", "label"=>"Workgroup", "description"=>"which workgroup is owning ticket", "allowed_values"=>null, "sql"=>"workgroup_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("workgroup_name", array("label"=>"Managed by Workgroup", "description"=>"name of workgroup managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'workgroup_id', "target_attcode"=>"name")));  
+    MetaModel::Init_AddAttribute(new AttributeExternalKey("agent_id", array("targetclass"=>"bizPerson", "jointype"=> "", "label"=>"Agent", "description"=>"who is managing the ticket", "allowed_values"=>null, "sql"=>"agent_id", "is_null_allowed"=>true, "depends_on"=>array("workgroup_id"))));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("agent_mail", array("label"=>"Managed by Agent", "description"=>"mail of agent managing the Ticket", "allowed_values"=>null, "extkey_attcode"=> 'agent_id', "target_attcode"=>"email")));
+		// Comment afficher le first + last name de l'agent ? Est-ce utile d'ajouter ce champ?
+		MetaModel::Init_AddAttribute(new AttributeText("action_log", array("label"=>"Action Logs", "description"=>"List all action performed during the incident", "allowed_values"=>null, "sql"=>"action_log", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+				MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Field defining the criticity if the incident", "allowed_values"=>new ValueSetEnum("critical,medium,low"), "sql"=>"criticity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeInteger("assignment_count", array("label"=>"Assignment Count", "description"=>"Number of times this ticket was assigned or reassigned", "allowed_values"=>null, "sql"=>"assignment_count", "default_value"=>0, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("resolution", array("label"=>"Resolution", "description"=>"Description of the resolution", "allowed_values"=>null, "sql"=>"resolution", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("impacted_infra_manual", array("label"=>"Impacted Infrastructure", "description"=>"CIs that are not meeting the SLA", "linked_class"=>"lnkInfraTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"infra_id", "allowed_values"=>null, "count_min"=>1, "count_max"=>0, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_tickets", array("label"=>"Related Tickets", "description"=>"Other incident tickets related to this one", "linked_class"=>"lnkRelatedTicket", "ext_key_to_me"=>"ticket_id", "ext_key_to_remote"=>"rel_ticket_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array(/*'impacted_infra_computed',*/ 'impacted_infra_manual'))));
+
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("title");
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("customer_id");
+		MetaModel::Init_AddFilterFromAttribute("caller_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_status");
+		MetaModel::Init_AddFilterFromAttribute("start_date");
+		MetaModel::Init_AddFilterFromAttribute("last_update");
+		MetaModel::Init_AddFilterFromAttribute("end_date");
+		MetaModel::Init_AddFilterFromAttribute("workgroup_id");
+		MetaModel::Init_AddFilterFromAttribute("agent_id");
+		MetaModel::Init_AddFilterFromAttribute("severity");
+		MetaModel::Init_AddFilterFromAttribute("assignment_count");
+
+		// doit-on aussi ajouter un filtre sur les extfields lié à une extkey ? ici le name de l'agent?
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name','title', 'customer_id', 'type','ticket_status', 'severity','start_date', 'initial_situation', 'current_situation','caller_id', 'impact', 'last_update', 'next_update','end_date', 'assignment_count', 'workgroup_id','agent_id','action_log','resolution')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('name', 'title', 'customer_id', 'type','ticket_status','severity','start_date', 'initial_situation')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'title', 'customer_id', 'caller_id','type', 'ticket_status', 'severity','start_date', 'last_update','end_date','agent_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'title', 'customer_id','caller_id','type','ticket_status', 'severity','start_date', 'last_update', 'end_date','agent_id')); // Criteria of the advanced search form
+
+		// State machine
+		MetaModel::Init_DefineState("New", array("label"=>"New (Unassigned)", "description"=>"Newly created ticket", "attribute_inherit"=>null,
+												 "attribute_list"=>array('name' => OPT_ATT_READONLY, 'assignment_count' => OPT_ATT_HIDDEN, 'end_date' => OPT_ATT_HIDDEN, 'next_update' => OPT_ATT_HIDDEN, 'last_update' =>  OPT_ATT_HIDDEN,
+												 "title"=>OPT_ATT_MANDATORY, "customer_id"=>OPT_ATT_MANDATORY, "caller_id"=>OPT_ATT_MANDATORY, "initial_situation"=>OPT_ATT_MANDATORY, "start_date"=>OPT_ATT_MANDATORY, "workgroup_id"=>OPT_ATT_MANDATORY,
+												 "severity"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_HIDDEN,"impacted_infra_manual"=>OPT_ATT_MANDATORY, "related_tickets"=>OPT_ATT_MUSTPROMPT)));
+		MetaModel::Init_DefineState("Assigned", array("label"=>"Assigned", "description"=>"Ticket is assigned to somebody", "attribute_inherit"=>null,
+												"attribute_list"=>array('name' => OPT_ATT_READONLY, "title"=>OPT_ATT_READONLY, "customer_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY, "start_date"=>OPT_ATT_READONLY,'assignment_count' => OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "workgroup_id"=>OPT_ATT_MUSTCHANGE, "agent_id"=>OPT_ATT_MUSTCHANGE)));
+		MetaModel::Init_DefineState("WorkInProgress", array("label"=>"Work In Progress", "description"=>"Work is in progress", "attribute_inherit"=>null, "attribute_list"=>array("title"=>OPT_ATT_READONLY, "customer_id"=>OPT_ATT_READONLY, "caller_id"=>OPT_ATT_READONLY, "initial_situation"=>OPT_ATT_READONLY,'end_date' => OPT_ATT_HIDDEN, "start_date"=>OPT_ATT_READONLY,"workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("Closed", array("label"=>"Closed", "description"=>"Ticket is closed", "attribute_inherit"=>null, "attribute_list"=>array("workgroup_id"=>OPT_ATT_MANDATORY, "agent_id"=>OPT_ATT_MANDATORY, "resolution"=>OPT_ATT_MANDATORY, "end_date"=>OPT_ATT_MANDATORY)));
+
+		MetaModel::Init_DefineStimulus("ev_assign", new StimulusUserAction(array("label"=>"Assign this ticket", "description"=>"Assign this ticket to a group and an agent")));
+		MetaModel::Init_DefineStimulus("ev_reassign", new StimulusUserAction(array("label"=>"Reassign this ticket", "description"=>"Reassign this ticket to a different group and agent")));
+		MetaModel::Init_DefineStimulus("ev_start_working", new StimulusUserAction(array("label"=>"Work on this ticket", "description"=>"Start working on this ticket")));
+		MetaModel::Init_DefineStimulus("ev_close", new StimulusUserAction(array("label"=>"Close this ticket", "description"=>"Close/resolve this ticket")));
+
+		MetaModel::Init_DefineTransition("New", "ev_assign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Assigned", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Assigned", "ev_start_working", array("target_state"=>"WorkInProgress", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("WorkInProgress", "ev_reassign", array("target_state"=>"Assigned", "actions"=>array('IncrementAssignmentCount'), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("WorkInProgress", "ev_close", array("target_state"=>"Closed", "actions"=>array('SetClosureDate'), "user_restriction"=>null));
+		
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('customer_id', $oGenerator->GetOrganizationId());
+		$this->Set('title', $oGenerator->GenerateString("enum(Site,Server,Line)| |enum(is down,is flip-flopping,is not responding)"));
+		$this->Set('agent_id', $oGenerator->GenerateKey("bizPerson", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_status', $oGenerator->GenerateString("enum(Open,Closed,Closed,Monitored)"));
+		$this->Set('start_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+		$this->Set('last_update', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+		$this->Set('end_date', $oGenerator->GenerateString("2007-|number(07-12)|-|number(01-30)| |number(07-12)|:|number(00-59)|:|number(00-59)"));
+	}
+	
+	public static function GetUIPage()
+	{
+		return './UI.php';
+	}
+	
+	// State machine actions
+	public function IncrementAssignmentCount($sStimulusCode)
+	{
+		$this->Set('assignment_count', $this->Get('assignment_count') + 1);
+		return true;
+	}
+	
+	public function SetClosureDate($sStimulusCode)
+	{
+		$this->Set('end_date', time());
+		return true;
+	}
+	
+	public function ComputeFields()
+	{
+		if ($this->GetKey() > 0)
+		{
+			$sName = sprintf('I-%06d', $this->GetKey());
+		}
+		else
+		{
+			$sName = "Id not set";
+		}
+		$this->Set('name', $sName);
+	}
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Incident
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkRelatedTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Related Ticket",
+			"description" => "Ticket related to a ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "impact",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("impact"),  // ????
+			"db_table" => "related_ticket",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("rel_ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Related Ticket id", "description"=>"The related ticket", "allowed_values"=>null, "sql"=>"rel_ticket_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("rel_ticket_name", array("label"=>"Related ticket", "description"=>"Name of the related ticket", "allowed_values"=>null, "extkey_attcode"=> 'rel_ticket_id', "target_attcode"=>"title")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title")));
+		MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Impact on the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("rel_ticket_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('rel_ticket_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('rel_ticket_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('rel_ticket_id', 'ticket_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('rel_ticket_id', 'ticket_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Incident
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInfraTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Infra Ticket",
+			"description" => "Infra impacted by a ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "impact",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("impact"),  // ????
+			"db_table" => "infra_ticket",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"The infrastructure impacted", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title")));
+		MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Impact", "description"=>"Level of impact of the infra by the related ticket", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("infra_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('infra_id', 'ticket_id', 'impact')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'ticket_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'ticket_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Contqct and a Incident
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkContactTicket extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Contact Ticket",
+			"description" => "Contacts to be notify for a ticket",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "role",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("role"),  // ????
+			"db_table" => "contact_ticket",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "jointype"=> '', "label"=>"Contact", "description"=>"Contact to Notify", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"Contact email", "description"=>"Mail for the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("ticket_id", array("targetclass"=>"bizIncidentTicket", "jointype"=> '', "label"=>"Ticket #", "description"=>"Ticket number", "allowed_values"=>null, "sql"=>"ticket_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("ticket_name", array("label"=>"Ticket name", "description"=>"Name of the ticket", "allowed_values"=>null, "extkey_attcode"=> 'ticket_id', "target_attcode"=>"title")));
+		MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("contact_id");
+		MetaModel::Init_AddFilterFromAttribute("ticket_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('contact_id', 'ticket_id', 'role')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('contact_id', 'ticket_id', 'role')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('contact_id', 'ticket_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('contact_id', 'ticket_id')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('ticket_id', $oGenerator->GenerateKey("bizIncidentTicket", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+////////////////////////////////////////////////////////////////////////////////////
+//**
+//* A workgroup is a queue in a given call tracking system
+//* It belongs to a team and a given organization
+////////////////////////////////////////////////////////////////////////////////////
+class bizWorkgroup extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Workgroup",
+			"description" => "Call tracking workgroup",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "workgroups",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("team_id", array("targetclass"=>"bizTeam", "label"=>"Team", "description"=>"Team owning the workgroup", "allowed_values"=>null, "sql"=>"team_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("team_name", array("label"=>"Team name", "description"=>"name of the team", "allowed_values"=>null, "extkey_attcode"=> 'team_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of this work group", "allowed_values"=>new ValueSetEnum("1st level support,2nd level support,3rd level support"), "sql"=>"role", "default_value"=>"1st level support", "is_null_allowed"=>true, "depends_on"=>array())));
+		
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("org_name");
+		MetaModel::Init_AddFilterFromAttribute("team_id");
+		MetaModel::Init_AddFilterFromAttribute("role");
+		
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'team_id', 'role')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'team_id','role')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'team_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'team_id','role')); // Criteria of the advanced search form
+
+	}
+}
+
+
+
+?>

+ 1629 - 0
business/itop.business.class.inc.php

@@ -0,0 +1,1629 @@
+<?php
+require_once('../application/cmdbabstract.class.inc.php');
+require_once('../application/template.class.inc.php');
+
+
+/**
+ * itop.business.class.inc.php
+ * User defined objects, implements the business need 
+ *
+ * @package     iTopBizModelSamples
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+/**
+ * Possible values for the statuses of objects
+ */
+$oAllowedStatuses = new ValueSetEnum('production,implementation,obsolete');
+
+/**
+ * Relation graphs
+ */
+MetaModel::RegisterRelation("impacts", array("description"=>"objects being functionaly impacted", "verb_down"=>"impacts", "verb_up"=>"is impacted by"));
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* An organization that owns some objects
+*
+* An organization "owns" some persons (its employees) but also some other objects
+* (its assets) like buildings, computers, furniture...
+* the services that they provides, the contracts/OLA they have signed as customer
+* 
+* Organization ownership might be used to manage the R/W access to the object
+*/
+////////////////////////////////////////////////////////////////////////////////////
+/**
+ * itop.business.class.inc.php
+ * User defined objects, implements the business need 
+ *
+ * @package     iTopBizModelSamples
+ * @author      Erwan Taloc <taloche@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class bizOrganization extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		global $oAllowedStatuses;
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Organization",
+			"description" => "Organizational structure: can be Company and/or Department",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("name"),
+			"db_table" => "organizations",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array() )));
+		MetaModel::Init_AddAttribute(new AttributeString("code", array("label"=>"Code", "description"=>"Organization code (Siret, DUNS,...)", "allowed_values"=>null, "sql"=>"code", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array() )));
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass"=>"bizOrganization", "label"=>"Parent Id", "description"=>"Parent organization", "allowed_values"=>null, "sql"=>"parent_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("parent_name", array("label"=>"Parent Name", "description"=>"Name of the parent organization", "allowed_values"=>null, "extkey_attcode"=> 'parent_id', "target_attcode"=>"name")));
+
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("code");
+		MetaModel::Init_AddFilterFromAttribute("status");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'code', 'status', 'parent_id')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'parent_id')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'code', 'status')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'code', 'status')); // Criteria of the advanced search form
+	}
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		//$this->SetKey($oGenerator->GetOrganizationCode());
+		$this->Set('name', $oGenerator->GetOrganizationName());
+		$this->Set('code', $oGenerator->GetOrganizationCode());
+		$this->Set('status', 'implementation');
+		$this->Set('parent_id', 1);
+
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Class of objects owned by some organization
+*
+* This is the root class of all the objects that can be "owned" by an organization
+* 
+* A Real Object
+*   can be supported by Contacts, having a specific role (same contact with multiple roles?)
+*   can be documented by Documents
+*/
+////////////////////////////////////////////////////////////////////////////////////
+/**
+ * itop.business.class.inc.php
+ * User defined objects, implements the business need 
+ *
+ * @package     iTopBizModelSamples
+ * @author      Erwan Taloc <taloche@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class logRealObject extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		global $oAllowedStatuses;
+		
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Object",
+			"description" => "Any CMDB object",
+			"key_type" => "autoincrement",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("name"),
+			"db_table" => "objects",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "obj_class",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"Common name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Lifecycle status", "allowed_values"=>$oAllowedStatuses, "sql"=>"status", "default_value"=>"implementation", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("org_id", array("targetclass"=>"bizOrganization", "label"=>"Organization Id", "description"=>"ID of the object owner organization", "allowed_values"=>new ValueSetObjects("bizOrganization: status Contains 'implementation'", 'name', array('name'=>true)), "sql"=>"org_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning this object", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name")));
+
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("status");
+		MetaModel::Init_AddFilterFromAttribute("org_id");
+		MetaModel::Init_AddFilterFromAttribute("org_name");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'status', 'org_id')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form
+	}
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('name', "<overload in derived class>");
+		$this->Set('status', $oGenerator->GenerateString("enum(implementation,production)"));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Any kind of thing that can be contacted (person, team, hotline...)
+* A contact can:
+*   be linked to any Real Object with a role
+*   be part of a GroupContact
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizContact extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Contact",
+			"description" => "Contact",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "contacts",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department of the contact", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("email", array("label"=>"eMail", "description"=>"Email address", "allowed_values"=>null, "sql"=>"email", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("phone", array("label"=>"Phone", "description"=>"Telephone", "allowed_values"=>null, "sql"=>"telephone", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location Id", "description"=>"Id of the location where the contact is located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location Name", "description"=>"Name of the location where the contact is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("org_name");
+		MetaModel::Init_AddFilterFromAttribute("email");
+		MetaModel::Init_AddFilterFromAttribute("phone");
+		MetaModel::Init_AddFilterFromAttribute("location_id");
+		MetaModel::Init_AddFilterFromAttribute("location_name");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'email', 'location_id', 'phone')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form
+	}
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('name', "<overload in derived classes>");
+		$this->Set('email', "<overload in derived classes>");
+		$this->Set('phone', $oGenerator->GenerateString("enum(+1,+33,+44,+49,+421)| |number(100-999)| |number(000-999)"));
+		$this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() )));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Physical person only  
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizPerson extends bizContact
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Person",
+			"description" => "Person",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "first_name", "name"),  // comment en définir plusieurs
+			// "reconc_keys" => array("org_name", "employe_number"), 
+			"db_table" => "persons",   // Can it use the same physical DB table as any contact ?
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/person.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("first_name", array("label"=>"first Name", "description"=>"First name", "allowed_values"=>null, "sql"=>"first_name", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("employe_number", array("label"=>"Employe Number", "description"=>"employe number", "allowed_values"=>null, "sql"=>"employe_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("first_name");
+		MetaModel::Init_AddFilterFromAttribute("employe_number");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone', 'employe_number')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('first_name', 'name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('first_name', 'name', 'status', 'email', 'location_id', 'phone', 'employe_number')); // Criteria of the advanced search form
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		parent::Generate($oGenerator);
+		$this->Set('name', $oGenerator->GenerateLastName());
+		$this->Set('first_name', $oGenerator->GenerateFirstName());
+		$this->Set('email', $oGenerator->GenerateEmail($this->Get('first_name'), $this->Get('name')));
+		$this->Set('phone', $oGenerator->GenerateString("enum(+1,+33,+44,+49,+421)| |number(100-999)| |number(000-999)"));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A team is basically a contact which is also a group of contacts
+* (and thus a team can contain other teams)
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizTeam extends bizContact
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Team",
+			"description" => "A group of contacts",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "teams",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/team.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+
+		MetaModel::Init_InheritFilters();
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'email', 'location_id', 'phone')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'email', 'location_id', 'phone')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id')); // Criteria of the advanced search form
+	}
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* An electronic document, with version tracking
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizDocument extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Document",
+			"description" => "Document",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "documents",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/document.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalField("org_name", array("label"=>"Organization", "description"=>"Company / Department owning the document", "allowed_values"=>null, "extkey_attcode"=> 'org_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeEnum("scope", array("label"=>"scope", "description"=>"Scope of this document", "allowed_values"=>new ValueSetEnum("organization,hardware support"), "sql"=>"scope", "default_value"=>"organization", "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_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("scope");
+		MetaModel::Init_AddFilterFromAttribute("description");
+
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'scope','description')); // 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
+		// 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
+
+	}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* 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("docname", "version_number"),
+			"db_table" => "document_versions",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/document.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, "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 number", "description"=>"Service name", "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('docname', '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
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkDocumentRealObject extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "DocumentsLinks",
+			"description" => "A link between a document and another object",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "link_type",
+			"state_attcode" => "",
+			"reconc_keys" => array("doc_name", "object_name"),
+			"db_table" => "documents_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("doc_id", array("targetclass"=>"bizDocument", "label"=>"Document Name", "description"=>"id of the Document", "allowed_values"=>null, "sql"=>"doc_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("doc_name", array("label"=>"Document", "description"=>"name of the document", "allowed_values"=>null, "extkey_attcode"=> 'doc_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"object name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link_type", "description"=>"Type of the link", "allowed_values"=>null, "sql"=>"link_type", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("doc_id");
+		MetaModel::Init_AddFilterFromAttribute("doc_name");
+		MetaModel::Init_AddFilterFromAttribute("object_id");
+		MetaModel::Init_AddFilterFromAttribute("object_name");
+		MetaModel::Init_AddFilterFromAttribute("link_type");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('doc_id', 'object_name', 'link_type')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('doc_id', 'object_name', 'link_type')); // Attributes to be displayed for a list
+	}
+}
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Object and a contact
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkContactRealObject extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "ContactsLinks",
+			"description" => "A link between a contact and another object",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "role",
+			"state_attcode" => "",
+			"reconc_keys" => array("contact_name", "object_name"),
+			"db_table" => "contacts_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("contact_id", array("targetclass"=>"bizContact", "label"=>"Contact", "description"=>"The contact", "allowed_values"=>null, "sql"=>"contact_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_name", array("label"=>"Contact name", "description"=>"name of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_phone", array("label"=>"Phone", "description"=>"Phone number of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"phone")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("contact_email", array("label"=>"eMail", "description"=>"eMail address of the contact", "allowed_values"=>null, "extkey_attcode"=> 'contact_id', "target_attcode"=>"email")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("object_id", array("targetclass"=>"logRealObject", "label"=>"object", "description"=>"Object linked", "allowed_values"=>null, "sql"=>"object_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("object_name", array("label"=>"Object name", "description"=>"name of the linked object", "allowed_values"=>null, "extkey_attcode"=> 'object_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("role", array("label"=>"Role", "description"=>"Role of the contact", "allowed_values"=>null, "sql"=>"role", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("contact_id");
+		MetaModel::Init_AddFilterFromAttribute("contact_name");
+		MetaModel::Init_AddFilterFromAttribute("object_id");
+		MetaModel::Init_AddFilterFromAttribute("object_name");
+		MetaModel::Init_AddFilterFromAttribute("role");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('contact_id', 'contact_phone', 'contact_email', 'object_id', 'role')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('contact_id', 'contact_phone', 'contact_email', 'object_id', 'role')); // Attributes to be displayed for a list
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Any Infrastructure object (bizLocation, bizDevice, bizApplication, bizCircuit, bizInterface)
+* An infrastructure object:
+*   can be covered by an OLA
+*   can support the delivery of a Service
+*   can be part of an GroupInfra
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class logInfra extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Infra",
+			"description" => "Infrastructure real object",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "infra",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+    MetaModel::Init_AddAttribute(new AttributeEnum("severity", array("label"=>"Severity", "description"=>"Severity for this infrastructure", "allowed_values"=>new ValueSetEnum("high,medium,low"), "sql"=>"severity", "default_value"=>"low", "is_null_allowed"=>false, "depends_on"=>array())));
+  
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("severity");
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* bizLocation (Region, Country, City, Site, Building, Floor, Room, Rack,...)
+* pourrait être mis en plusieurs sous objects, puisqu'une adresse sur region n'a pas trop de sens
+* 
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizLocation extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Location",
+			"description" => "Any type of location: Region, Country, City, Site, Building, Floor, Room, Rack,...",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "location",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/location.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeText("address", array("label"=>"Address", "description"=>"The postal address of the location", "allowed_values"=>null, "sql"=>"address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("country", array("label"=>"Country", "description"=>"Country of the location", "allowed_values"=>null, "sql"=>"country", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_location_id", array("targetclass"=>"bizLocation", "label"=>"Parent Location", "description"=>"where is the real object physically located", "allowed_values"=>null, "sql"=>"parent_location_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("parent_location_name", array("label"=>"Parent location (Name)", "description"=>"name of the parent location", "allowed_values"=>null, "extkey_attcode"=> 'parent_location_id', "target_attcode"=>"name")));
+
+    //  on veut pouvoir rechercher une location qui soit un descendant (pas obligatoirement direct) d'une Location, on fait comment ?
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("country");
+		MetaModel::Init_AddFilterFromAttribute("address");
+		MetaModel::Init_AddFilterFromAttribute("parent_location_id");
+		MetaModel::Init_AddFilterFromAttribute("parent_location_name");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'address', 'country', 'parent_location_id')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'country')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'country', 'parent_location_name')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'address', 'country', 'parent_location_id', 'org_id')); // Criteria of the advanced search form
+	}
+	
+	public function ComputeValues()
+	{ 
+  /*
+		$this->Set("location_id", $this->GetKey());
+		// Houston, I've got an issue, as this field is calculated, I should reload the object... ?
+		$this->Set("location_name", "abc (to be finalized)");
+  */
+	}
+
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+/*
+		parent::DisplayDetails($oPage);
+
+
+
+		$oSearchFilter = new CMDBSearchFilter('bizServer');
+		$oSearchFilter->AddCondition('location_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Servers");
+			$oPage->p("$count server(s) at this location:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizNetworkDevice');
+		$oSearchFilter->AddCondition('location_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Network Devices");
+			$oPage->p("$count Network Device(s) at this location:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizPC');
+		$oSearchFilter->AddCondition('location_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("PCs");
+			$oPage->p("$count PC(s) at this location:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizPerson');
+		$oSearchFilter->AddCondition('location_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Contacts");
+			$oPage->p("$count person(s) located to this location:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+
+		$oSearchFilter = new CMDBSearchFilter('lnkDocumentRealObject');
+		$oSearchFilter->AddCondition('object_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Details");
+			$oPage->p("$count Document(s) linked to this location:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+*/
+	
+	}
+
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		parent::Generate($oGenerator);
+		$sLastName = $oGenerator->GenerateLastName();
+		$sCityName = $oGenerator->GenerateCityName();
+		$this->Set('name', $sCityName);
+		$this->Set('country', $oGenerator->GenerateCountryName());
+		$this->Set('address', $oGenerator->GenerateString("number(1-999)| |enum(rue,rue,rue,place,avenue,av.,route de)| |$sLastName| |number(0000-9999)|0 |$sCityName"));
+		$this->Set('parent_location_id', 1);
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Circuit (one end only)
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizCircuit extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Circuit",
+			"description" => "Any type of circuit",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "carrier_name", "carrier_ref", "name"), // inherited attributes
+			"db_table" => "circuits",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/Circuits.html",
+		);
+
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"speed", "description"=>"speed of the circuit", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location ID", "description"=>"Id of the location", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location", "description"=>"Name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("interface_id", array("targetclass"=>"bizInterface", "label"=>"Interface Id", "description"=>"id of the interface", "allowed_values"=>null, "sql"=>"interface_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface_name", array("label"=>"Interface", "description"=>"Name of the interface", "allowed_values"=>null, "extkey_attcode"=> 'interface_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("provider_id", array("targetclass"=>"bizOrganization", "label"=>"Carrier ID", "description"=>"Organization ID of the provider of the Circuit", "allowed_values"=>null, "sql"=>"provider_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("carrier_name", array("label"=>"Carrier", "description"=>"Name of the carrier", "allowed_values"=>null, "extkey_attcode"=> 'provider_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("carrier_ref", array("label"=>"Carrier reference", "description"=>"reference of the circuit used by the carrier", "allowed_values"=>null, "sql"=>"carrier_ref", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("speed");
+		MetaModel::Init_AddFilterFromAttribute("location_id");
+		MetaModel::Init_AddFilterFromAttribute("location_name");
+		MetaModel::Init_AddFilterFromAttribute("interface_id");
+		MetaModel::Init_AddFilterFromAttribute("provider_id");
+		MetaModel::Init_AddFilterFromAttribute("carrier_ref");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'speed', 'provider_id', 'carrier_ref', 'location_id')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'provider_id', 'carrier_ref', 'speed')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'carrier_ref', 'speed', 'provider_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'carrier_ref', 'speed', 'provider_id')); // Criteria of the advanced search form
+	}
+	
+	public function ComputeValues()
+	{
+/*
+		$oLocatedObject = MetaModel::GetObject("Located Object", $this->Get("located_object_id"));
+
+		$this->Set("location_id", $oLocatedObject->Get("location_id"));
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("location_name", "abc (to be finalized)");
+
+		$this->Set("device_id", $oLocatedObject->Get("device_id"));
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("device_name", "abc (to be finalized)");
+
+		$this->Set("interface_id", $oLocatedObject->Get("interface_id"));
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("interface_name", "abc (to be finalized)");
+*/
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Any Device Network Interface 
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizInterface extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Interface",
+			"description" => "Interface",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "device_name", "name"),
+			"db_table" => "interfaces",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/interface.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "label"=>"Device", "description"=>"Device on which the interface is physically located", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device", "description"=>"name of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_id", array("label"=>"Device location", "description"=>"location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_id")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("device_location_name", array("label"=>"Device location", "description"=>"name of the location of the device on which the interface is located", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"location_name")));
+
+		MetaModel::Init_AddAttribute(new AttributeEnum("logical_type", array("label"=>"Logical type", "description"=>"Logical type of interface", "allowed_values"=>new ValueSetEnum("primary,secondary,backup,port,logical"), "sql"=>"logical_type", "default_value"=>"port", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("physical_type", array("label"=>"Physical type", "description"=>"Physical type of interface", "allowed_values"=>new ValueSetEnum("ethernet,framerelay,atm,vlan"), "sql"=>"physical_type", "default_value"=>"ethernet", "is_null_allowed"=>false, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("label"=>"IP address", "description"=>"address IP for this interface", "allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("mask", array("label"=>"Subnet Mask", "description"=>"Subnet mask for this interface", "allowed_values"=>null, "sql"=>"mask", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("mac", array("label"=>"MAC address", "description"=>"MAC address for this interface", "allowed_values"=>null, "sql"=>"mac", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeString("speed", array("label"=>"Speed (Kb/s)", "description"=>"speed of this interface", "allowed_values"=>null, "sql"=>"speed", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+	  MetaModel::Init_AddAttribute(new AttributeEnum("duplex", array("label"=>"Duplex", "description"=>"Duplex configured for this interface", "allowed_values"=>new ValueSetEnum("half,full,unknown"), "sql"=>"duplex", "default_value"=>"unknown", "is_null_allowed"=>true, "depends_on"=>array())));
+	
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("device_id");
+		MetaModel::Init_AddFilterFromAttribute("device_name");
+		MetaModel::Init_AddFilterFromAttribute("device_location_id");
+		MetaModel::Init_AddFilterFromAttribute("logical_type");
+		MetaModel::Init_AddFilterFromAttribute("physical_type");
+    MetaModel::Init_AddFilterFromAttribute("ip_address");
+    MetaModel::Init_AddFilterFromAttribute("mac");		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'device_id', 'device_location_id','severity','logical_type','physical_type','ip_address','mask','mac','speed','duplex')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'device_id','severity')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'ip_address','mac','device_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'device_id', 'org_id')); // Criteria of the advanced search form
+	}
+
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+    /*
+		$oSearchFilter = new CMDBSearchFilter('lnkInterfaces');
+		$oSearchFilter->AddCondition('interface1_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Connected interfaces");
+			$oPage->p("$count interface(s) connected to this device:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+	*/
+	}
+
+	public function ComputeValues()
+	{
+	/*
+		// my location is the location of my device
+		$oDevice = MetaModel::GetObject("bizDevice", $this->Get("device_id"));
+		$this->Set("location_id", $oDevice->Get("location_id"));
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("location_name", "abc (to be finalized)");
+
+		// my device is given by my Creator
+
+		// my interface is myself
+		$this->Set("interface_id", $this->GetKey());
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("interface_name", "abc (to be finalized)");
+	*/
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Interfaces
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInterfaces extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "InterfacesLinks",
+			"description" => "A link between two interfaces",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "link_type",
+			"state_attcode" => "",
+			"reconc_keys" => array("interface1_id", "interface2_id"),
+			"db_table" => "interfaces_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("interface1_id", array("targetclass"=>"bizInterface", "label"=>"Interface1", "description"=>"The interface1", "sql"=>"interface1_id", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_name", array("label"=>"Interface1 name", "description"=>"name of the interface1", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("interface2_id", array("targetclass"=>"bizInterface", "label"=>"Interface2", "description"=>"The interface2", "sql"=>"interface2_id", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_name", array("label"=>"Interface2 name", "description"=>"name of the interface2", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_id", array("label"=>"Device1", "description"=>"device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_id")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface1_device_name", array("label"=>"Device name", "description"=>"name of the device", "extkey_attcode"=> 'interface1_id', "allowed_values"=> null, "target_attcode"=>"device_name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_id", array("label"=>"Device2", "description"=>"device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_id")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("interface2_device_name", array("label"=>"Device name", "description"=>"name of the device", "extkey_attcode"=> 'interface2_id', "allowed_values"=> null, "target_attcode"=>"device_name")));
+		MetaModel::Init_AddAttribute(new AttributeString("link_type", array("label"=>"link type", "description"=>" Definition of the link", "sql"=>"link_type", "default_value"=>"", "allowed_values"=> null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("interface1_id");
+		MetaModel::Init_AddFilterFromAttribute("interface1_name");
+		MetaModel::Init_AddFilterFromAttribute("interface2_id");
+		MetaModel::Init_AddFilterFromAttribute("interface2_name");
+		MetaModel::Init_AddFilterFromAttribute("interface2_device_name");
+		MetaModel::Init_AddFilterFromAttribute("link_type");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('interface1_id', 'interface2_id', 'link_type')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('interface1_id', 'interface1_device_id', 'interface2_id', 'interface2_device_id', 'link_type')); // Attributes to be displayed for the complete details
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* Any electronic device
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizDevice extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Device",
+			"description" => "Electronic devices",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "devices",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("location_id", array("targetclass"=>"bizLocation", "label"=>"Location", "description"=>"where is the located object physically located", "allowed_values"=>null, "sql"=>"location_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("location_name", array("label"=>"Location name", "description"=>"name of the location", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("country", array("label"=>"Country", "description"=>"country where the device is located", "allowed_values"=>null, "extkey_attcode"=> 'location_id', "target_attcode"=>"country")));
+		MetaModel::Init_AddAttribute(new AttributeString("brand", array("label"=>"Brand", "description"=>"The manufacturer of the device", "allowed_values"=>null, "sql"=>"brand", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("model", array("label"=>"Model", "description"=>"The model number of the device", "allowed_values"=>null, "sql"=>"model", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("serial_number", array("label"=>"Serial Number", "description"=>"The serial number of the device", "allowed_values"=>null, "sql"=>"serial_number", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("location_id");
+		MetaModel::Init_AddFilterFromAttribute("country");
+		MetaModel::Init_AddFilterFromAttribute("brand");
+		MetaModel::Init_AddFilterFromAttribute("model");
+		MetaModel::Init_AddFilterFromAttribute("serial_number");
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		switch ($sRelCode)
+		{
+		case "impacts":
+			$aRels = array(
+				"connected device" => array("sQuery"=>"bizDevice: PKEY IS device_id IN (bizInterface: PKEY IS interface2_id IN (lnkInterfaces: interface1_id IN (bizInterface: device_id = \$[this.pkey::])))", "bPropagate"=>true, "iDistance"=>3),
+				"hosted app" => array("sQuery"=>"bizApplication: infra_id = \$[this.pkey::]", "bPropagate"=>true, "iDistance"=>3),
+			);
+			return array_merge($aRels, parent::GetRelationQueries($sRelCode));
+		}
+	}
+
+	public function ComputeValues()
+	{
+	/*
+		// my location is the location of my device (external field)
+		$this->Set("location_id", $this->Get("device_location_id"));
+		// Houston, I've got an issue, as this field is calculated, I should reload the object...
+		$this->Set("location_name", "abc (to be finalized)");
+
+		// my device is myself
+		$this->Set("device_id", $this->GetKey());
+
+		// my interface is "nothing"
+		$this->Set("interface_id", null);
+	*/
+  }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A personal computer
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizPC extends bizDevice
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "PC",
+			"description" => "Personal Computers",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "pcs",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/pc.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of computer", "allowed_values"=>new ValueSetEnum("desktop PC,laptop"), "sql"=>"type", "default_value"=>"desktop PC", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"CPU", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment number", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("memory_size");
+		MetaModel::Init_AddFilterFromAttribute("cpu");
+		MetaModel::Init_AddFilterFromAttribute("hdd_size");
+		MetaModel::Init_AddFilterFromAttribute("os_family");
+		MetaModel::Init_AddFilterFromAttribute("os_version");
+		MetaModel::Init_AddFilterFromAttribute("mgmt_ip");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status','severity', 'org_id', 'location_id', 'brand', 'model','os_family','os_version','mgmt_ip','default_gateway','shipment_number','serial_number', 'type', 'cpu', 'memory_size', 'hdd_size')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'severity', 'org_id', 'location_id', 'brand', 'model', 'type')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'severity','type', 'brand', 'model','os_family','mgmt_ip')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'brand', 'model', 'cpu', 'memory_size', 'hdd_size')); // Criteria of the advanced search form
+	}
+
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+		/*
+		parent::DisplayDetails($oPage);
+		$oSearchFilter = new CMDBSearchFilter('lnkContactRealObject');
+		$oSearchFilter->AddCondition('object_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Contacts");
+			$oPage->p("$count contact(s) linked to this PC:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizInterface');
+		$oSearchFilter->AddCondition('device_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Interfaces");
+			$oPage->p("$count interface(s) for this device:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		*/
+	}
+
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('name', $oGenerator->GenerateString("enum(pc,pc,pc,pc,pc,win,redhat,linux,srv,workstation)|number(000-999)|.|domain()"));
+		$this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Dell,Compaq,Siemens,Packard Bell,IBM,Gateway,Medion,Sony)"));
+		$this->Set('model', $oGenerator->GenerateString("enum(Vectra,Deskpro,Dimension,Optiplex,Latitude,Precision,Vaio)"));
+		$this->Set('serial_number', $oGenerator->GenerateString("enum(FR,US,TW,CH)|number(000000-999999)"));
+		$this->Set('memory_size', $oGenerator->GenerateString("enum(128,256,384,512,768,1024,1536,2048)"));
+		$this->Set('cpu', $oGenerator->GenerateString("enum(Pentium III,Pentium 4, Pentium M,Core Duo,Core 2 Duo,Celeron,Opteron,Thurion,Athlon)"));
+		$this->Set('hdd_size', $oGenerator->GenerateString("enum(40,60,80,120,200,300)"));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A server
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizServer extends bizDevice
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Server",
+			"description" => "Computer Servers",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "status",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "servers",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/server.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"Status of the server", "allowed_values"=>new ValueSetEnum("In Store,Shipped,Plugged,Production Candidate,In Production,In Change,Being Deconfigured,Obsolete"), "sql"=>"status", "default_value"=>"In Store", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("memory_size", array("label"=>"Memory Size", "description"=>"Size of the memory", "allowed_values"=>null, "sql"=>"memory_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("cpu", array("label"=>"Model", "description"=>"CPU type", "allowed_values"=>null, "sql"=>"cpu_type", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("number_of_cpus", array("label"=>"Number of CPUs", "description"=>"Number of CPUs", "allowed_values"=>null, "sql"=>"number_of_cpus", "default_value"=>"1", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("hdd_size", array("label"=>"HDD Size", "description"=>"Size of the hard drive", "allowed_values"=>null, "sql"=>"hdd_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("hdd_free_size", array("label"=>"Free HDD Size", "description"=>"Size of the free space on the hard drive(s)", "allowed_values"=>null, "sql"=>"hdd_free_size", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("os_family", array("label"=>"OS Family", "description"=>"Type of operating system", "allowed_values"=>null, "sql"=>"os_family", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("os_version", array("label"=>"OS Version", "description"=>"Detailed version number of the operating system", "allowed_values"=>null, "sql"=>"os_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("shipment_number", array("label"=>"Shipment number", "description"=>"Number for tracking shipment", "allowed_values"=>null, "sql"=>"shipment_number", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("mgmt_ip", array("label"=>"Mgmt IP", "description"=>"Management IP", "allowed_values"=>null, "sql"=>"mgmt_ip", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("memory_size");
+		MetaModel::Init_AddFilterFromAttribute("cpu");
+		MetaModel::Init_AddFilterFromAttribute("number_of_cpus");
+		MetaModel::Init_AddFilterFromAttribute("hdd_size");
+		MetaModel::Init_AddFilterFromAttribute("hdd_free_size");
+		MetaModel::Init_AddFilterFromAttribute("os_family");
+		MetaModel::Init_AddFilterFromAttribute("os_version");
+	
+ 
+		// Life cycle
+		MetaModel::Init_DefineState("In Store", array("label"=>"InStore", "description"=>"Device in store", "attribute_inherit"=>null,
+												 "attribute_list"=>array()));
+		MetaModel::Init_DefineState("Shipped", array("label"=>"Shipped", "description"=>"The device had been shipped to future location", "attribute_inherit"=>null,
+												"attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"serial_number"=>OPT_ATT_MANDATORY,"shipment_number"=>OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("Plugged", array("label"=>"Plugged", "description"=>"The device is connected to the network", "attribute_inherit"=>null,
+													"attribute_list"=>array("location_id"=>OPT_ATT_MANDATORY,"mgmt_ip"=>OPT_ATT_MANDATORY,"name"=>OPT_ATT_MANDATORY)));
+		MetaModel::Init_DefineState("Production Candidate", array("label"=>"Pre-Production", "description"=>"The device is ready to be move to production", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("In Production", array("label"=>"Production", "description"=>"The device is on production", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("In Change", array("label"=>"InChange", "description"=>"A change is being performed on the device", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Being Deconfigured", array("label"=>"BeingDeconfigured", "description"=>"The device is about to be removed from is current location", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+		MetaModel::Init_DefineState("Obsolete", array("label"=>"Obsolete", "description"=>"The device is no more used", "attribute_inherit"=>null,
+												"attribute_list"=>array()));
+
+
+		MetaModel::Init_DefineStimulus("ev_store", new StimulusUserAction(array("label"=>"Store this server", "description"=>"This server is move to storage")));
+		MetaModel::Init_DefineStimulus("ev_ship", new StimulusUserAction(array("label"=>"Ship this server", "description"=>"This server is shipped to futur location")));
+		MetaModel::Init_DefineStimulus("ev_plug", new StimulusUserAction(array("label"=>"Plug this server", "description"=>"The server is pluuged on the network")));
+		MetaModel::Init_DefineStimulus("ev_configuration_finished", new StimulusUserAction(array("label"=>"Configuration finished", "description"=>"The device is ready to move to production evaluation")));
+		MetaModel::Init_DefineStimulus("ev_val_failed", new StimulusUserAction(array("label"=>"Review configuration", "description"=>"The configuration for this server is not completed")));
+		MetaModel::Init_DefineStimulus("ev_mtp", new StimulusUserAction(array("label"=>"Move to Production", "description"=>"The server is moved to production")));
+		MetaModel::Init_DefineStimulus("ev_start_change", new StimulusUserAction(array("label"=>"Change Start [No Click]", "description"=>"A change starts for this server")));
+		MetaModel::Init_DefineStimulus("ev_end_change", new StimulusUserAction(array("label"=>"End Change [No Click]", "description"=>"No more change running for this server")));
+		MetaModel::Init_DefineStimulus("ev_decommission", new StimulusUserAction(array("label"=>"Decommission", "description"=>"The server is being decommissioned")));
+		MetaModel::Init_DefineStimulus("ev_obsolete", new StimulusUserAction(array("label"=>"Obsolete", "description"=>"The server is no more used")));
+		MetaModel::Init_DefineStimulus("ev_recycle", new StimulusUserAction(array("label"=>"Recycle this server", "description"=>"The server is move back to deconfiguration")));
+
+		MetaModel::Init_DefineTransition("In Store", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("In Store", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Shipped", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Shipped", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Plugged", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Plugged", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Plugged", "ev_configuration_finished", array("target_state"=>"Production Candidate", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Production Candidate", "ev_val_failed", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Production Candidate", "ev_mtp", array("target_state"=>"In Production", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("In Production", "ev_start_change", array("target_state"=>"In Change", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("In Production", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("In Production", "ev_decommission", array("target_state"=>"Being Deconfigured", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("In Change", "ev_end_change", array("target_state"=>"In Production", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Being Deconfigured", "ev_ship", array("target_state"=>"Shipped", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Being Deconfigured", "ev_plug", array("target_state"=>"Plugged", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Being Deconfigured", "ev_store", array("target_state"=>"In Store", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Being Deconfigured", "ev_obsolete", array("target_state"=>"Obsolete", "actions"=>array(), "user_restriction"=>null));
+		MetaModel::Init_DefineTransition("Obsolete", "ev_recycle", array("target_state"=>"Being Deconfigured", "actions"=>array(), "user_restriction"=>null));
+		
+
+
+		
+	
+		// Display lists
+
+  		MetaModel::Init_SetZListItems('details', array('name', 'mgmt_ip','default_gateway','status', 'severity','org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version','serial_number','shipment_number', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status','severity', 'org_id', 'location_id', 'brand', 'model', 'os_family', 'os_version')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status','severity', 'brand', 'model', 'os_family', 'location_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status','brand', 'model', 'os_family', 'os_version', 'location_id', 'cpu', 'number_of_cpus', 'memory_size', 'hdd_size', 'hdd_free_size')); // Criteria of the advanced search form
+	}
+	
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+		/*
+		parent::DisplayDetails($oPage);
+		$oSearchFilter = new CMDBSearchFilter('lnkContactRealObject');
+		$oSearchFilter->AddCondition('object_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Contacts");
+			$oPage->p("$count contact(s) for this server:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizInterface');
+		$oSearchFilter->AddCondition('device_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Interfaces");
+			$oPage->p("$count interface(s) for this server:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('Application');
+		$oSearchFilter->AddCondition('infra_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Installed applications");
+			$oPage->p("$count application(s) installed on this server:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('bizPatch');
+		$oSearchFilter->AddCondition('infra_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+			$oPage->SetCurrentTab("Installed patches");
+			$oPage->p("$count patch(s) installed on this server:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		*/
+
+
+	}
+
+
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('name', $oGenerator->GenerateString("enum(pc,pc,pc,pc,pc,win,redhat,linux,srv,workstation)|number(000-999)|.|domain()"));
+		$this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Dell,Compaq,Siemens,Packard Bell,IBM,Gateway,Medion,Sony)"));
+		$this->Set('model', $oGenerator->GenerateString("enum(Vectra,Deskpro,Dimension,Optiplex,Latitude,Precision,Vaio)"));
+		$this->Set('serial_number', $oGenerator->GenerateString("enum(FR,US,TW,CH)|number(000000-999999)"));
+		$this->Set('memory_size', $oGenerator->GenerateString("enum(512,1024,2048,4096,2048,4096,8192,8192,8192,16384,32768)"));
+		$this->Set('cpu', $oGenerator->GenerateString("enum(Pentium III,Pentium 4,Pentium M,Core Duo,Core 2 Duo,Celeron,Opteron,Thurion,Athlon)"));
+		$this->Set('number_of_cpu', $oGenerator->GenerateString("enum(1,1,2,2,2,2,2,4,4,8)"));
+		$this->Set('hdd_size', $oGenerator->GenerateString("enum(500,1024,500,1024,500,1024,2048)"));
+		$this->Set('hdd_free_size', $this->Get('hdd_size')*$oGenerator->GenerateString("number(20-80)"));
+		$this->Set('os_family', $oGenerator->GenerateString("enum(Windows,Windows,Windows,Linux,Windows,Linux,Windows,Linux,Linux,HP-UX,Solaris,AIX)"));
+		$this->Set('os_version', $oGenerator->GenerateString("enum(XP,XP,XP,RH EL 4,RH EL 5,SuSE 10.3,SuSE 10.4,11.11,11.11i)"));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A network device
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizNetworkDevice extends bizDevice
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Network Device",
+			"description" => "A network device",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "network_devices",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/network.device.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of device", "allowed_values"=>new ValueSetEnum("switch,router,firewall,load balancer,hub,WAN accelerator"), "sql"=>"type", "default_value"=>"switch", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("ip_address", array("label"=>"Mgmt IP", "description"=>"Management IP address", "allowed_values"=>null, "sql"=>"ip_address", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("default_gateway", array("label"=>"Default Gateway", "description"=>"Default Gateway for this device", "allowed_values"=>null, "sql"=>"default_gateway", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("ios_version", array("label"=>"IOS version", "description"=>"IOS (software) version", "allowed_values"=>null, "sql"=>"ios_version", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("memory", array("label"=>"Memory", "description"=>"Memory description", "allowed_values"=>null, "sql"=>"memory", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeString("snmp_read", array("label"=>"SNMP Community (Read)", "description"=>"SNMP Read Community String", "allowed_values"=>null, "sql"=>"snmp_read", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("snmp_write", array("label"=>"SNMP Community (Write)", "description"=>"SNMP Write Community String", "allowed_values"=>null, "sql"=>"snmp_write", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("ip_address");
+		MetaModel::Init_AddFilterFromAttribute("ios_version");
+
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status','severity','org_id', 'location_id', 'brand','model','type','ip_address','default_gateway','serial_number','ios_version','memory','snmp_read','snmp_write')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status','brand','model','type','ip_address')); // Attributes to be displayed for a list
+		
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'location_id', 'brand','model','type','ip_address')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'org_id', 'location_id', 'brand','model','type','ip_address','serial_number','ios_version','snmp_read','snmp_write')); // Criteria of the advanced search form
+
+
+	}
+
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('location_id', $oGenerator->GenerateKey("bizLocation", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('name', $oGenerator->GenerateString("enum(sw,swi,switch,rout,rtr,gw)|number(000-999)|.|domain()"));
+		$this->Set('brand', $oGenerator->GenerateString("enum(Hewlett-Packard,Cisco,3Com,Avaya,Alcatel,Cabletron,Extrem Networks,Juniper,Netgear,Synopitcs,Xylan)"));
+		$this->Set('model', $oGenerator->GenerateString("enum(Procurve ,Catalyst ,Multiswitch ,C)|enum(25,26,36,40,65)|enum(00,09,10,50)"));
+		$this->Set('serial_number', $oGenerator->GenerateString("enum(FAA,AGA,PAD,COB,DFE)|number(0000-9999)|enum(M,X,L)"));
+		$this->Set('ip_address', $oGenerator->GenerateString("number(10-248)|.|number(1-254)|.|number(1-254)|.|number(1-254)"));
+		$this->Set('ios_version', $oGenerator->GenerateString("enum(9,10,12)|.|enum(0,1,2)|enum(,,,,XP,.5.1)"));
+		$this->Set('snmp_read', $oGenerator->GenerateString("enum(Ew,+0,**,Ps)|number(00-99)|enum(+,=,],;, )|enum(Aze,Vbn,Bbn,+9+,-9-,#)"));
+		$this->Set('snmp_write', $oGenerator->GenerateString("enum(M3,l3,$,*,Zz,Ks,jh)|number(00-99)|enum(A*e,V%n,Bbn,+,-,#)|number(0-9)"));
+	}
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* A "Solution"
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class bizInfraGroup extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Infra Group",
+			"description" => "A group of infrastructure elements",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("org_name", "name"), // inherited attributes
+			"db_table" => "infra_group",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/group.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeEnum("type", array("label"=>"Type", "description"=>"Type of groupe", "allowed_values"=>new ValueSetEnum("Monitoring,Reporting,list"), "sql"=>"type", "default_value"=>"list", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"usage of the Group", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_group_id", array("targetclass"=>"bizInfraGroup", "label"=>"Parent Group", "description"=>"including group", "allowed_values"=>null, "sql"=>"parent_group_id", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("parent_group_name", array("label"=>"Parent Group (Name)", "description"=>"name of the parent group", "allowed_values"=>null, "extkey_attcode"=> 'parent_group_id', "target_attcode"=>"name")));
+
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("type");
+		MetaModel::Init_AddFilterFromAttribute("parent_group_id");
+		MetaModel::Init_AddFilterFromAttribute("parent_group_name");
+
+
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name', 'status', 'org_id', 'type', 'description','parent_group_id')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'org_id', 'type', 'description')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'status', 'type')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'status', 'type', 'description', 'org_id')); // Criteria of the advanced search form
+	}
+
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+	/*
+  	$oSearchFilter = new CMDBSearchFilter('lnkInfraGrouping');
+		$oSearchFilter->AddCondition('infra_group_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+   			$oPage->SetCurrentTab("RelatedInfrastructure");
+			$oPage->p("Infrastructure Link to this group:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+		$oSearchFilter = new CMDBSearchFilter('lnkContactRealObject');
+		$oSearchFilter->AddCondition('object_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+   			$oPage->SetCurrentTab("TeamLinks");
+			$oPage->p("People concerned by this group:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+*/
+	}
+
+
+
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('org_id', $oGenerator->GetOrganizationId());
+		$this->Set('name', $oGenerator->GenerateString("enum(ov_nnm_,ovpi_,vitalnet_,datacenter_,web_farm_)|number(000-999)"));
+		$this->Set('type', $oGenerator->GenerateString("enum(Application,Infrastructure)"));
+	}	
+}
+////////////////////////////////////////////////////////////////////////////////////
+//**
+//* An application is an instance of a software install on a PC or Server
+//* 
+////////////////////////////////////////////////////////////////////////////////////
+class bizApplication extends logInfra
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Application",
+			"description" => "General application",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("device_name", "name"), // inherited attributes
+			"db_table" => "applications",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/application.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Hosting device", "description"=>"The device where application is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Hosting device", "description"=>"Name of the device where application is installed", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installed date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddAttribute(new AttributeString("version", array("label"=>"Version", "description"=>"Application version", "allowed_values"=>null, "sql"=>"version", "default_value"=>"undefined", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("function", array("label"=>"Function", "description"=>"Function provided by this application", "allowed_values"=>null, "sql"=>"function", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("function");
+		MetaModel::Init_AddFilterFromAttribute("version");
+		MetaModel::Init_AddFilterFromAttribute("device_id");
+
+		
+		
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name','device_id','org_id','status','install_date', 'version','function')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name','device_id', 'version', 'function')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'device_id','version','function')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'device_id','version','function')); // Criteria of the advanced search form
+
+	}
+
+	public static function GetRelationQueries($sRelCode)
+	{
+		switch ($sRelCode)
+		{
+		case "impacts":
+			$aRels = array(
+				"client app" => array("sQuery"=>"bizApplication: PKEY IS client_id IN (lnkClientServer: server_id = \$[this.pkey::])", "bPropagate"=>true, "iDistance"=>3),
+			);
+			return array_merge($aRels, parent::GetRelationQueries($sRelCode));
+		}
+	}
+
+	function DisplayDetails(web_page $oPage)
+	{
+		parent::DisplayDetails($oPage);
+	/*
+  	$oSearchFilter = new CMDBSearchFilter('lnkClientServer');
+		$oSearchFilter->AddCondition('server_id', $this->GetKey(), '=');
+		$oSet = new CMDBObjectSet($oSearchFilter);
+		$count = $oSet->Count();
+		if ($count > 0)
+		{
+   			$oPage->SetCurrentTab("Connected clients");
+			$oPage->p("Client applications impacted when down:");
+			$this->DisplaySet($oPage, $oSet);
+		}
+*/
+	}
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between any Infra and a Group
+*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkInfraGrouping extends cmdbAbstractObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Infra Grouping",
+			"description" => "Infra part of an Infra Group",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "impact", 
+			"state_attcode" => "",
+			"reconc_keys" => array(""),
+			"db_table" => "infra_grouping",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_id", array("targetclass"=>"logInfra", "jointype"=> '', "label"=>"Infrastructure", "description"=>"Infrastructure part of the group", "allowed_values"=>null, "sql"=>"infra_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_name", array("label"=>"Infrastructure name", "description"=>"Name of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("infra_status", array("label"=>"Status", "description"=>"Status of the impacted infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_id', "target_attcode"=>"status")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("infra_group_id", array("targetclass"=>"bizInfraGroup", "jointype"=> '', "label"=>"Group Name", "description"=>"Name of the group", "allowed_values"=>null, "sql"=>"infra_group_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("group_name", array("label"=>"Group name", "description"=>"Name of the group containing infrastructure", "allowed_values"=>null, "extkey_attcode"=> 'infra_group_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("impact", array("label"=>"Relation", "description"=>"Relation between this group and infra", "allowed_values"=>null, "sql"=>"impact", "default_value"=>"none", "is_null_allowed"=>true, "depends_on"=>array())));
+    // impact should modelized: enum (eg: if the group si dead when infra is dead)
+
+		MetaModel::Init_AddFilterFromAttribute("infra_id");
+		MetaModel::Init_AddFilterFromAttribute("infra_group_id");
+		MetaModel::Init_AddFilterFromAttribute("impact");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('infra_id','infra_status', 'impact', 'infra_group_id')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('infra_id','infra_status', 'impact', 'infra_group_id')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('infra_id', 'infra_group_id', 'impact')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('infra_id', 'infra_group_id', 'impact')); // Criteria of the advanced search form
+	}
+	
+	public function Generate(cmdbDataGenerator $oGenerator)
+	{
+		$this->Set('infra_id', $oGenerator->GenerateKey("logInfra", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('infra_group_id', $oGenerator->GenerateKey("bizInfraGroup", array('org_id' =>$oGenerator->GetOrganizationId() )));
+		$this->Set('impact', $oGenerator->GenerateString("enum(none,mandatory,partial)"));
+	}
+
+}
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////////
+/**
+* n-n link between two applications, one is the server side and the scond one the client*/
+////////////////////////////////////////////////////////////////////////////////////
+class lnkClientServer extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "ClientServerLinks",
+			"description" => "Link between client server application",
+			"key_type" => "autoincrement",
+			"key_label" => "link_id",
+			"name_attcode" => "relation",  // ????
+			"state_attcode" => "",
+			"reconc_keys" => array("relation"),  // ????
+			"db_table" => "clientserver_links",
+			"db_key_field" => "link_id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("client_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Client", "description"=>"The client part of the link", "allowed_values"=>null, "sql"=>"client_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("client_name", array("label"=>"Client", "description"=>"Name of the client", "allowed_values"=>null, "extkey_attcode"=> 'client_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("server_id", array("targetclass"=>"bizApplication", "jointype"=> '', "label"=>"Server", "description"=>"the server part of the link", "allowed_values"=>null, "sql"=>"server_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("server_name", array("label"=>"Server", "description"=>"Name of the server", "allowed_values"=>null, "extkey_attcode"=> 'server_id', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeString("relation", array("label"=>"Relation", "description"=>"Type of relation between both application", "allowed_values"=>null, "sql"=>"relation", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("client_id");
+		MetaModel::Init_AddFilterFromAttribute("server_id");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('client_id', 'server_id', 'relation')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('list', array('client_id', 'server_id', 'relation')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('client_id', 'server_id')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('client_id', 'server_id')); // Criteria of the advanced search form
+	}
+
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//**
+//* A patch is an application or OS fixe for an infrastructure
+//* 
+////////////////////////////////////////////////////////////////////////////////////
+class bizPatch extends logRealObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "bizmodel,searchable",
+			"name" => "Patch",
+			"description" => "Patch installed on infrastucture",
+			"key_type" => "",
+			"key_label" => "id",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array("device_name", "name"), // inherited attributes
+			"db_table" => "patches",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+			"display_template" => "../business/templates/default.html",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("device_id", array("targetclass"=>"bizDevice", "jointype"=> '', "label"=>"Device", "description"=>"The Device where patch is installed", "allowed_values"=>null, "sql"=>"device_id", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("device_name", array("label"=>"Device name", "description"=>"Name of the impacted device", "allowed_values"=>null, "extkey_attcode"=> 'device_id', "target_attcode"=>"name")));
+   	MetaModel::Init_AddAttribute(new AttributeDate("install_date", array("label"=>"Installed date", "description"=>"Date when application was installed", "allowed_values"=>null, "sql"=>"install_date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		
+		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"description du patch", "allowed_values"=>null, "sql"=>"description", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("patch_type", array("label"=>"Type", "description"=>"type de patch", "allowed_values"=>new ValueSetEnum("OS,Application"), "sql"=>"patch_type", "default_value"=>"OS", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+
+		MetaModel::Init_AddFilterFromAttribute("patch_type");
+		MetaModel::Init_AddFilterFromAttribute("device_id");
+
+		
+		
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('name','device_id', 'install_date', 'patch_type','description')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name','device_id', 'patch_type','install_date')); // Attributes to be displayed for a list
+		// Search criteria
+		MetaModel::Init_SetZListItems('standard_search', array('name', 'device_id','patch_type')); // Criteria of the std search form
+		MetaModel::Init_SetZListItems('advanced_search', array('name', 'device_id','patch_type')); // Criteria of the advanced search form
+
+	}
+}
+
+/*** Insert here all modules requires for ITOP application  ***/
+
+require_once('incident.business.php');
+require_once('ServiceMgmt.business.php');
+require_once('ChangeMgmt.php');
+require_once('KEDB.php')
+?>

+ 12 - 0
business/templates/Circuits.html

@@ -0,0 +1,12 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/WanLinks.jpg" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Interfaces">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizInterface: PKEY IS interface_id IN (bizCircuit: pkey = $pkey$)</itopblock>
+	</itoptab>
+</itoptabs>

+ 19 - 0
business/templates/application.html

@@ -0,0 +1,19 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/software.jpg" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizApplication: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Impacted_Client_Application">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkClientServer: server_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Application_Server">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkClientServer: client_id = $pkey$</itopblock>
+	</itoptab>
+ 	<itoptab name="Contracts">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraContract: infra_id = $pkey$</itopblock>
+	</itoptab>
+
+</itoptabs>

+ 17 - 0
business/templates/change.html

@@ -0,0 +1,17 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/imageChange.gif" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Impacted_Infrastructure">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraChangeTicket: ticket_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contacts_to_Notify">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactChange: change_id = $pkey$</itopblock>
+	</itoptab>
+
+</itoptabs>
+<br style="clear:both"/>

+ 18 - 0
business/templates/contract.html

@@ -0,0 +1,18 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/erwanContracts.jpg" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizContract: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Covered_Infrastructures">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraContract: contract_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contact">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactContract: contract_id = $pkey$</itopblock>
+	</itoptab>	
+	<itoptab name="Document">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkDocumentContract: contract_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 8 - 0
business/templates/default.html

@@ -0,0 +1,8 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/tar.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+

+ 13 - 0
business/templates/document.html

@@ -0,0 +1,13 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</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/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Version">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizDocVersion: document = $pkey$</itopblock>
+	</itoptab>
+
+</itoptabs>

+ 16 - 0
business/templates/group.html

@@ -0,0 +1,16 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/tar.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizInfraGroup: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Infrastructures">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraGrouping: infra_group_id = $pkey$</itopblock>
+	</itoptab>
+		<itoptab name="Contacts">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+	
+</itoptabs>

+ 14 - 0
business/templates/interface.html

@@ -0,0 +1,14 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/tar.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizInterface: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Linked_interfaces">
+		<itopblock blockclass="DisplayBlock" type="list" linkage="interface1_id" encoding="text/sibusql">lnkInterfaces: interface1_id = $pkey$</itopblock>
+		<itopblock blockclass="DisplayBlock" type="list" linkage="interface2_id" encoding="text/sibusql">lnkInterfaces: interface2_id = $pkey$</itopblock>
+	</itoptab>
+	
+</itoptabs>

+ 11 - 0
business/templates/knownError.html

@@ -0,0 +1,11 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizKnownError: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Related_infra">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraError: error_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 25 - 0
business/templates/location.html

@@ -0,0 +1,25 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/starthere.png" style="margin-top:-20px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizLocation: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Contacts">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizContact: location_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Servers">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizServer: location_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="PCs">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizPC: location_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Network_Devices">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizNetworkDevice: location_id = $pkey$</itopblock>
+	</itoptab>
+
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkDocumentRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 25 - 0
business/templates/network.device.html

@@ -0,0 +1,25 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizNetworkDevice: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Interfaces">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizInterface: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contacts">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Incidents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Changes">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkDocumentRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>
+

+ 27 - 0
business/templates/pc.html

@@ -0,0 +1,27 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/laptop_pcmcia.png" style="margin-top:-20px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizPC: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Installed_Application">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizApplication: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Installed_Patches">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizPatch: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contacts">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Interfaces">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizInterface: device_id = $pkey$</itopblock>
+	</itoptab>
+		<itoptab name="Incidents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$)</itopblock>
+	</itoptab>
+</itoptabs>

+ 18 - 0
business/templates/person.html

@@ -0,0 +1,18 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/users2-big.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizPerson: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Teams">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Infrastructure">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactRealObject: contact_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkDocumentRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 30 - 0
business/templates/server.html

@@ -0,0 +1,30 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/network-server.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizServer: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Applications">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizApplication: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Patches">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizPatch: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Interfaces">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizInterface: device_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contacts">
+		<itopblock blockclass="DisplayBlock" type="list" linkage="object_id" encoding="text/sibusql">lnkContactRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+		<itoptab name="Incidents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizIncidentTicket: PKEY IS ticket_id IN (lnkInfraTicket: infra_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Changes">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizChangeTicket: PKEY IS ticket_id IN (lnkInfraChangeTicket: infra_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkDocumentRealObject: object_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>

+ 13 - 0
business/templates/service.html

@@ -0,0 +1,13 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/kservices-big.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Contracts">
+		<itopblock blockclass="DisplayBlock" asynchronous="true" type="list" encoding="text/sibusql">bizContract: service_id = $pkey$</itopblock>
+	</itoptab>
+</itoptabs>
+

+ 13 - 0
business/templates/software.html

@@ -0,0 +1,13 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/software.jpg" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">bizSoftware: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Installed_Instances">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizApplication: soft_id = $pkey$</itopblock>
+	</itoptab>
+
+</itoptabs>

+ 21 - 0
business/templates/team.html

@@ -0,0 +1,21 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/users2-big.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Members">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizContact: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Teams">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizTeam: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Infrastructure">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">logInfra: PKEY IS object_id IN (lnkContactRealObject: contact_id = $pkey$)</itopblock>
+	</itoptab>
+	<itoptab name="Documents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">bizDocument: PKEY IS doc_id IN (lnkDocumentRealObject: object_id = $pkey$)</itopblock>
+	</itoptab>
+</itoptabs>

+ 21 - 0
business/templates/ticket.html

@@ -0,0 +1,21 @@
+<div class="page_header">
+	<itopblock blockclass="MenuBlock" type="popup" encoding="text/sibusql" label="Actions">$class$: pkey = $pkey$</itopblock>
+	<h1>$class$: <span class="hilite">$name$</span></h1>
+	<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOpSetAttribute WHERE objkey = $pkey$ AND objclass = '$class$'</itopblock>
+</div>
+<img src="../images/messagebox_warning.png" style="margin-top:-10px; margin-right:10px; float:right">
+<itopblock blockclass="DisplayBlock" asynchronous="true" type="bare_details" encoding="text/sibusql">$class$: pkey = $pkey$</itopblock>
+<itoptabs>
+	<itoptab name="Impacted_Infrastructure">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkInfraTicket: ticket_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Related_Incidents">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkRelatedTicket: ticket_id = $pkey$</itopblock>
+	</itoptab>
+	<itoptab name="Contacts_to_Notify">
+		<itopblock blockclass="DisplayBlock" type="list" encoding="text/sibusql">lnkContactTicket: ticket_id = $pkey$</itopblock>
+	</itoptab>
+
+
+</itoptabs>
+<br style="clear:both"/>

+ 272 - 0
business/test_farm.class.inc.php

@@ -0,0 +1,272 @@
+<?php
+
+
+/**
+ * test_farm.class.inc.php
+ * User defined objects, for unit testing - SQL generation oriented (complex links) 
+ *
+ * @package     iTopUnitTests
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+///////////////////////////////////////////////////////////////////////////////
+// Business implementation demo
+///////////////////////////////////////////////////////////////////////////////
+
+//todo MetaModel::RegisterRelation("Potes", array("description"=>"ceux dont l'email ressemble au mien", "verb_down"=>"est pote de", "verb_up"=>"est pote de"));
+
+
+//todo MetaModel::RegisterZList("list1", array("description"=>"une premiere list, just for fun", "type"=>"attributes"));
+//todo MetaModel::RegisterZList("list2", array("description"=>"la secunda e meliora", "type"=>"attributes"));
+//todo MetaModel::RegisterZList("list3", array("description"=>"la variante qui tue", "type"=>"filters"));
+
+
+class Animal extends cmdbObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Animal",
+			"description" => "An animal",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(""),
+			"db_table" => "animals",
+			"db_key_field" => "animalid",
+			"db_finalclass_field" => "actualclass",
+		);
+		MetaModel::Init_Params($aParams);
+
+		MetaModel::Init_AddAttribute(new AttributeEnum("sex", array("label"=>"sex", "description"=>"sex", "allowed_values"=>new ValueSetEnum('male, female'), "sql"=>"sex", "default_value"=>"male", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("species", array("label"=>"species", "description"=>"species", "allowed_values"=>null, "sql"=>"species", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeInteger("speed", array("label"=>"walk speed", "description"=>"maximum possible speed m.s-1", "allowed_values"=>null, "sql"=>"speed", "default_value"=>4, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("mother", array("label"=>"mother", "description"=>"mother", "allowed_values"=>null, "sql"=>"mother", "targetclass"=>"Animal", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("father", array("label"=>"father", "description"=>"father", "allowed_values"=>null, "sql"=>"father", "targetclass"=>"Animal", "is_null_allowed"=>true, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("sex");
+		MetaModel::Init_AddFilterFromAttribute("species");
+		MetaModel::Init_AddFilterFromAttribute("speed");
+		MetaModel::Init_AddFilterFromAttribute("mother");
+		MetaModel::Init_AddFilterFromAttribute("father");
+	}
+}
+
+
+class Mammal extends Animal
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Mammal",
+			"description" => "An animal with some characteristics",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "mammals",
+			"db_key_field" => "mammalid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeInteger("height", array("label"=>"height", "description"=>"size in centimeters", "allowed_values"=>null, "sql"=>"height", "default_value"=>1, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeDate("birth", array("label"=>"birth date", "description"=>"birth date", "allowed_values"=>null, "sql"=>"birth", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("member", array("label"=>"member", "description"=>"leader", "allowed_values"=>null, "sql"=>"member", "targetclass"=>"Group", "is_null_allowed"=>true, "depends_on"=>array())));
+
+// ?		MetaModel::Init_AddAttribute(new AttributeLinkedSet("a2a", array("label"=>"animal to animal", "description"=>"interanimal relations", "depends_on"=>array(), "linked_class"=>"Animal2animal", "ext_key_to_me"=>"animal1", "count_min"=>0, "count_max"=>10, "allowed_values"=>null)));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("height");
+		MetaModel::Init_AddFilterFromAttribute("birth");
+		MetaModel::Init_AddFilterFromAttribute("member");
+	}
+}
+
+class Bird extends Animal
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Bird",
+			"description" => "Un regroupement de gens",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "birds",
+			"db_key_field" => "birdid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('geese', 'rooster', 'chicken', 'turckey', 'pie', 'corbeau')));
+
+		MetaModel::Init_InheritFilters();
+	}
+}
+
+class WalkingBird extends Bird
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "WalkingBird",
+			"description" => "A bird which nevers flies",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "walkingbirds",
+			"db_key_field" => "walkingbirdid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('geese', 'rooster', 'chicken', 'turckey')));
+		MetaModel::Init_InheritFilters();
+	}
+}
+
+class FlyingBird extends Bird
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "FlyingBird",
+			"description" => "A bird which nevers flies",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "flyingbirds",
+			"db_key_field" => "flyingbirdid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_OverloadAttributeParams("species", array("allowed_values"=>array('pie', 'corbeau')));
+		MetaModel::Init_AddAttribute(new AttributeInteger("flyingspeed", array("label"=>"flying speed", "description"=>"flying at ms.s-1", "allowed_values"=>null, "sql"=>"headcount", "default_value"=>10, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("flyingspeed");
+	}
+}
+
+class AnimalRelation extends cmdbObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "AnimalRelation",
+			"description" => "Link between two animals",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "a2a",
+			"db_key_field" => "linkid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+
+		// What makes it being a link...
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("animal1", array("label"=>"source", "description"=>"the animal which does ...", "allowed_values"=>null, "sql"=>"a1", "targetclass"=>"Animal", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("animal2", array("label"=>"target", "description"=>"the animal to which something is done...", "allowed_values"=>null, "sql"=>"a2", "targetclass"=>"Animal", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("animal1");
+		MetaModel::Init_AddFilterFromAttribute("animal2");
+	}
+}
+
+
+class EaterToEaten extends AnimalRelation
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "EaterToEaten",
+			"description" => "Animal 1 eats animal 2",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "eatertoeaten",
+			"db_key_field" => "eatertoeatonid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeEnum("DeadOrAlive", array("label"=>"DeadOrAlive", "description"=>"State in which it is ok for the eater to proceed", "allowed_values"=>new ValueSetEnum('dead, fresh, cooked'), "sql"=>"deadoralive", "default_value"=>"fresh", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("DeadOrAlive");
+	}
+}
+
+class Group extends cmdbObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "blah",
+			"name" => "Group",
+			"description" => "Group of animals",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "name",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "groups",
+			"db_key_field" => "groupid",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"name", "description"=>"name", "allowed_values"=>null, "sql"=>"name", "default_value"=>"xxx", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("leader", array("label"=>"leader", "description"=>"leader", "allowed_values"=>null, "sql"=>"leader", "targetclass"=>"Mammal", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("leader_name", array("label"=>"Leader Name", "description"=>"Name of the leader (defined on Mammal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"name")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("leader_speed", array("label"=>"Leader Name", "description"=>"Speed of the leader (defined on Animal)", "allowed_values"=>null, "extkey_attcode"=> 'leader', "target_attcode"=>"speed")));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("name");
+		MetaModel::Init_AddFilterFromAttribute("leader");
+		MetaModel::Init_AddFilterFromAttribute("leader_name");
+		MetaModel::Init_AddFilterFromAttribute("leader_speed");
+	}
+}
+
+
+?>

+ 38 - 0
config-dist.php

@@ -0,0 +1,38 @@
+<?php
+
+//
+// phpMyORM configuration file
+//
+// To be manually edited (or generated by the configuration wizard)
+//
+// The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
+//
+
+$MySettings = array(
+	'db_host' => 'localhost',
+	'db_user' => 'itop',
+	'db_pwd' => '1T0p',
+	'db_name' => 'itopv06',
+	'db_subname' => '', // use it to differentiate two applications instances running on the same DB
+);
+
+// Modules: file names should be specified as a absolute paths
+
+$MyModules = array(
+	'application' => array (
+		'../application/menunode.class.inc.php',
+		'../application/audit.rule.class.inc.php',
+		// to be continued...
+	),
+	'business' => array (
+		'../business/itop.business.class.inc.php'
+		// to be continued...
+	),
+	'addons' => array (
+		'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php', 
+		// other modules to come later
+	)
+);
+
+
+?>

+ 35 - 0
config-test-farm.php

@@ -0,0 +1,35 @@
+<?php
+
+//
+// phpMyORM configuration file
+//
+// To be manually edited (or generated by the configuration wizard)
+//
+// The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
+//
+
+$MySettings = array(
+	'db_host' => 'localhost',
+	'db_user' => 'RomainDBLogin',
+	'db_pwd' => '',
+	'db_name' => 'TestFarm',
+	'db_subname' => '', // use it to differentiate two applications instances running on the same DB
+);
+
+// Modules: file names should be specified as a absolute paths
+
+$MyModules = array(
+	'application' => array (
+		// to be continued...
+	),
+	'business' => array (
+		'../business/test_farm.class.inc.php',
+		// to be continued...
+	),
+	'addons' => array (
+		//'user rights' => '/addons/userrights/userrightsnull.class.inc.php', // or userrightsmatrix.class.inc.php
+		// other modules to come later
+	)
+);
+
+?>

+ 36 - 0
config-test-mymodel.php

@@ -0,0 +1,36 @@
+<?php
+
+//
+// phpMyORM configuration file
+//
+// To be manually edited (or generated by the configuration wizard)
+//
+// The file is used in MetaModel::LoadConfig() which does all the necessary initialization job
+//
+
+
+$MySettings = array(
+	'db_host' => 'localhost',
+	'db_user' => 'itop',
+	'db_pwd' => '1T0p',
+	'db_name' => 'TestBizModelGenericItop',
+	'db_subname' => 'tribute2itop', // use it to differentiate two applications instances running on the same DB
+);
+
+// Modules: file names should be specified as a absolute paths
+
+$MyModules = array(
+	'application' => array (
+		// to be continued...
+	),
+	'business' => array (
+		'../business/business_test.class.inc.php'
+		// to be continued...
+	),
+	'addons' => array (
+		// other modules to come later
+	)
+);
+
+
+?>

+ 491 - 0
core/MyHelpers.class.inc.php

@@ -0,0 +1,491 @@
+<?php
+
+/**
+ * MyHelpers
+ * various dev/debug helpers, to cleanup or at least re-organize
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class MyHelpers
+{
+	public static function CheckValueInArray($sDescription, $value, $aData)
+	{
+		if (!in_array($value, $aData))
+		{
+			self::HandleWrongValue($sDescription, $value, $aData);
+		}
+	}
+
+	public static function CheckKeyInArray($sDescription, $key, $aData)
+	{
+		if (!array_key_exists($key, $aData))
+		{
+			self::HandleWrongValue($sDescription, $key, array_keys($aData));
+		}
+	}
+
+	public static function HandleWrongValue($sDescription, $value, $aData)
+	{
+		if (count($aData) == 0)
+		{
+			$sArrayDesc = "{}";
+		}
+		else
+		{
+			$sArrayDesc = "{".implode(", ", $aData)."}";
+		}
+		// exit!
+		throw new CoreException("Wrong value for $sDescription, found '$value' while expecting a value in $sArrayDesc");
+	}
+
+	// getmicrotime()
+	// format sss.mmmuuupppnnn 
+	public static function getmicrotime()
+	{ 
+		list($usec, $sec) = explode(" ",microtime()); 
+		return ((float)$usec + (float)$sec); 
+	}
+
+	/*
+	 * MakeSQLComment
+	 * converts hash into text comment which we can use in a (mySQL) query
+	 */
+	public static function MakeSQLComment ($aHash)
+	{
+		if (empty($aHash)) return "";
+		$sComment = "";
+		{
+			foreach($aHash as $sKey=>$sValue)
+			{
+				$sComment .= "\n-- ". $sKey ."=>" . $sValue;
+			}
+		}
+		return $sComment;
+	}
+
+	public static function var_dump_html($aWords, $bFullDisplay = false)
+	{
+		echo "<pre>\n";
+		if ($bFullDisplay)
+		{
+			print_r($aWords); // full dump!
+		}
+		else
+		{
+			var_dump($aWords); // truncate things when they are too big
+		}
+		echo "\n</pre>\n";
+	}
+
+	public static function arg_dump_html()
+	{
+		echo "<pre>\n";
+		echo "GET:\n";
+		var_dump($_GET);
+		echo "POST:\n";
+		var_dump($_POST);
+		echo "\n</pre>\n";
+	}
+
+	public static function var_dump_string($var)
+	{
+		ob_start();
+		print_r($var);
+		$sRet = ob_get_clean();
+		return $sRet;
+	}
+
+	protected static function first_diff_line($s1, $s2)
+	{
+		$aLines1 = explode("\n", $s1);
+		$aLines2 = explode("\n", $s2);
+		for ($i = 0 ; $i < min(count($aLines1), count($aLines2)) ; $i++)
+		{
+			if ($aLines1[$i] != $aLines2[$i]) return $i;
+		}
+		return false;
+	}
+
+	protected static function highlight_line($sMultiline, $iLine, $sHighlightStart = '<b>', $sHightlightEnd = '</b>')
+	{
+		$aLines = explode("\n", $sMultiline);
+		$aLines[$iLine] = $sHighlightStart.$aLines[$iLine].$sHightlightEnd;
+		return implode("\n", $aLines);
+	}
+
+	protected static function first_diff($s1, $s2)
+	{
+		// do not work fine with multiline strings
+		$iLen1 = strlen($s1);
+		$iLen2 = strlen($s2);
+		for ($i = 0 ; $i < min($iLen1, $iLen2) ; $i++)
+		{
+			if ($s1[$i] !== $s2[$i]) return $i;
+		}
+		return false;
+	}
+
+	protected static function last_diff($s1, $s2)
+	{
+		// do not work fine with multiline strings
+		$iLen1 = strlen($s1);
+		$iLen2 = strlen($s2);
+		for ($i = 0 ; $i < min(strlen($s1), strlen($s2)) ; $i++)
+		{
+			if ($s1[$iLen1 - $i - 1] !== $s2[$iLen2 - $i - 1]) return array($iLen1 - $i, $iLen2 - $i);
+		}
+		return false;
+	}
+
+	protected static function text_cmp_html($sText1, $sText2, $sHighlight)
+	{
+		$iDiffPos = self::first_diff_line($sText1, $sText2);
+		$sDisp1 = self::highlight_line($sText1, $iDiffPos, '<div style="'.$sHighlight.'">', '</div>');
+		$sDisp2 = self::highlight_line($sText2, $iDiffPos, '<div style="'.$sHighlight.'">', '</div>');
+		echo "<table style=\"valign=top;\">\n";
+		echo "<tr>\n";
+		echo "<td><pre>$sDisp1</pre></td>\n";
+		echo "<td><pre>$sDisp2</pre></td>\n";
+		echo "</tr>\n";
+		echo "</table>\n";
+	}
+
+	protected static function string_cmp_html($s1, $s2, $sHighlight)
+	{
+		$iDiffPos = self::first_diff($s1, $s2);
+		if ($iDiffPos === false)
+		{
+			echo "strings are identical";
+			return;
+		}
+		$sStart = substr($s1, 0, $iDiffPos);
+
+		$aLastDiff = self::last_diff($s1, $s2);
+		$sEnd = substr($s1, $aLastDiff[0]);
+
+		$sMiddle1 = substr($s1, $iDiffPos, $aLastDiff[0] - $iDiffPos);
+		$sMiddle2 = substr($s2, $iDiffPos, $aLastDiff[1] - $iDiffPos);
+		
+		echo "<p>$sStart<span style=\"$sHighlight\">$sMiddle1</span>$sEnd</p>\n";
+		echo "<p>$sStart<span style=\"$sHighlight\">$sMiddle2</span>$sEnd</p>\n";
+	}
+
+	protected static function object_cmp_html($oObj1, $oObj2, $sHighlight)
+	{
+		$sObj1 = self::var_dump_string($oObj1);
+		$sObj2 = self::var_dump_string($oObj2);
+		return self::text_cmp_html($sObj1, $sObj2, $sHighlight);
+	}
+
+	public static function var_cmp_html($var1, $var2, $sHighlight = 'color:red; font-weight:bold;')
+	{
+		if (is_object($var1))
+		{
+			return self::object_cmp_html($var1, $var2, $sHighlight);
+		}
+		else if (count(explode("\n", $var1)) > 1)
+		{
+			// multiline string
+			return self::text_cmp_html($var1, $var2, $sHighlight);
+		}
+		else
+		{
+			return self::string_cmp_html($var1, $var2, $sHighlight);
+		}
+	}
+
+	public static function get_callstack_html($iLevelsToIgnore = 0, $aCallStack = null)
+	{
+		if ($aCallStack == null) $aCallStack = debug_backtrace();
+		
+		$aCallStack = array_slice($aCallStack, $iLevelsToIgnore);
+	
+		$aDigestCallStack = array();
+		$bFirstLine = true;		
+		foreach ($aCallStack as $aCallInfo)
+		{
+			$sLine = empty($aCallInfo['line']) ? "" : $aCallInfo['line'];
+			$sFile = empty($aCallInfo['file']) ? "" : $aCallInfo['file'];
+			$sClass = empty($aCallInfo['class']) ? "" : $aCallInfo['class'];
+			$sType = empty($aCallInfo['type']) ? "" : $aCallInfo['type'];
+			$sFunction = empty($aCallInfo['function']) ? "" : $aCallInfo['function'];
+
+			if ($bFirstLine)
+			{
+				$bFirstLine = false;
+				// For this line do not display the "function name" because
+				// that will be the name of our error handler for sure !
+				$sFunctionInfo = "N/A";
+			}
+			else
+			{
+				$args = '';
+				if (empty($aCallInfo['args'])) $aCallInfo['args'] = array();
+				foreach ($aCallInfo['args'] as $a)
+				{
+					if (!empty($args))
+					{
+						$args .= ', ';
+					}
+					switch (gettype($a))
+					{
+						case 'integer':
+						case 'double':
+						$args .= $a;
+					break;
+						case 'string':
+						$a = Str::pure2html(self::beautifulstr($a, 1024, true, true));
+						$args .= "\"$a\"";
+						break;
+					case 'array':
+						$args .= 'Array('.count($a).')';
+						break;
+					case 'object':
+						$args .= 'Object('.get_class($a).')';
+						break;
+					case 'resource':
+						$args .= 'Resource('.strstr($a, '#').')';
+						break;
+					case 'boolean':
+						$args .= $a ? 'True' : 'False';
+						break;
+					case 'NULL':
+						$args .= 'Null';
+						break;
+					default:
+						$args .= 'Unknown';
+					}
+				}
+				$sFunctionInfo = "$sClass $sType $sFunction($args)";
+			}
+			$aDigestCallStack[] = array('File'=>$sFile, 'Line'=>$sLine, 'Function'=>$sFunctionInfo);
+		}
+		return self::make_table_from_assoc_array($aDigestCallStack);
+	}
+
+	public static function dump_callstack($iLevelsToIgnore = 0, $aCallStack = null)
+	{
+		return self::get_callstack_html($iLevelsToIgnore, $aCallStack);
+	}
+
+	///////////////////////////////////////////////////////////////////////////////
+	// Source: New
+	// Last modif: 2004/12/20 RQU
+	///////////////////////////////////////////////////////////////////////////////
+	public static function make_table_from_assoc_array(&$aData)
+	{
+		if (!is_array($aData)) throw new CoreException("make_table_from_assoc_array: Error - the passed argument is not an array");
+		$aFirstRow = reset($aData);
+		if (count($aData) == 0) return '';
+		if (!is_array($aFirstRow)) throw new CoreException("make_table_from_assoc_array: Error - the passed argument is not a bi-dimensional array");
+		$sOutput = "";
+		$sOutput .= "<TABLE WIDTH=\"100%\" BORDER=\"0\" CELLSPACING=\"1\" CELLPADDING=\"1\">\n";
+	
+		// Table header
+		//
+		$sOutput .= "   <TR CLASS=celltitle>\n";
+		foreach ($aFirstRow as $fieldname=>$trash) {
+			$sOutput .= "      <TD><B>".$fieldname."</B></TD>\n";
+		}
+		$sOutput .= "   </TR>\n";
+	
+		// Table contents
+		//
+		$iCount = 0;
+		foreach ($aData as $aRow) {
+			$sStyle = ($iCount++ % 2 ? "STYLE=\"background-color : #eeeeee\"" : "");
+			$sOutput .= "   <TR $sStyle CLASS=cell>\n";
+			foreach ($aRow as $data) {
+				if (strlen($data) == 0) {
+					$data = "&nbsp;";
+				}
+				$sOutput .= "      <TD>".$data."</TD>\n";
+			}
+			$sOutput .= "   </TR>\n";
+		}
+		
+		$sOutput .= "</TABLE>\n";
+		return $sOutput;
+	}
+
+	public static function debug_breakpoint($arg)
+	{
+		echo "<H1> Debug breakpoint </H1>\n";
+		MyHelpers::var_dump_html($arg);
+		MyHelpers::dump_callstack();
+		exit;
+	}
+	public static function debug_breakpoint_notempty($arg)
+	{
+		if (empty($arg)) return;
+		echo "<H1> Debug breakpoint (triggered on non-empty value) </H1>\n";
+		MyHelpers::var_dump_html($arg);
+		MyHelpers::dump_callstack();
+		exit;
+	}
+
+	/**
+	* utf8... converts non ASCII chars into '?'
+	* Decided after some complex investigations, to have the tools work fine (Oracle+Perl vs mySQL+PHP...)
+	*/
+	public static function utf8($strText)
+	{
+		return iconv("WINDOWS-1252", "ASCII//TRANSLIT", $strText);
+	}	
+
+	/**
+	* xmlentities()
+	* ... same as htmlentities, but designed for xml !
+	*/
+	public static function xmlentities($string)
+	{
+		return str_replace( array( '&', '"', "'", '<', '>' ), array ( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;' ), $string );
+	}
+
+	/**
+	* xmlencode()
+	* Encodes a string so that for sure it can be output as an xml data string
+	*/
+	public static function xmlencode($string)
+	{
+		return xmlentities(iconv("UTF-8", "UTF-8//IGNORE",$string));
+	}
+
+	///////////////////////////////////////////////////////////////////////////////
+	// Source: New - format strings for output
+	// Last modif: 2005/01/18 RQU
+	///////////////////////////////////////////////////////////////////////////////
+	public static function beautifulstr($sLongString, $iMaxLen, $bShowLen=false, $bShowTooltip=true)
+	{
+		if (!is_string($sLongString)) throw new CoreException("beautifulstr: expect a string as 1st argument");
+	
+		// Nothing to do if the string is short
+		if (strlen($sLongString) <= $iMaxLen) return $sLongString;
+	
+		// Truncate the string
+		$sSuffix = "...";
+		if ($bShowLen) {
+			$sSuffix .= "(".strlen($sLongString)." chars)...";
+		}
+		$sOutput = substr($sLongString, 0, $iMaxLen - strlen($sSuffix)).$sSuffix;
+		$sOutput = htmlspecialchars($sOutput);
+	
+		// Add tooltip if required
+		//if ($bShowTooltip) {
+		//	$oTooltip = new gui_tooltip($sLongString);
+		//	$sOutput = "<SPAN ".$oTooltip->get_mouseOver_code().">".$sOutput."</SPAN>";
+		//}
+		return $sOutput;
+	}
+}
+
+/**
+Utility class: static methods for cleaning & escaping untrusted (i.e.
+user-supplied) strings.
+Any string can (usually) be thought of as being in one of these 'modes':
+pure = what the user actually typed / what you want to see on the page /
+      what is actually stored in the DB
+gpc  = incoming GET, POST or COOKIE data
+sql  = escaped for passing safely to RDBMS via SQL (also, data from DB
+      queries and file reads if you have magic_quotes_runtime on--which
+      is rare)
+html = safe for html display (htmlentities applied)
+Always knowing what mode your string is in--using these methods to
+convert between modes--will prevent SQL injection and cross-site scripting.
+This class refers to its own namespace (so it can work in PHP 4--there is no
+self keyword until PHP 5). Do not change the name of the class w/o changing
+all the internal references.
+Example usage: a POST value that you want to query with:
+$username = Str::gpc2sql($_POST['username']);
+*/
+//This sets SQL escaping to use slashes; for Sybase(/MSSQL)-style escaping
+// ( ' --> '' ), set to true.
+define('STR_SYBASE', false);
+class Str
+{
+	public static function gpc2sql($gpc, $maxLength = false)
+	{
+		return self::pure2sql(self::gpc2pure($gpc), $maxLength);
+	}
+	public static function gpc2html($gpc, $maxLength = false)
+	{
+		return self::pure2html(self::gpc2pure($gpc), $maxLength);
+	}
+	public static function gpc2pure($gpc)
+	{
+		if (ini_get('magic_quotes_sybase')) $pure = str_replace("''", "'", $gpc);
+		else                                $pure = get_magic_quotes_gpc() ? stripslashes($gpc) : $gpc;
+		return $pure;
+	}
+	public static function html2pure($html)
+	{
+		return html_entity_decode($html);
+	}
+	public static function html2sql($html, $maxLength = false)
+	{
+		return self::pure2sql(self::html2pure($html), $maxLength);
+	}
+	public static function pure2html($pure, $maxLength = false)
+	{
+		return $maxLength
+			? htmlentities(substr($pure, 0, $maxLength))
+			: htmlentities($pure);
+	}
+	public static function pure2sql($pure, $maxLength = false)
+	{
+		if ($maxLength) $pure = substr($pure, 0, $maxLength);
+		return (STR_SYBASE)
+			? str_replace("'", "''", $pure)
+			: addslashes($pure);
+	}
+	public static function sql2html($sql, $maxLength = false)
+	{
+		$pure = self::sql2pure($sql);
+		if ($maxLength) $pure = substr($pure, 0, $maxLength);
+		return self::pure2html($pure);
+	}
+	public static function sql2pure($sql)
+	{
+		return (STR_SYBASE)
+			? str_replace("''", "'", $sql)
+			: stripslashes($sql);
+	}
+
+	public static function xml2pure($xml)
+	{
+		// #@# - not implemented
+		return $xml;
+	}
+	public static function pure2xml($pure)
+	{
+		return self::xmlencode($pure);
+	}
+
+	protected static function xmlentities($string)
+	{
+		return str_replace( array( '&', '"', "'", '<', '>' ), array ( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;' ), $string );
+	}
+
+	/**
+	* xmlencode()
+	* Encodes a string so that for sure it can be output as an xml data string
+	*/
+	protected static function xmlencode($string)
+	{
+		return self::xmlentities(iconv("ISO-8859-1", "UTF-8//IGNORE",$string));
+	}
+
+	public static function islowcase($sString)
+	{
+		return (strtolower($sString) == $sString);
+	}
+}
+
+?>

+ 323 - 0
core/archive.class.inc.php

@@ -0,0 +1,323 @@
+<?php
+
+/**
+ * archive.class.inc.php
+ * Utility to import/export the DB from/to a ZIP file 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+/**
+ * iTopArchive a class to manipulate (read/write) iTop archives with their catalog
+ * Each iTop archive is a zip file that contains (at the root of the archive)
+ * a file called catalog.xml holding the description of the archive
+ */
+class iTopArchive
+{
+	const read = 0;
+	const create = ZipArchive::CREATE;
+	
+	protected $m_sZipPath;
+	protected $m_oZip;
+	protected $m_sVersion;
+	protected $m_sTitle;
+	protected $m_sDescription;
+	protected $m_aPackages;
+	protected $m_aErrorMessages;
+
+	/**
+	 * Construct an iTopArchive object
+	 * @param $sArchivePath string The full path the archive file
+	 * @param $iMode integrer Either iTopArchive::read for reading an existing archive or iTopArchive::create for creating a new one. Updating is not supported (yet)
+	 */
+	public function __construct($sArchivePath, $iMode = iTopArchive::read)
+	{
+		$this->m_sZipPath = $sArchivePath;
+		$this->m_oZip = new ZipArchive();
+		$this->m_oZip->open($this->m_sZipPath, $iMode);
+		$this->m_aErrorMessages = array();
+		$this->m_sVersion = '1.0';
+		$this->m_sTitle = '';
+		$this->m_sDescription = '';
+		$this->m_aPackages = array();
+	}
+
+	public function SetTitle($sTitle)
+	{
+		$this->m_sTitle = $sTitle;
+	}
+	
+	public function SetDescription($sDescription)
+	{
+		$this->m_sDescription = $sDescription;
+	}
+	
+	public function GetTitle()
+	{
+		return $this->m_sTitle;
+	}
+	
+	public function GetDescription()
+	{
+		return $this->m_sDescription;
+	}
+	
+	public function GetPackages()
+	{
+		return $this->m_aPackages;
+	}
+	
+	public function __destruct()
+	{
+		$this->m_oZip->close();
+	}
+	
+	/**
+	 * Get the error message explaining the latest error encountered
+	 * @return array All the error messages encountered during the validation
+	 */
+	public function GetErrors()
+	{
+		return $this->m_aErrorMessages;
+	}
+	
+	/**
+	 * Read the catalog from the archive (zip) file
+	 * @param sPath string Path the the zip file
+	 * @return boolean True in case of success, false otherwise
+	 */
+	public function ReadCatalog()
+	{
+		if ($this->IsValid())
+		{
+			$sXmlCatalog = $this->m_oZip->getFromName('catalog.xml');
+			$oParser = xml_parser_create();
+			xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes);
+			xml_parser_free($oParser);
+			
+			$iIndex = $aIndexes['ARCHIVE'][0];
+			$this->m_sVersion = $aValues[$iIndex]['attributes']['VERSION'];
+			$iIndex = $aIndexes['TITLE'][0];
+			$this->m_sTitle = $aValues[$iIndex]['value'];
+			$iIndex = $aIndexes['DESCRIPTION'][0];
+			if (array_key_exists('value', $aValues[$iIndex]))
+			{
+				// #@# implement a get_array_value(array, key, default) ?
+				$this->m_sDescription = $aValues[$iIndex]['value'];
+			}
+			
+			foreach($aIndexes['PACKAGE'] as $iIndex)
+			{
+				$this->m_aPackages[$aValues[$iIndex]['attributes']['HREF']] = array( 'type' => $aValues[$iIndex]['attributes']['TYPE'], 'title'=> $aValues[$iIndex]['attributes']['TITLE'], 'description' => $aValues[$iIndex]['value']);
+			}
+			
+			//echo "Archive path: {$this->m_sZipPath}<br/>\n";
+			//echo "Archive format version: {$this->m_sVersion}<br/>\n";
+			//echo "Title: {$this->m_sTitle}<br/>\n";
+			//echo "Description: {$this->m_sDescription}<br/>\n";
+			//foreach($this->m_aPackages as $aFile)
+			//{
+			//	echo "{$aFile['title']} ({$aFile['type']}): {$aFile['description']}<br/>\n";
+			//}
+		}
+		return true;
+	}
+	
+	public function WriteCatalog()
+	{
+		$sXml  = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?".">\n"; // split the XML closing tag that disturbs PSPad's syntax coloring
+		$sXml .= "<archive version=\"1.0\">\n";
+		$sXml .= "<title>{$this->m_sTitle}</title>\n";
+		$sXml .= "<description>{$this->m_sDescription}</description>\n";
+		foreach($this->m_aPackages as $sFileName => $aFile)
+		{
+			$sXml .= "<package title=\"{$aFile['title']}\" type=\"{$aFile['type']}\" href=\"$sFileName\">{$aFile['description']}</package>\n";
+		}
+		$sXml .= "</archive>";
+		$this->m_oZip->addFromString('catalog.xml', $sXml);
+	}
+	
+   /**
+	* Add a package to the archive
+	* @param string $sExternalFilePath The path to the file to be added to the archive as a package (directories are not yet implemented)
+	* @param string $sFilePath The name of the file inside the archive
+	* @param string $sTitle A short title for this package
+	* @param string $sType Type of the package. SQL scripts must be of type 'text/sql'
+	* @param string $sDescription A longer description of the purpose of this package
+	* @return none
+	*/
+	public function AddPackage($sExternalFilePath, $sFilePath, $sTitle, $sType, $sDescription)
+	{
+		$this->m_aPackages[$sFilePath] = array('title' => $sTitle, 'type' => $sType, 'description' => $sDescription);
+		$this->m_oZip->addFile($sExternalFilePath, $sFilePath);
+	}
+	 
+   /**
+	* Reads the contents of the given file from the archive
+	* @param string $sFileName The path to the file inside the archive
+	* @return string The content of the file read from the archive
+	*/
+	public function GetFileContents($sFileName)
+	{
+		return $this->m_oZip->getFromName($sFileName);
+	}
+	 
+   /**
+	* Extracts the contents of the given file from the archive
+	* @param string $sFileName The path to the file inside the archive
+	* @param string $sDestinationFileName The path of the file to write
+	* @return none
+	*/
+	 public function ExtractToFile($sFileName, $sDestinationFileName)
+	 {
+	 	$iBufferSize = 64 * 1024; // Read 64K at a time
+		$oZipStream = $this->m_oZip->getStream($sFileName);
+		$oDestinationStream = fopen($sDestinationFileName, 'wb');
+		while (!feof($oZipStream)) {
+			$sContents = fread($oZipStream, $iBufferSize);
+			fwrite($oDestinationStream, $sContents);
+		}
+		fclose($oZipStream);
+		fclose($oDestinationStream);
+	 }
+	 
+	 /**
+	  * Apply a SQL script taken from the archive. The package must be listed in the catalog and of type text/sql
+	  * @param string $sFileName The path to the SQL package inside the archive
+	  * @return boolean false in case of error, true otherwise
+	  */
+	 public function ImportSql($sFileName, $sDatabase = 'itop')
+	 {
+		if ( ($this->m_oZip->locateName($sFileName) == false) || (!isset($this->m_aPackages[$sFileName])) || ($this->m_aPackages[$sFileName]['type'] != 'text/sql'))
+		{
+			// invalid type or not listed in the catalog
+			return false;
+		}
+		$sTempName = tempnam("../tmp/", "sql");
+		//echo "Extracting to: '$sTempName'<br/>\n";
+		$this->ExtractToFile($sFileName, $sTempName);
+		// Note: the command line below works on Windows with the right path to mysql !!!
+		$sCommandLine = 'type "'.$sTempName.'" | "/iTop/MySQL Server 5.0/bin/mysql.exe" -u root '.$sDatabase;
+		//echo "Executing: '$sCommandLine'<br/>\n";
+		exec($sCommandLine, $aOutput, $iRet);
+		//echo "Return code: $iRet<br/>\n";
+		//echo "Output:<br/><pre>\n";
+		//print_r($aOutput);
+		//echo "</pre><br/>\n";
+		unlink($sTempName);
+		return ($iRet == 0);
+	 }
+	 
+	 /**
+	  * Dumps some part of the specified MySQL database into the archive as a text/sql package
+	  * @param $sTitle string A short title for this SQL script
+	  * @param $sDescription string A longer description of the purpose of this SQL script
+	  * @param $sFileName string The name of the package inside the archive
+	  * @param $sDatabase string name of the database
+	  * @param $aTables array array or table names. If empty, all tables are dumped
+	  * @param $bStructureOnly boolean Whether or not to dump the data or just the schema
+	  * @return boolean False in case of error, true otherwise
+	  */
+	 public function AddDatabaseDump($sTitle, $sDescription, $sFileName, $sDatabase = 'itop', $aTables = array(), $bStructureOnly = true)
+	 {
+		$sTempName = tempnam("../tmp/", "sql");
+		$sNoData = $bStructureOnly ? "--no-data" : "";
+		$sCommandLine = "\"/iTop/MySQL Server 5.0/bin/mysqldump.exe\" --user=root --opt $sNoData --result-file=$sTempName $sDatabase ".implode(" ", $aTables);
+		//echo "Executing command: '$sCommandLine'<br/>\n";
+		exec($sCommandLine, $aOutput, $iRet);
+		//echo "Return code: $iRet<br/>\n";
+		//echo "Output:<br/><pre>\n";
+		//print_r($aOutput);
+		//echo "</pre><br/>\n";
+		if ($iRet == 0)
+		{
+			$this->AddPackage($sTempName, $sFileName, $sTitle, 'text/sql', $sDescription);
+		}
+		//unlink($sTempName);
+		return ($iRet == 0);
+	 }
+
+	/**
+	 * Check the consistency of the archive
+	 * @return boolean True if the archive file is consistent
+	 */
+	 public function IsValid()
+	 {
+	 	// TO DO: use a DTD to validate the XML instead of this hand-made validation
+	 	$bResult = true;
+		$aMandatoryTags = array('ARCHIVE' => array('VERSION'),
+								'TITLE' => array(),
+								'DESCRIPTION' => array(),
+								'PACKAGE'  => array('TYPE', 'HREF', 'TITLE'));
+		
+		$sXmlCatalog = $this->m_oZip->getFromName('catalog.xml');
+		$oParser = xml_parser_create();
+		xml_parse_into_struct($oParser, $sXmlCatalog, $aValues, $aIndexes);
+		xml_parser_free($oParser);
+		
+		foreach($aMandatoryTags as $sTag => $aAttributes)
+		{
+			// Check that all the required tags are present
+			if (!isset($aIndexes[$sTag]))
+			{
+				$this->m_aErrorMessages[] = "The XML catalog does not contain the mandatory tag $sTag.";
+				$bResult = false; 
+			}
+			else
+			{
+				foreach($aIndexes[$sTag] as $iIndex)
+				{
+					switch($aValues[$iIndex]['type'])
+					{
+						case 'complete':
+						case 'open':
+							// Check that all the required attributes are present
+							foreach($aAttributes as $sAttribute)
+							{
+								if (!isset($aValues[$iIndex]['attributes'][$sAttribute]))
+								{
+									$this->m_aErrorMessages[] = "The tag $sTag ($iIndex) does not contain the required attribute $sAttribute.";
+								}
+							}
+						break;
+						
+						default:
+							// ignore other type of tags: close or cdata
+					}
+				}
+			}
+		}
+		return $bResult;
+	 }
+}
+/*
+// Unit test - reading an archive
+$sArchivePath = '../tmp/archive.zip';
+$oArchive = new iTopArchive($sArchivePath, iTopArchive::read);
+$oArchive->ReadCatalog();
+$oArchive->ImportSql('full_backup.sql');
+
+// Writing an archive --
+
+$sArchivePath = '../tmp/archive2.zip';
+$oArchive = new iTopArchive($sArchivePath, iTopArchive::create);
+$oArchive->SetTitle('First Archive !');
+$oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.');
+$oArchive->AddPackage('../tmp/schema.sql', 'test.sql', 'this is just a test', 'text/sql', 'My first attempt at creating an archive from PHP...');
+$oArchive->WriteCatalog();
+
+
+$sArchivePath = '../tmp/archive2.zip';
+$oArchive = new iTopArchive($sArchivePath, iTopArchive::create);
+$oArchive->SetTitle('First Archive !');
+$oArchive->SetDescription('This is just a test. Does not contain a lot of useful data.');
+$oArchive->AddDatabaseDump('Test', 'This is my first automatic dump', 'schema.sql', 'itop', array('objects'));
+$oArchive->WriteCatalog();
+*/
+?>

+ 989 - 0
core/attributedef.class.inc.php

@@ -0,0 +1,989 @@
+<?php
+
+require_once('MyHelpers.class.inc.php');
+
+
+/**
+ * add some description here... 
+ *
+ * @package     iTopORM
+ */
+define('EXTKEY_RELATIVE', 1);
+
+/**
+ * add some description here... 
+ *
+ * @package     iTopORM
+ */
+define('EXTKEY_ABSOLUTE', 2);
+
+
+/**
+ * Attribute definition API, implemented in and many flavours (Int, String, Enum, etc.) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class AttributeDefinition
+{
+	abstract public function GetType();
+	abstract public function GetTypeDesc();
+	abstract public function GetEditClass();
+	abstract public function GetDBFieldType();
+
+	protected $m_sCode;
+	private $m_aParams = array();
+	private $m_sHostClass = array();
+	protected function Get($sParamName) {return $this->m_aParams[$sParamName];}
+	
+	public function __construct($sCode, $aParams)
+	{
+		$this->m_sCode = $sCode;
+		$this->m_aParams = $aParams;
+		$this->ConsistencyCheck();
+	}
+	public function OverloadParams($aParams)
+	{
+		foreach ($aParams as $sParam => $value)
+		{
+			if (!array_key_exists($sParam, $this->m_aParams))
+			{
+				throw new CoreException("Unknown attribute definition parameter '$sParam', please select a value in {".implode(", ", $this->m_aParams)."}");
+			}
+			else
+			{
+				$this->m_aParams[$sParam] = $value;
+			}
+		}
+	}
+	public function SetHostClass($sHostClass)
+	{
+		$this->m_sHostClass = $sHostClass;
+	}
+	public function GetHostClass()
+	{
+		return $this->m_sHostClass;
+	}
+
+	// Note: I could factorize this code with the parameter management made for the AttributeDef class
+	// to be overloaded
+	static protected function ListExpectedParams()
+	{
+		return array("label", "description", "allowed_values");
+	}
+
+	private function ConsistencyCheck()
+	{
+
+		// Check that any mandatory param has been specified
+		//
+		$aExpectedParams = $this->ListExpectedParams();
+		foreach($aExpectedParams as $sParamName)
+		{
+			if (!array_key_exists($sParamName, $this->m_aParams))
+			{
+				$aBacktrace = debug_backtrace();
+				$sTargetClass = $aBacktrace[2]["class"];
+				$sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"];
+				throw new Exception("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
+			}
+		}
+	} 
+
+	// table, key field, name field
+	public function ListDBJoins()
+	{
+		return "";
+		// e.g: return array("Site", "infrid", "name");
+	} 
+	public function IsDirectField() {return false;} 
+	public function IsScalar() {return false;} 
+	public function IsLinkSet() {return false;} 
+	public function IsExternalKey($iType = EXTKEY_RELATIVE) {return false;} 
+	public function IsExternalField() {return false;} 
+	public function IsWritable() {return false;} 
+	public function GetCode() {return $this->m_sCode;} 
+	public function GetLabel() {return $this->Get("label");} 
+	public function GetDescription() {return $this->Get("description");} 
+	public function GetValuesDef() {return $this->Get("allowed_values");} 
+	public function GetPrerequisiteAttributes() {return $this->Get("depends_on");} 
+	//public function IsSearchableStd() {return $this->Get("search_std");} 
+	//public function IsSearchableGlobal() {return $this->Get("search_global");} 
+	//public function IsMandatory() {return $this->Get("is_mandatory");} 
+	//public function GetMinVal() {return $this->Get("min");} 
+	//public function GetMaxVal() {return $this->Get("max");} 
+	//public function GetSize() {return $this->Get("size");} 
+	//public function GetCheckRegExp() {return $this->Get("regexp");} 
+	//public function GetCheckFunc() {return $this->Get("checkfunc");} 
+
+	// Definition: real value is what will be stored in memory and maintained by MetaModel
+	// DBObject::Set()        relies on MakeRealValue()
+	// MetaModel::MakeQuery()  relies on RealValueToSQLValue()
+	// DBObject::FromRow()    relies on SQLToRealValue()
+	public function MakeRealValue($proposedValue) {return $proposedValue;} // force an allowed value (type conversion and possibly forces a value as mySQL would do upon writing!)
+	public function RealValueToSQLValue($value) {return $value;} // format value as a valuable SQL literal (quoted outside)
+	public function SQLValueToRealValue($value) {return $value;} // take the result of a fetch... and make it a PHP variable
+
+	public function GetJSCheckFunc()
+	{
+		$sRegExp = $this->Get("regexp");
+		if (empty($sRegExp)) return 'return true;';
+
+		return "return regexp('$sRegExp', myvalue);";
+	} 
+	public function CheckValue($value)
+	{
+		$sRegExp = $this->Get("regexp");
+		if (empty($sRegExp)) return true;
+		
+		return preg_match(preg_escape($this->Get("regexp")), $value);
+	}
+	 
+	public function MakeValue()
+	{
+		$sComputeFunc = $this->Get("compute_func");
+		if (empty($sComputeFunc)) return null;
+
+		return call_user_func($sComputeFunc);
+	}
+	
+	abstract public function DBGetUsedFields();
+	abstract public function GetDefaultValue();
+
+	//
+	// To be overloaded in subclasses
+	//
+	
+	abstract public function GetBasicFilterOperators(); // returns an array of "opCode"=>"description"
+	abstract public function GetBasicFilterLooseOperator(); // returns an "opCode"
+	//abstract protected GetBasicFilterHTMLInput();
+	abstract public function GetBasicFilterSQLExpr($sOpCode, $value); 
+
+	public function GetAsHTML($sValue)
+	{
+		return Str::pure2html($sValue);
+	}
+
+	public function GetAsXML($sValue)
+	{
+		return Str::pure2xml($sValue);
+	}
+
+	public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',')
+	{
+		return str_replace($sSeparator, $sSepEscape, $sValue);
+	}
+}
+
+/**
+ * Set of objects directly linked to an object, and being part of its definition  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeLinkedSet extends AttributeDefinition
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("depends_on", "linked_class", "ext_key_to_me", "count_min", "count_max"));
+	}
+
+	public function GetType() {return "Array of objects";}
+	public function GetTypeDesc() {return "Any kind of objects [subclass] of the same class";}
+	public function GetEditClass() {return "List";}
+	public function GetDBFieldType() {return "N/A";} // should be moved out of the AttributeDef root class
+
+	public function IsWritable() {return true;} 
+	public function IsLinkSet() {return true;} 
+
+	public function GetDefaultValue() {return DBObjectSet::FromScratch($this->Get('linked_class'));}
+
+	public function GetLinkedClass() {return $this->Get('linked_class');}
+	public function GetExtKeyToMe() {return $this->Get('ext_key_to_me');}
+
+	public function DBGetUsedFields() {return array();}
+	public function GetBasicFilterOperators() {return array();}
+	public function GetBasicFilterLooseOperator() {return '';}
+	public function GetBasicFilterSQLExpr($sOpCode, $value) {return '';}
+
+	public function GetAsHTML($sValue)
+	{
+		return "ERROR: LIST OF OBJECTS";
+	}
+
+	public function GetAsXML($sValue)
+	{
+		return "ERROR: LIST OF OBJECTS";
+	}
+
+	public function GetAsCSV($sValue, $sSeparator = ';', $sSepEscape = ',')
+	{
+		return "ERROR: LIST OF OBJECTS";
+	}
+}
+
+/**
+ * Set of objects linked to an object (n-n), and being part of its definition  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeLinkedSetIndirect extends AttributeLinkedSet
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("ext_key_to_remote"));
+	}
+	public function GetExtKeyToRemote() { return $this->Get('ext_key_to_remote'); }
+}
+
+/**
+ * Abstract class implementing default filters for a DB column  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeDBFieldVoid extends AttributeDefinition
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("depends_on", "sql"));
+	}
+
+	public function GetType() {return "Void";}
+	public function GetTypeDesc() {return "Any kind of value, from the DB";}
+	public function GetEditClass() {return "String";}
+	public function GetDBFieldType() {return "VARCHAR(255)";}
+	
+	public function IsDirectField() {return true;} 
+	public function IsScalar() {return true;} 
+	public function IsWritable() {return true;} 
+	public function GetSQLExpr()    {return $this->Get("sql");}
+	public function GetDefaultValue() {return "";}
+	public function IsNullAllowed() {return false;}
+	public function DBGetUsedFields()
+	{
+		// #@# bugge a mort... a suivre...
+		return array($this->Get("sql"));
+	} 
+
+	public function GetBasicFilterOperators()
+	{
+		return array("="=>"equals", "!="=>"differs from");
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		return "=";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+		switch ($sOpCode)
+		{
+		case '!=':
+			return $this->GetSQLExpr()." != $sQValue";
+			break;
+		case '=':
+		default:
+			return $this->GetSQLExpr()." = $sQValue";
+		}
+	} 
+}
+
+/**
+ * Base class for all kind of DB attributes, with the exception of external keys 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeDBField extends AttributeDBFieldVoid
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("default_value", "is_null_allowed"));
+	}
+	public function GetDefaultValue() {return $this->Get("default_value");}
+	public function IsNullAllowed() {return strtolower($this->Get("is_null_allowed"));}
+}
+
+/**
+ * Map an integer column to an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeInteger extends AttributeDBField
+{
+	static protected function ListExpectedParams()
+	{
+		return parent::ListExpectedParams();
+		//return array_merge(parent::ListExpectedParams(), array());
+	}
+
+	public function GetType() {return "Integer";}
+	public function GetTypeDesc() {return "Numeric value (could be negative)";}
+	public function GetEditClass() {return "String";}
+	public function GetDBFieldType() {return "INT";}
+	
+	public function GetBasicFilterOperators()
+	{
+		return array(
+			"!="=>"differs from",
+			"="=>"equals",
+			">"=>"greater (strict) than",
+			">="=>"greater than",
+			"<"=>"less (strict) than",
+			"<="=>"less than",
+			"in"=>"in"
+		);
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		// Unless we implement an "equals approximately..." or "same order of magnitude"
+		return "=";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+		switch ($sOpCode)
+		{
+		case '!=':
+			return $this->GetSQLExpr()." != $sQValue";
+			break;
+		case '>':
+			return $this->GetSQLExpr()." > $sQValue";
+			break;
+		case '>=':
+			return $this->GetSQLExpr()." >= $sQValue";
+			break;
+		case '<':
+			return $this->GetSQLExpr()." < $sQValue";
+			break;
+		case '<=':
+			return $this->GetSQLExpr()." <= $sQValue";
+			break;
+		case 'in':
+			if (!is_array($value)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
+			return $this->GetSQLExpr()." IN ('".implode("', '", $value)."')"; 
+			break;
+
+		case '=':
+		default:
+			return $this->GetSQLExpr()." = \"$value\"";
+		}
+	} 
+
+	public function MakeRealValue($proposedValue)
+	{
+		//return intval($proposedValue); could work as well
+		return (int)$proposedValue;
+	}
+	public function RealValueToSQLValue($value)
+	{
+		assert(is_numeric($value));
+		return $value; // supposed to be an int
+	}
+	public function SQLValueToRealValue($value)
+	{
+		// Use cast (int) or intval() ?
+		return (int)$value;
+		
+	}
+}
+
+/**
+ * Map a varchar column (size < ?) to an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeString extends AttributeDBField
+{
+	static protected function ListExpectedParams()
+	{
+		return parent::ListExpectedParams();
+		//return array_merge(parent::ListExpectedParams(), array());
+	}
+
+	public function GetType() {return "String";}
+	public function GetTypeDesc() {return "Alphanumeric string";}
+	public function GetEditClass() {return "String";}
+	public function GetDBFieldType() {return "VARCHAR(255)";}
+
+	public function GetBasicFilterOperators()
+	{
+		return array(
+			"="=>"equals",
+			"!="=>"differs from",
+			"Like"=>"equals (no case)",
+			"NotLike"=>"differs from (no case)",
+			"Contains"=>"contains",
+			"Begins with"=>"begins with",
+			"Finishes with"=>"finishes with"
+		);
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		return "Contains";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+		switch ($sOpCode)
+		{
+		case '=':
+		case '!=':
+			return $this->GetSQLExpr()." $sOpCode $sQValue";
+		case 'Begins with':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("$value%");
+		case 'Finishes with':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value");
+		case 'Contains':
+			return $this->GetSQLExpr()." LIKE ".CMDBSource::Quote("%$value%");
+		case 'NotLike':
+			return $this->GetSQLExpr()." NOT LIKE $sQValue";
+		case 'Like':
+		default:
+			return $this->GetSQLExpr()." LIKE $sQValue";
+		}
+	} 
+
+	public function MakeRealValue($proposedValue)
+	{
+		return (string)$proposedValue;
+		// if (!settype($proposedValue, "string"))
+		// {
+		// 	throw new CoreException("Failed to change the type of '$proposedValue' to a string");
+		// }
+	}
+	public function RealValueToSQLValue($value)
+	{
+		assert(is_string($value));
+		return $value;
+	}
+	public function SQLValueToRealValue($value)
+	{
+		return $value;
+	}
+}
+
+/**
+ * Map a text column (size > ?) to an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeText extends AttributeString
+{
+	public function GetType() {return "Text";}
+	public function GetTypeDesc() {return "Multiline character string";}
+	public function GetEditClass() {return "Text";}
+	public function GetDBFieldType() {return "TEXT";}
+
+	public function GetAsHTML($sValue)
+	{
+		return str_replace("\n", "<br>\n", parent::GetAsHTML($sValue));
+	}
+
+	public function GetAsXML($value)
+	{
+		return Str::pure2xml($value);
+	}
+
+	public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',')
+	{
+		return str_replace("\n", "[newline]", parent::GetAsCSV($sValue, $sSeparator, $sSepEscape));
+	}
+}
+
+/**
+ * Map a enum column to an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeEnum extends AttributeString
+{
+	static protected function ListExpectedParams()
+	{
+		return parent::ListExpectedParams();
+		//return array_merge(parent::ListExpectedParams(), array());
+	}
+
+	public function GetType() {return "Enum";}
+	public function GetTypeDesc() {return "List of predefined alphanumeric strings";}
+	public function GetEditClass() {return "String";}
+	public function GetDBFieldType()
+	{
+		$oValDef = $this->GetValuesDef();
+		if ($oValDef)
+		{
+			$aValues = CMDBSource::Quote($oValDef->GetValues(array(), ""), true);
+		}
+		else
+		{
+			$aValues = array();
+		}
+		if (count($aValues) > 0)
+		{
+			return "ENUM(".implode(", ", $aValues).")";
+		}
+		else
+		{
+			return "VARCHAR(255)"; // ENUM() is not an allowed syntax!
+		}
+	}
+
+	public function GetBasicFilterOperators()
+	{
+		return parent::GetBasicFilterOperators();
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		return parent::GetBasicFilterLooseOperator();
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		return parent::GetBasicFilterSQLExpr($sOpCode, $value);
+	} 
+}
+
+/**
+ * Map a date+time column to an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeDate extends AttributeDBField
+{
+	const MYDATEFORMAT = "Y-m-d H:i:s";
+	//const MYDATETIMEZONE = "UTC";
+	const MYDATETIMEZONE = "Europe/Paris";
+	static protected $const_TIMEZONE = null; // set once for all upon object construct 
+
+	static public function InitStatics()
+	{
+		// Init static constant once for all (remove when PHP allows real static const)
+		self::$const_TIMEZONE = new DateTimeZone(self::MYDATETIMEZONE);
+
+		// #@# Init default timezone -> do not get a notice... to be improved !!!
+		date_default_timezone_set(self::MYDATETIMEZONE);
+	}
+
+	static protected function ListExpectedParams()
+	{
+		return parent::ListExpectedParams();
+		//return array_merge(parent::ListExpectedParams(), array());
+	}
+
+	public function GetType() {return "Date";}
+	public function GetTypeDesc() {return "Date and time";}
+	public function GetEditClass() {return "Date";}
+	public function GetDBFieldType() {return "TIMESTAMP";}
+
+	public function GetBasicFilterOperators()
+	{
+		return array(
+			"="=>"equals",
+			"!="=>"differs from",
+			"<"=>"before",
+			"<="=>"before",
+			">"=>"after (strictly)",
+			">="=>"after",
+			"SameDay"=>"same day (strip time)",
+			"SameMonth"=>"same year/month",
+			"SameYear"=>"same year",
+			"Today"=>"today",
+			">|"=>"after today + N days",
+			"<|"=>"before today + N days",
+			"=|"=>"equals today + N days",
+		);
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		// Unless we implement a "same xxx, depending on given precision" !
+		return "=";
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$sQValue = CMDBSource::Quote($value);
+
+		switch ($sOpCode)
+		{
+		case '=':
+		case '!=':
+		case '<':
+		case '<=':
+		case '>':
+		case '>=':
+			return $this->GetSQLExpr()." $sOpCode $sQValue";
+		case 'SameDay':
+			return "DATE(".$this->GetSQLExpr().") = DATE($sQValue)";
+		case 'SameMonth':
+			return "DATE_FORMAT(".$this->GetSQLExpr().", '%Y-%m') = DATE_FORMAT($sQValue, '%Y-%m')";
+		case 'SameYear':
+			return "MONTH(".$this->GetSQLExpr().") = MONTH($sQValue)";
+		case 'Today':
+			return "DATE(".$this->GetSQLExpr().") = CURRENT_DATE()";
+		case '>|':
+			return "DATE(".$this->GetSQLExpr().") > DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
+		case '<|':
+			return "DATE(".$this->GetSQLExpr().") < DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
+		case '=|':
+			return "DATE(".$this->GetSQLExpr().") = DATE_ADD(CURRENT_DATE(), INTERVAL $sQValue DAY)";
+		default:
+			return $this->GetSQLExpr()." = $sQValue";
+		}
+	}
+	
+	public function MakeRealValue($proposedValue)
+	{
+		if (!is_numeric($proposedValue))
+		{
+			return $proposedValue;
+		}
+		else
+		{
+			return date("Y-m-d H:i:s", $proposedValue);
+		}
+		throw new CoreException("Invalid type for a date (found ".gettype($proposedValue)." and accepting string/int/DateTime)");
+		return null;
+	}
+	public function RealValueToSQLValue($value)
+	{
+		if (empty($value))
+		{
+			// Make a valid date for MySQL. TO DO: support NULL as a literal value for fields that can be null.
+			return '0000-00-00 00:00:00';
+		}
+		return $value;
+	}
+	public function SQLValueToRealValue($value)
+	{
+		return $value;
+	}
+
+	public function GetAsHTML($value)
+	{
+		return Str::pure2html($value);
+	}
+
+	public function GetAsXML($value)
+	{
+		return Str::pure2xml($value);
+	}
+
+	public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',')
+	{
+		return str_replace($sSeparator, $sSepEscape, $value);
+	}
+}
+
+// Init static constant once for all (remove when PHP allows real static const)
+AttributeDate::InitStatics();
+
+
+/**
+ * Map a foreign key to an attribute 
+ *  AttributeExternalKey and AttributeExternalField may be an external key
+ *  the difference is that AttributeExternalKey corresponds to a column into the defined table
+ *  where an AttributeExternalField corresponds to a column into another table (class)
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeExternalKey extends AttributeDBFieldVoid
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("targetclass", "is_null_allowed"));
+	}
+
+	public function GetType() {return "Extkey";}
+	public function GetTypeDesc() {return "Link to another object";}
+	public function GetEditClass() {return "ExtKey";}
+	public function GetDBFieldType() {return "INT";}
+
+	public function IsExternalKey($iType = EXTKEY_RELATIVE) {return true;}
+	public function GetTargetClass($iType = EXTKEY_RELATIVE) {return $this->Get("targetclass");}
+	public function GetKeyAttDef($iType = EXTKEY_RELATIVE){return $this;}
+	public function GetKeyAttCode() {return $this->GetCode();} 
+	
+
+	public function GetDefaultValue() {return 0;}
+	public function IsNullAllowed() {return $this->Get("is_null_allowed");}
+
+	public function GetBasicFilterOperators()
+	{
+		return parent::GetBasicFilterOperators();
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		return parent::GetBasicFilterLooseOperator();
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		return parent::GetBasicFilterSQLExpr($sOpCode, $value);
+	} 
+
+	// overloaded here so that an ext key always have the answer to
+	// "what are you possible values?"
+	public function GetValuesDef()
+	{
+		$oValSetDef = $this->Get("allowed_values");
+		if (!$oValSetDef)
+		{
+			// Let's propose every existing value
+			$oValSetDef = new ValueSetObjects($this->GetTargetClass());
+		}
+		return $oValSetDef;
+	} 
+}
+
+/**
+ * An attribute which corresponds to an external key (direct or indirect) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeExternalField extends AttributeDefinition
+{
+
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("extkey_attcode", "target_attcode"));
+	}
+
+	public function GetType() {return "ExtkeyField";}
+	public function GetTypeDesc() {return "Field of an object pointed to by the current object";}
+	public function GetEditClass() {return "ExtField";}
+	public function GetDBFieldType()
+	{
+		// throw new CoreException("external attribute: does it make any sense to request its type ?");  
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetDBFieldType(); 
+	}
+
+	public function IsExternalKey($iType = EXTKEY_RELATIVE)
+	{
+		switch($iType)
+		{
+		case EXTKEY_ABSOLUTE:
+			// see further
+			$oRemoteAtt = $this->GetExtAttDef();
+			return $oRemoteAtt->IsExternalKey($iType);
+
+		case EXTKEY_RELATIVE:
+			return false;
+
+		default:
+			throw new CoreException("Unexpected value for argument iType: '$iType'");
+		}
+	}
+
+	public function GetTargetClass($iType = EXTKEY_RELATIVE)
+	{
+		return $this->GetKeyAttDef($iType)->GetTargetClass();
+	}
+
+	public function IsExternalField() {return true;} 
+	public function GetKeyAttCode() {return $this->Get("extkey_attcode");} 
+	public function GetExtAttCode() {return $this->Get("target_attcode");} 
+
+	public function GetKeyAttDef($iType = EXTKEY_RELATIVE)
+	{
+		switch($iType)
+		{
+		case EXTKEY_ABSOLUTE:
+			// see further
+			$oRemoteAtt = $this->GetExtAttDef();
+			if ($oRemoteAtt->IsExternalField())
+			{
+				return $oRemoteAtt->GetKeyAttDef(EXTKEY_ABSOLUTE);
+			}
+			else if ($oRemoteAtt->IsExternalKey())
+			{
+				return $oRemoteAtt;
+			}
+			return $this->GetKeyAttDef(EXTKEY_RELATIVE); // which corresponds to the code hereafter !
+
+		case EXTKEY_RELATIVE:
+			return MetaModel::GetAttributeDef($this->GetHostClass(), $this->Get("extkey_attcode"));
+
+		default:
+			throw new CoreException("Unexpected value for argument iType: '$iType'");
+		}
+	}
+
+	public function GetExtAttDef()
+	{
+		$oKeyAttDef = $this->GetKeyAttDef();
+		$oExtAttDef = MetaModel::GetAttributeDef($oKeyAttDef->Get("targetclass"), $this->Get("target_attcode"));
+		return $oExtAttDef;
+	}
+
+	public function GetSQLExpr()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetSQLExpr(); 
+	} 
+	public function DBGetUsedFields()
+	{
+		// No field is used but the one defined in the field of the external class
+		// #@# so what ?
+		return array();
+	} 
+
+	public function GetDefaultValue()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetDefaultValue(); 
+	}
+	public function IsNullAllowed()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->IsNullAllowed(); 
+	}
+
+	public function GetBasicFilterOperators()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetBasicFilterOperators(); 
+	}
+	public function GetBasicFilterLooseOperator()
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetBasicFilterLooseOperator(); 
+	}
+
+	public function GetBasicFilterSQLExpr($sOpCode, $value)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetBasicFilterSQLExpr($sOpCode, $value); 
+	} 
+
+	public function MakeRealValue($proposedValue)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->MakeRealValue($proposedValue);
+	}
+	public function RealValueToSQLValue($value)
+	{
+		// This one could be used in case of filtering only
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->RealValueToSQLValue($value);
+	}
+	public function SQLValueToRealValue($value)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->SQLValueToRealValue($value);
+	}
+	public function GetAsHTML($value)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetAsHTML($value);
+	}
+	public function GetAsXML($value)
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetAsXML($value);
+	}
+	public function GetAsCSV($value, $sSeparator = ';', $sSepEscape = ',')
+	{
+		$oExtAttDef = $this->GetExtAttDef();
+		return $oExtAttDef->GetAsCSV($value);
+	}
+}
+
+/**
+ * Map a varchar column to an URL (formats the ouput in HMTL) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class AttributeURL extends AttributeString
+{
+	static protected function ListExpectedParams()
+	{
+		//return parent::ListExpectedParams();
+		return array_merge(parent::ListExpectedParams(), array("target", "label"));
+	}
+
+	public function GetType() {return "Url";}
+	public function GetTypeDesc() {return "Absolute or relative URL as a text string";}
+	public function GetEditClass() {return "String";}
+
+	public function GetAsHTML($sValue)
+	{
+		$sTarget = $this->Get("target");
+		if (empty($sTarget)) $sTarget = "_blank";
+		$sLabel = Str::pure2html($sValue);
+		if (strlen($sLabel) > 40)
+		{
+			// Truncate the length to about 40 characters, by removing the middle
+			$sLabel = substr($sLabel, 0, 25).'...'.substr($sLabel, -15);
+		}
+		return "<a target=\"$sTarget\" href=\"$sValue\">$sLabel</a>";
+	}
+}
+
+?>

+ 439 - 0
core/bulkchange.class.inc.php

@@ -0,0 +1,439 @@
+<?php
+
+/**
+ * BulkChange
+ * Interpret a given data set and update the DB accordingly (fake mode avail.) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class BulkChangeException extends CoreException
+{
+}
+
+/**
+ * CellChangeSpec
+ * A series of classes, keeping the information about a given cell: could it be changed or not (and why)?  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class CellChangeSpec
+{
+	protected $m_proposedValue;
+
+	public function __construct($proposedValue)
+	{
+		$this->m_proposedValue = $proposedValue;
+	}
+
+	public function GetValue()
+	{
+		return $this->m_proposedValue; 
+	}
+
+	abstract public function GetDescription();
+}
+
+
+class CellChangeSpec_Void extends CellChangeSpec
+{
+	public function GetDescription()
+	{
+		return $this->GetValue();
+	}
+}
+
+class CellChangeSpec_Unchanged extends CellChangeSpec
+{
+	public function GetDescription()
+	{
+		return $this->GetValue()." (unchanged)";
+	}
+}
+
+class CellChangeSpec_Init extends CellChangeSpec
+{
+	public function GetDescription()
+	{
+		return $this->GetValue();
+	}
+}
+
+class CellChangeSpec_Modify extends CellChangeSpec
+{
+	protected $m_previousValue;
+
+	public function __construct($proposedValue, $previousValue)
+	{
+		$this->m_previousValue = $previousValue;
+		parent::__construct($proposedValue);
+	}
+
+	public function GetDescription()
+	{
+		return $this->GetValue()." (previous: ".$this->m_previousValue.")";
+	}
+}
+
+class CellChangeSpec_Issue extends CellChangeSpec_Modify
+{
+	protected $m_sReason;
+
+	public function __construct($proposedValue, $previousValue, $sReason)
+	{
+		$this->m_sReason = $sReason;
+		parent::__construct($proposedValue, $previousValue);
+	}
+
+	public function GetDescription()
+	{
+		if (is_null($this->m_proposedValue))
+		{
+			return 'Could not be changed - reason: '.$this->m_sReason;
+		}
+		return 'Could not be changed to "'.$this->GetValue().'" - reason: '.$this->m_sReason.' (previous: '.$this->m_previousValue.')';
+	}
+}
+
+
+/**
+ * RowStatus
+ * A series of classes, keeping the information about a given row: could it be changed or not (and why)?  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class RowStatus
+{
+	public function __construct()
+	{
+	}
+
+	abstract public function GetDescription();
+}
+
+class RowStatus_NoChange extends RowStatus
+{
+	public function GetDescription()
+	{
+		return "unchanged";
+	}
+}
+
+class RowStatus_NewObj extends RowStatus
+{
+	protected $m_iObjKey;
+
+	public function __construct($iObjKey = null)
+	{
+		$this->m_iObjKey = $iObjKey;
+	}
+
+	public function GetDescription()
+	{
+		if (is_null($this->m_iObjKey))
+		{
+			return "Create";
+		}
+		else
+		{
+			return 'Created ('.$this->m_iObjKey.')';
+		}	
+	}
+}
+
+class RowStatus_Modify extends RowStatus
+{
+	protected $m_iChanged;
+
+	public function __construct($iChanged)
+	{
+		$this->m_iChanged = $iChanged;
+	}
+
+	public function GetDescription()
+	{
+		return "update ".$this->m_iChanged." cols";
+	}
+}
+
+class RowStatus_Issue extends RowStatus
+{
+	protected $m_sReason;
+
+	public function __construct($sReason)
+	{
+		$this->m_sReason = $sReason;
+	}
+
+	public function GetDescription()
+	{
+		return 'Skipped - reason:'.$this->m_sReason;
+	}
+}
+
+
+/**
+ * BulkChange
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class BulkChange
+{
+	protected $m_sClass;
+	protected $m_aData;
+	// Note: hereafter, iCol maybe actually be any acceptable key (string)
+	// #@# todo: rename the variables to sColIndex
+	protected $m_aAttList; // attcode => iCol
+	protected $m_aReconcilKeys;// iCol => attcode
+	protected $m_aExtKeys;	// aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
+
+	public function __construct($sClass, $aData, $aAttList, $aReconcilKeys, $aExtKeys)
+	{
+		$this->m_sClass = $sClass;
+		$this->m_aData = $aData;
+		$this->m_aAttList = $aAttList;
+		$this->m_aReconcilKeys = $aReconcilKeys;
+		$this->m_aExtKeys = $aExtKeys;
+	}
+
+	protected function PrepareObject(&$oTargetObj, $aRowData, &$aErrors)
+	{
+		$aResults = array();
+		$aErrors = array();
+	
+		// External keys reconciliation
+		//
+		foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
+		{
+			$oExtKey = MetaModel::GetAttributeDef(get_class($oTargetObj), $sAttCode);
+			$oReconFilter = new CMDBSearchFilter($oExtKey->GetTargetClass());
+			foreach ($aKeyConfig as $sForeignAttCode => $iCol)
+			{
+				// The foreign attribute is one of our reconciliation key
+				$sFieldId = MakeExtFieldSelectValue($sAttCode, $sForeignAttCode);
+				$oReconFilter->AddCondition($sForeignAttCode, $aRowData[$iCol], '=');
+				$aResults["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]);
+			}
+			$oExtObjects = new CMDBObjectSet($oReconFilter);
+			switch($oExtObjects->Count())
+			{
+			case 0:
+				$aErrors[$sAttCode] = "Object not found";
+				$aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), 'Object not found');
+				break;
+			case 1:
+				// Do change the external key attribute
+				$oForeignObj = $oExtObjects->Fetch();
+				$oTargetObj->Set($sAttCode, $oForeignObj->GetKey());
+	
+				// Report it
+				if (array_key_exists($sAttCode, $oTargetObj->ListChanges()))
+				{
+					if ($oTargetObj->IsNew())
+					{
+						$aResults[$sAttCode]= new CellChangeSpec_Init($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode));
+					}
+					else
+					{
+						$aResults[$sAttCode]= new CellChangeSpec_Modify($oForeignObj->GetKey(), $oTargetObj->Get($sAttCode), $oTargetObj->GetOriginal($sAttCode));
+					}
+				}
+				else
+				{
+					$aResults[$sAttCode]= new CellChangeSpec_Unchanged($oTargetObj->Get($sAttCode));
+				}
+				break;
+			default:
+				$aErrors[$sAttCode] = "Found ".$oExtObjects->Count()." matches";
+				$aResults[$sAttCode]= new CellChangeSpec_Issue(null, $oTargetObj->Get($sAttCode), "Found ".$oExtObjects->Count()." matches");
+			}
+		}	
+	
+		// Set the object attributes
+		//
+		foreach ($this->m_aAttList as $sAttCode => $iCol)
+		{
+			if (!$oTargetObj->CheckValue($sAttCode, $aRowData[$iCol]))
+			{
+				$aErrors[$sAttCode] = "Unexpected value";
+			}
+			else
+			{
+				$oTargetObj->Set($sAttCode, $aRowData[$iCol]);
+			}
+		}
+	
+		// Reporting on fields
+		//
+		$aChangedFields = $oTargetObj->ListChanges();
+		foreach ($this->m_aAttList as $sAttCode => $iCol)
+		{
+			if (isset($aErrors[$sAttCode]))
+			{
+				$aResults["col$iCol"]= new CellChangeSpec_Issue($aRowData[$iCol], $oTargetObj->Get($sAttCode), $aErrors[$sAttCode]);
+			}
+			elseif (array_key_exists($sAttCode, $aChangedFields))
+			{
+				$originalValue = $oTargetObj->GetOriginal($sAttCode);
+				if ($oTargetObj->IsNew())
+				{
+					$aResults["col$iCol"]= new CellChangeSpec_Init($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue);
+				}
+				else
+				{
+					$aResults["col$iCol"]= new CellChangeSpec_Modify($aRowData[$iCol], $oTargetObj->Get($sAttCode), $originalValue);
+				}
+			}
+			else
+			{
+				// By default... nothing happens
+				$aResults["col$iCol"]= new CellChangeSpec_Void($aRowData[$iCol]);
+			}
+		}
+	
+		// Checks
+		//
+		if (!$oTargetObj->CheckConsistency())
+		{
+			$aErrors["GLOBAL"] = "Attributes not consistent with each others";
+		}
+		return $aResults;
+	}
+	
+	
+	protected function CreateObject(&$aResult, $iRow, $aRowData, CMDBChange $oChange = null)
+	{
+		$oTargetObj = MetaModel::NewObject($this->m_sClass);
+		$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
+	
+		if (count($aErrors) > 0)
+		{
+			$sErrors = implode(', ', $aErrors);
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)");
+			return;
+		}
+	
+		// Check that any external key will have a value proposed
+		// Could be said once for all rows !!!
+		foreach(MetaModel::ListAttributeDefs($this->m_sClass) as $sAttCode=>$oAtt)
+		{
+			if (!$oAtt->IsExternalKey()) continue;
+		}
+	
+		// Optionaly record the results
+		//
+		if ($oChange)
+		{
+			$newID = $oTargetObj->DBInsertTrackedNoReload($oChange);
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj($newID);
+		}
+		else
+		{
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_NewObj();
+		}
+	
+	}
+	
+	protected function UpdateObject(&$aResult, $iRow, $oTargetObj, $aRowData, CMDBChange $oChange = null)
+	{
+		$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
+	
+		// Reporting
+		//
+		if (count($aErrors) > 0)
+		{
+			$sErrors = implode(', ', $aErrors);
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue("Unexpected attribute value(s)");
+			return;
+		}
+	
+		$aChangedFields = $oTargetObj->ListChanges();
+		if (count($aChangedFields) > 0)
+		{
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_Modify(count($aChangedFields));
+	
+			// Optionaly record the results
+			//
+			if ($oChange)
+			{
+				$oTargetObj->DBUpdateTracked($oChange);
+			}
+		}
+		else
+		{
+			$aResult[$iRow]["__STATUS__"] = new RowStatus_NoChange();
+		}
+	}
+	
+	public function Process(CMDBChange $oChange = null)
+	{
+		// Note: $oChange can be null, in which case the aim is to check what would be done
+	
+		// Compute the results
+		//
+		$aResult = array();
+		foreach($this->m_aData as $iRow => $aRowData)
+		{
+			$oReconciliationFilter = new CMDBSearchFilter($this->m_sClass);
+			foreach($this->m_aReconcilKeys as $sAttCode)
+			{
+				$iCol = $this->m_aAttList[$sAttCode];
+				$oReconciliationFilter->AddCondition($sAttCode, $aRowData[$iCol], '=');
+			}
+			$oReconciliationSet = new CMDBObjectSet($oReconciliationFilter);
+			switch($oReconciliationSet->Count())
+			{
+			case 0:
+				$this->CreateObject($aResult, $iRow, $aRowData, $oChange);
+				// $aResult[$iRow]["__STATUS__"]=> set in CreateObject
+				$aResult[$iRow]["__RECONCILIATION__"] = "Object not found";
+				break;
+			case 1:
+				$oTargetObj = $oReconciliationSet->Fetch();
+				$this->UpdateObject($aResult, $iRow, $oTargetObj, $aRowData, $oChange);
+				$aResult[$iRow]["__RECONCILIATION__"] = "Found a match ".$oTargetObj->GetKey();
+				// $aResult[$iRow]["__STATUS__"]=> set in UpdateObject
+				break;
+			default:
+				foreach ($this->m_aAttList as $sAttCode => $iCol)
+				{
+					$aResult[$iRow]["col$iCol"]= $aRowData[$iCol];
+				}
+				$aResult[$iRow]["__RECONCILIATION__"] = "Found ".$oReconciliationSet->Count()." matches";
+				$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue("ambiguous reconciliation");
+			}
+	
+			// Whatever happened, do report the reconciliation values
+			foreach($this->m_aReconcilKeys as $sAttCode)
+			{
+				$iCol = $this->m_aAttList[$sAttCode];
+				$aResult[$iRow]["col$iCol"] = new CellChangeSpec_Void($aRowData[$iCol]);
+			}
+		}
+		return $aResult;
+	}
+}
+
+
+?>

+ 44 - 0
core/cmdbchange.class.inc.php

@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * A change as requested/validated at once by user, may groups many atomic changes 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class CMDBChange extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "core/cmdb",
+			"name" => "change",
+			"description" => "Changes tracking",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "date",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_change",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeDate("date", array("label"=>"date", "description"=>"date and time at which the changes have been recorded", "allowed_values"=>null, "sql"=>"date", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("userinfo", array("label"=>"misc. info", "description"=>"caller's defined information", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+
+		//MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("date");
+		MetaModel::Init_AddFilterFromAttribute("userinfo");
+	}
+
+}
+
+?>

+ 167 - 0
core/cmdbchangeop.class.inc.php

@@ -0,0 +1,167 @@
+<?php
+
+/**
+ * Various atomic change operations, to be tracked 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class CMDBChangeOp extends DBObject
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "core/cmdb",
+			"name" => "change operation",
+			"description" => "Change operations tracking",
+			"key_type" => "autoincrement",
+			"key_label" => "",
+			"name_attcode" => "change",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_changeop",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "optype",
+		);
+		MetaModel::Init_Params($aParams);
+		//MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeExternalKey("change", array("label"=>"change", "description"=>"change", "allowed_values"=>null, "sql"=>"changeid", "targetclass"=>"CMDBChange", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("date", array("label"=>"date", "description"=>"date and time of the change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"date")));
+		MetaModel::Init_AddAttribute(new AttributeExternalField("userinfo", array("label"=>"user", "description"=>"who made this change", "allowed_values"=>null, "extkey_attcode"=>"change", "target_attcode"=>"userinfo")));
+		MetaModel::Init_AddAttribute(new AttributeString("objclass", array("label"=>"object class", "description"=>"object class", "allowed_values"=>null, "sql"=>"objclass", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("objkey", array("label"=>"object id", "description"=>"object id", "allowed_values"=>null, "sql"=>"objkey", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+
+		MetaModel::Init_AddFilterFromAttribute("objclass");
+		MetaModel::Init_AddFilterFromAttribute("objkey");
+		MetaModel::Init_AddFilterFromAttribute("date");
+		MetaModel::Init_AddFilterFromAttribute("userinfo");
+	}
+}
+
+
+
+/**
+ * Record the creation of an object  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class CMDBChangeOpCreate extends CMDBChangeOp
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "core/cmdb",
+			"name" => "object creation",
+			"description" => "Object creation tracking",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "change",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_changeop_create",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+
+		MetaModel::Init_InheritFilters();
+	}
+}
+
+
+/**
+ * Record the deletion of an object 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class CMDBChangeOpDelete extends CMDBChangeOp
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "core/cmdb",
+			"name" => "object deletion",
+			"description" => "Object deletion tracking",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "change",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_changeop_delete",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+
+		MetaModel::Init_InheritFilters();
+	}
+}
+
+
+/**
+ * Record the modification of an attribute 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class CMDBChangeOpSetAttribute extends CMDBChangeOp
+{
+	public static function Init()
+	{
+		$aParams = array
+		(
+			"category" => "core/cmdb",
+			"name" => "object change",
+			"description" => "Object properties change tracking",
+			"key_type" => "",
+			"key_label" => "",
+			"name_attcode" => "change",
+			"state_attcode" => "",
+			"reconc_keys" => array(),
+			"db_table" => "priv_changeop_setatt",
+			"db_key_field" => "id",
+			"db_finalclass_field" => "",
+		);
+		MetaModel::Init_Params($aParams);
+		MetaModel::Init_InheritAttributes();
+		MetaModel::Init_AddAttribute(new AttributeString("attcode", array("label"=>"Attribute", "description"=>"code of the modified property", "allowed_values"=>null, "sql"=>"attcode", "default_value"=>"", "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("oldvalue", array("label"=>"Previous value", "description"=>"previous value of the attribute", "allowed_values"=>null, "sql"=>"oldvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeString("newvalue", array("label"=>"New value", "description"=>"new value of the attribute", "allowed_values"=>null, "sql"=>"newvalue", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+
+		MetaModel::Init_InheritFilters();
+		MetaModel::Init_AddFilterFromAttribute("attcode");
+		MetaModel::Init_AddFilterFromAttribute("oldvalue");
+		MetaModel::Init_AddFilterFromAttribute("newvalue");
+		
+		// Display lists
+		MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'attcode', 'oldvalue', 'newvalue')); // Attributes to be displayed for a list
+	}
+}
+
+?>

+ 432 - 0
core/cmdbobject.class.inc.php

@@ -0,0 +1,432 @@
+<?php
+
+/**
+ * cmdbObjectClass
+ * the file to include, then the core is yours
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+require_once('coreexception.class.inc.php');
+
+require_once('config.class.inc.php');
+
+require_once('attributedef.class.inc.php');
+require_once('filterdef.class.inc.php');
+require_once('stimulus.class.inc.php');
+require_once('valuesetdef.class.inc.php');
+require_once('MyHelpers.class.inc.php');
+
+require_once('expression.class.inc.php');
+
+require_once('cmdbsource.class.inc.php');
+require_once('sqlquery.class.inc.php');
+require_once('oql/oqlquery.class.inc.php');
+require_once('oql/oqlexception.class.inc.php');
+require_once('oql/oql-parser.php');
+require_once('oql/oql-lexer.php');
+require_once('oql/oqlinterpreter.class.inc.php');
+
+require_once('dbobject.class.php');
+require_once('dbobjectsearch.class.php');
+require_once('dbobjectset.class.php');
+
+require_once('cmdbchange.class.inc.php');
+require_once('cmdbchangeop.class.inc.php');
+
+require_once('csvparser.class.inc.php');
+require_once('bulkchange.class.inc.php');
+
+require_once('userrights.class.inc.php');
+
+//
+// Error handling
+// To be finalized... or removed ?
+//
+function cmdbErrorHandler($errno, $errstr, $errfile, $errline)
+{
+//		font-family: Courier-New, Courier, Arial, Helevtica;
+	$sErrorStyle = "
+		background-color: #ffaaaa;
+		color: #000000;
+		border: 1px dashed #000000;
+		padding: 0.25em;
+		margin-top: 1em;
+	";
+	$sCallStackStyle = "
+		font-size: smaller;
+		background-color: #ffcccc;
+		color: #000000;
+		border: 1px dashed #000000;
+		padding: 0.25em;
+		margin-top: 1em;
+	";
+
+	switch ($errno)
+	{
+	case E_USER_ERROR:
+	case E_ERROR:
+		echo "<div style=\"$sErrorStyle\">\n";
+		echo "<b>Error</b> [$errno] $errstr<br />\n";
+		echo "<div style=\"$sCallStackStyle\">\n";
+		MyHelpers::dump_callstack(1);
+		echo "</div>\n";
+		echo "Hereafter the biz model internals:<br />\n";
+		echo "<pre>\n";
+		MetaModel::static_var_dump();
+		echo "</pre>\n";
+		echo "Aborting...<br />\n";
+		echo "</div>\n";
+		exit(1);
+		break;
+	case E_USER_WARNING:
+	case E_WARNING:
+		echo "<div style=\"background-color:#FAA;\">\n";
+		echo "<b>Warning</b> [$errno] $errstr<br />\n";
+		echo "<div style=\"background-color:#FCC;\">\n";
+		MyHelpers::dump_callstack(1);
+		echo "</div>\n";
+		echo "</div>\n";
+		break;
+	case E_USER_NOTICE:
+	case E_NOTICE:
+		echo "<div style=\"background-color:#FAA;\">\n";
+		echo "<b>Notice</b> [$errno] $errstr<br />\n";
+		echo "<div style=\"background-color:#FCC;\">\n";
+		MyHelpers::dump_callstack(1);
+		echo "</div>\n";
+		echo "</div>\n";
+		break;
+	default:
+		echo "Unknown error type: [$errno] $errstr<br />\n";
+		MyHelpers::dump_callstack(1);
+		break;
+	}
+}
+
+error_reporting(E_ALL | E_STRICT);
+//set_error_handler("cmdbErrorHandler");
+
+
+
+//
+//
+//
+
+
+/**
+ * A persistent object, which changes are accurately recorded
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class CMDBObject extends DBObject
+{
+	protected $m_datCreated;
+	protected $m_datUpdated;
+	protected static $m_oCurrChange = null;
+
+
+	private function RecordObjCreation(CMDBChange $oChange)
+	{
+		$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpCreate");
+		$oMyChangeOp->Set("change", $oChange->GetKey());
+		$oMyChangeOp->Set("objclass", get_class($this));
+		$oMyChangeOp->Set("objkey", $this->GetKey());
+		$iId = $oMyChangeOp->DBInsertNoReload();
+	}
+	private function RecordObjDeletion(CMDBChange $oChange, $objkey)
+	{
+		$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpDelete");
+		$oMyChangeOp->Set("change", $oChange->GetKey());
+		$oMyChangeOp->Set("objclass", get_class($this));
+		$oMyChangeOp->Set("objkey", $objkey);
+		$iId = $oMyChangeOp->DBInsertNoReload();
+	}
+	private function RecordAttChanges(CMDBChange $oChange, array $aValues)
+	{
+		// $aValues is an array of $sAttCode => $value
+		//
+		foreach ($aValues as $sAttCode=> $value)
+		{
+			$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+			if ($oAttDef->IsLinkSet()) continue; // #@# temporary
+			$oMyChangeOp = MetaModel::NewObject("CMDBChangeOpSetAttribute");
+			$oMyChangeOp->Set("change", $oChange->GetKey());
+			$oMyChangeOp->Set("objclass", get_class($this));
+			$oMyChangeOp->Set("objkey", $this->GetKey());
+			$oMyChangeOp->Set("attcode", $sAttCode);
+			$oMyChangeOp->Set("oldvalue", $this->GetOriginal($sAttCode));
+			$oMyChangeOp->Set("newvalue", $value);
+			$iId = $oMyChangeOp->DBInsertNoReload();
+		}
+	}
+
+	public function DBInsert()
+	{
+		if(!is_object(self::$m_oCurrChange))
+		{
+			throw new CoreException("DBInsert() could not be used here, please use DBInsertTracked() instead");
+		}
+		return $this->DBInsertTracked_Internal();
+	}
+
+	public function DBInsertTracked(CMDBChange $oChange)
+	{
+		self::$m_oCurrChange = $oChange;
+		$ret = $this->DBInsertTracked_Internal();
+		self::$m_oCurrChange = null;
+		return $ret;
+	}
+
+	public function DBInsertTrackedNoReload(CMDBChange $oChange)
+	{
+		self::$m_oCurrChange = $oChange;
+		$ret = $this->DBInsertTracked_Internal(true);
+		self::$m_oCurrChange = null;
+		return $ret;
+	}
+
+	protected function DBInsertTracked_Internal($bDoNotReload = false)
+	{
+		if ($bDoNotReload)
+		{
+			$ret = parent::DBInsertNoReload();
+		}
+		else
+		{
+			$ret = parent::DBInsert();
+		}
+		$this->RecordObjCreation(self::$m_oCurrChange);
+		return $ret;
+	}
+
+	public function DBClone($newKey = null)
+	{
+		if(!self::$m_oCurrChange)
+		{
+			throw new CoreException("DBClone() could not be used here, please use DBCloneTracked() instead");
+		}
+		return $this->DBCloneTracked_Internal();
+	}
+
+	public function DBCloneTracked(CMDBChange $oChange, $newKey = null)
+	{
+		self::$m_oCurrChange = $oChange;
+		$this->DBCloneTracked_Internal($newKey);
+		self::$m_oCurrChange = null;
+	}
+
+	protected function DBCloneTracked_Internal($newKey = null)
+	{
+		$newKey = parent::DBClone($newKey);
+		$oClone = MetaModel::GetObject(get_class($this), $newKey); 
+
+		$oClone->RecordObjCreation(self::$m_oCurrChange);
+		return $newKey;
+	}
+
+	public function DBUpdate()
+	{
+		if(!self::$m_oCurrChange)
+		{
+			throw new CoreException("DBUpdate() could not be used here, please use DBUpdateTracked() instead");
+		}
+		return $this->DBUpdateTracked_internal();
+	}
+
+	public function DBUpdateTracked(CMDBChange $oChange)
+	{
+		self::$m_oCurrChange = $oChange;
+		$this->DBUpdateTracked_Internal();
+		self::$m_oCurrChange = null;
+	}
+
+	protected function DBUpdateTracked_Internal()
+	{
+		// Copy the changes list before the update (the list should be reset afterwards)
+		$aChanges = $this->ListChanges();
+		if (count($aChanges) == 0)
+		{
+			throw new CoreWarning("Attempting to update an unchanged object");
+			return;
+		}
+
+		$ret = parent::DBUpdate();
+		$this->RecordAttChanges(self::$m_oCurrChange, $aChanges);
+		return $ret;
+	}
+
+	public function DBDelete()
+	{
+		if(!self::$m_oCurrChange)
+		{
+			throw new CoreException("DBDelete() could not be used here, please use DBDeleteTracked() instead");
+		}
+		return $this->DBDeleteTracked_Internal();
+	}
+
+	public function DBDeleteTracked(CMDBChange $oChange)
+	{
+		self::$m_oCurrChange = $oChange;
+		$this->DBDeleteTracked_Internal();
+		self::$m_oCurrChange = null;
+	}
+
+	protected function DBDeleteTracked_Internal()
+	{
+		$prevkey = $this->GetKey();
+		$ret = parent::DBDelete();
+		$this->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
+		return $ret;
+	}
+
+	public static function BulkDelete(DBObjectSearch $oFilter)
+	{
+		if(!self::$m_oCurrChange)
+		{
+			throw new CoreException("BulkDelete() could not be used here, please use BulkDeleteTracked() instead");
+		}
+		return $this->BulkDeleteTracked_Internal($oFilter);
+	}
+
+	public static function BulkDeleteTracked(CMDBChange $oChange, DBObjectSearch $oFilter)
+	{
+		self::$m_oCurrChange = $oChange;
+		$this->BulkDeleteTracked_Internal($oFilter);
+		self::$m_oCurrChange = null;
+	}
+
+	protected static function BulkDeleteTracked_Internal(DBObjectSearch $oFilter)
+	{
+		throw new CoreWarning("Change tracking not tested for bulk operations");
+
+		// Get the list of objects to delete (and record data before deleting the DB records)
+		$oObjSet = new CMDBObjectSet($oFilter);
+		$aObjAndKeys = array(); // array of pkey=>object
+		while ($oItem = $oObjSet->Fetch())
+		{
+			$aObjAndKeys[$oItem->GetKey()] = $oItem;
+		}
+		$oObjSet->FreeResult();
+
+		// Delete in one single efficient query
+		$ret = parent::BulkDelete($oFilter);
+		// Record... in many queries !!!
+		foreach($aObjAndKeys as $prevkey=>$oItem)
+		{
+			$oItem->RecordObjDeletion(self::$m_oCurrChange, $prevkey);
+		}
+		return $ret;
+	}
+
+	public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
+	{
+		if(!self::$m_oCurrChange)
+		{
+			throw new CoreException("BulkUpdate() could not be used here, please use BulkUpdateTracked() instead");
+		}
+		return $this->BulkUpdateTracked_Internal($oFilter, $aValues);
+	}
+
+	public static function BulkUpdateTracked(CMDBChange $oChange, DBObjectSearch $oFilter, array $aValues)
+	{
+		self::$m_oCurrChange = $oChange;
+		$this->BulkUpdateTracked_Internal($oFilter, $aValues);
+		self::$m_oCurrChange = null;
+	}
+
+	protected static function BulkUpdateTracked_Internal(DBObjectSearch $oFilter, array $aValues)
+	{
+		// $aValues is an array of $sAttCode => $value
+
+		// Get the list of objects to update (and load it before doing the change)
+		$oObjSet = new CMDBObjectSet($oFilter);
+		$oObjSet->Load();
+
+		// Update in one single efficient query
+		$ret = parent::BulkUpdate($oFilter, $aValues);
+
+		// Record... in many queries !!!
+		while ($oItem = $oObjSet->Fetch())
+		{
+			$aChangedValues = $oItem->ListChangedValues($aValues);
+			$oItem->RecordAttChanges(self::$m_oCurrChange, $aChangedValues);
+		}
+		return $ret;
+	}
+}
+
+
+
+/**
+ * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design!
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class CMDBObjectSet extends DBObjectSet
+{
+	// this is the public interface (?)
+	
+	// I have to define those constructors here... :-(
+	// just to get the right object class in return.
+	// I have to think again to those things: maybe it will work fine if a have a constructor define here (?)
+	
+	static public function FromScratch($sClass)
+	{
+		$oFilter = new CMDBSearchFilter($sClass);
+		$oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE
+		// NOTE: THIS DOES NOT WORK IF m_bLoaded is private...
+		// BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!!
+		$oRetSet->m_bLoaded = true; // no DB load
+		return $oRetSet;
+	} 
+
+	static public function FromArray($sClass, $aObjects)
+	{
+		$oFilter = new CMDBSearchFilter($sClass);
+		$oRetSet = new CMDBObjectSet($oFilter); // THE ONLY DIFF IS HERE
+		// NOTE: THIS DOES NOT WORK IF m_bLoaded is private...
+		// BUT IT THAT CASE YOU DO NOT GET ANY ERROR !!!!!
+		$oRetSet->m_bLoaded = true; // no DB load
+		$oRetSet->AddObjectArray($aObjects);
+		return $oRetSet;
+	} 
+	
+}
+
+/**
+ * TODO: investigate how to get rid of this class that was made to workaround some language limitation... or a poor design!
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class CMDBSearchFilter extends DBObjectSearch
+{
+	// this is the public interface (?)
+}
+
+
+?>

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

@@ -0,0 +1,420 @@
+<?php
+
+/**
+ * CMDBSource
+ * database access wrapper 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+require_once('MyHelpers.class.inc.php');
+
+class MySQLException extends CoreException
+{
+	public function __construct($sIssue, $aContext)
+	{
+		$aContext['mysql_error'] = mysql_error();
+		parent::__construct($sIssue, $aContext);
+	}
+}
+
+
+class CMDBSource
+{
+	protected static $m_sDBHost;
+	protected static $m_sDBUser;
+	protected static $m_sDBPwd;
+	protected static $m_sDBName;
+	protected static $m_resDBLink;
+
+	public static function Init($sServer, $sUser, $sPwd, $sSource = '')
+	{
+		self::$m_sDBHost = $sServer;
+		self::$m_sDBUser = $sUser;
+		self::$m_sDBPwd = $sPwd;
+		self::$m_sDBName = $sSource;
+		if (!self::$m_resDBLink = @mysql_pconnect($sServer, $sUser, $sPwd))
+		{
+			throw new MySQLException('Could not connect to the DB server', array('host'=>$sServer, 'user'=>$sUser));
+		}
+		if (!empty($sSource))
+		{
+			if (!mysql_select_db($sSource, self::$m_resDBLink))
+			{
+				throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
+			}
+		}
+	}
+
+	public static function ListDB()
+	{
+		$aDBs = self::QueryToCol('SHOW DATABASES', 'Database');
+		// Show Database does return the DB names in lower case
+		return $aDBs;
+	}
+
+	public static function IsDB($sSource)
+	{
+		try
+		{
+			$aDBs = self::ListDB();
+			foreach($aDBs as $sDBName)
+			{
+			// perform a case insensitive test because on Windows the table names become lowercase :-(
+				if (strtolower($sDBName) == strtolower($sSource)) return true;
+			}
+			return false;
+		}
+		catch(Exception $e)
+		{
+			// In case we don't have rights to enumerate the databases
+			// Let's try to connect directly
+			return @mysql_select_db($sSource, self::$m_resDBLink);
+		}
+
+	}
+
+	public static function GetDBVersion()
+	{
+		$aVersions = self::QueryToCol('SELECT Version() as version', 'version');
+		return $aVersions[0];
+	}
+	
+	public static function SelectDB($sSource)
+	{
+		if (!mysql_select_db($sSource, self::$m_resDBLink))
+		{
+			throw new MySQLException('Could not select DB', array('db_name'=>$sSource));
+		}
+		self::$m_sDBName = $sSource;
+	}
+
+	public static function CreateDB($sSource)
+	{
+		self::Query("CREATE DATABASE `$sSource`");
+		self::SelectDB($sSource);
+	}
+
+	public static function DropDB($sDBToDrop = '')
+	{
+		if (empty($sDBToDrop))
+		{
+			$sDBToDrop = self::$m_sDBName;
+		}
+		self::Query("DROP DATABASE `$sDBToDrop`");
+		if ($sDBToDrop == self::$m_sDBName)
+		{
+			self::$m_sDBName = '';
+		}
+	}
+
+	public static function CreateTable($sQuery)
+	{
+		$res = self::Query($sQuery);
+		self::_TablesInfoCacheReset(); // reset the table info cache!
+		return $res;
+	}
+
+	public static function DropTable($sTable)
+	{
+		$res = self::Query("DROP TABLE `$sTable`");
+		self::_TablesInfoCacheReset(true); // reset the table info cache!
+		return $res;
+	}
+
+	public static function DBHost() {return self::$m_sDBHost;}
+	public static function DBUser() {return self::$m_sDBUser;}
+	public static function DBPwd() {return self::$m_sDBPwd;}
+	public static function DBName() {return self::$m_sDBName;}
+
+	public static function Quote($value, $bAlways = false, $cQuoteStyle = "'")
+	{
+		// Quote variable and protect against SQL injection attacks
+		// Code found in the PHP documentation: quote_smart($value)
+
+		// bAlways should be set to true when the purpose is to create a IN clause,
+		// otherwise and if there is a mix of strings and numbers, the clause
+		// would always be false
+
+		if (is_array($value))
+		{
+			$aRes = array();
+			foreach ($value as $key => $itemvalue)
+			{
+				$aRes[$key] = self::Quote($itemvalue, $bAlways, $cQuoteStyle);
+			}
+			return $aRes;
+		}
+
+		// Stripslashes
+		if (get_magic_quotes_gpc())
+		{
+			$value = stripslashes($value);
+		}
+		// Quote if not a number or a numeric string
+		if ($bAlways || !is_numeric($value))
+		{
+			$value = $cQuoteStyle . mysql_real_escape_string($value, self::$m_resDBLink) . $cQuoteStyle;
+		}
+		return $value;
+	}
+
+	public static function Query($sSQLQuery)
+	{
+		// Add info into the query as a comment, for easier error tracking
+		//
+		//if ($user_contact)	$aTraceInf['userID'] = $user_contact->get_key();
+		//$aTraceInf['file'] = __FILE__;
+		if ($_SERVER['REQUEST_URI'])	$aTraceInf['requestURI'] = $_SERVER['REQUEST_URI'];
+		$i = 0;
+		foreach(debug_backtrace() as $aCallData) 
+		{
+			$sClass = key_exists("class", $aCallData) ? $aCallData["class"]."::" : "";
+	  		//if ($aCallData['function'] !== 'mysql_simple_query' AND $sClass !== 'r2_set::')
+	  		//{
+	  			if ($i == 3) break;
+				$aTraceInf['function'.$i] =  $sClass.$aCallData["function"]." on line ".$aCallData['line'];
+				$i++;
+	  		//}
+	  	}
+	  	// disabled until we need it really!
+		// $sSQLQuery = $sSQLQuery.MyHelpers::MakeSQLComment($aTraceInf);
+	  
+		$mu_t1 = MyHelpers::getmicrotime();
+		$result = mysql_query($sSQLQuery, self::$m_resDBLink);
+		if (!$result) 
+		{
+			throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
+		}
+		$mu_t2 = MyHelpers::getmicrotime();
+		// #@# todo - query_trace($sSQLQuery, $mu_t2 - $mu_t1);
+	
+		return $result;
+	}
+
+	public static function GetInsertId()
+	{
+		return mysql_insert_id(self::$m_resDBLink);
+	}
+	public static function InsertInto($sSQLQuery)
+	{
+		if (self::Query($sSQLQuery))
+		{
+			return self::GetInsertId();
+		}
+		return false;
+	}
+
+	public static function QueryToArray($sSql)
+	{
+		$aData = array();
+		$result = mysql_query($sSql, self::$m_resDBLink);
+		if (!$result)
+		{
+			throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
+		}
+		while ($aRow = mysql_fetch_array($result, MYSQL_BOTH))
+		{
+			$aData[] = $aRow;
+		}
+		mysql_free_result($result);
+		return $aData;
+	}
+
+	public static function QueryToCol($sSql, $col)
+	{
+		$aColumn = array();
+		$aData = self::QueryToArray($sSql);
+		foreach($aData as $aRow)
+		{
+			@$aColumn[] = $aRow[$col];
+		}
+		return $aColumn;
+	}
+
+	public static function ExplainQuery($sSql)
+	{
+		$aData = array();
+		$result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink);
+		if (!$result)
+		{
+			throw new MySQLException('Failed to issue SQL query', array('query' => $sSql));
+		}
+
+		$aNames = array();
+		for ($i = 0; $i < mysql_num_fields($result) ; $i++)
+		{
+		   $meta = mysql_fetch_field($result, $i);
+		   if (!$meta)
+		   {
+				throw new MySQLException('mysql_fetch_field: No information available', array('query'=>$sSql, 'i'=>$i));
+		   }
+		   else
+		   {
+		   		$aNames[] = $meta->name;
+		   }
+		}
+
+		$aData[] = $aNames;
+		while ($aRow = mysql_fetch_array($result, MYSQL_ASSOC))
+		{
+			$aData[] = $aRow;
+		}
+		mysql_free_result($result);
+		return $aData;
+	}
+
+	public static function TestQuery($sSql)
+	{
+		$result = mysql_query("EXPLAIN $sSql", self::$m_resDBLink);
+		if (!$result)
+		{
+			return mysql_error();
+		}
+
+		mysql_free_result($result);
+		return '';
+	}
+
+	public static function NbRows($result)
+	{
+		return mysql_num_rows($result);
+	}
+
+	public static function FetchArray($result)
+	{
+		return mysql_fetch_array($result, MYSQL_ASSOC);
+	}
+
+	public static function Seek($result, $iRow)
+	{
+		return mysql_data_seek($result, $iRow);
+	}
+
+	public static function FreeResult($result)
+	{
+		return mysql_free_result($result);
+	}
+
+	public static function IsTable($sTable)
+	{
+		$aTableInfo = self::GetTableInfo($sTable);
+		return (!empty($aTableInfo));
+	}
+
+	public static function IsKey($sTable, $iKey)
+	{
+		$aTableInfo = self::GetTableInfo($sTable);
+		if (empty($aTableInfo)) return false;
+		if (!array_key_exists($iKey, $aTableInfo["Fields"])) return false;
+		$aFieldData = $aTableInfo["Fields"][$iKey];
+		if (!array_key_exists("Key", $aFieldData)) return false;
+		return ($aFieldData["Key"] == "PRI"); 
+	}
+
+	public static function IsAutoIncrement($sTable, $sField)
+	{
+		$aTableInfo = self::GetTableInfo($sTable);
+		if (empty($aTableInfo)) return false;
+		if (!array_key_exists($sField, $aTableInfo["Fields"])) return false;
+		$aFieldData = $aTableInfo["Fields"][$sField];
+		if (!array_key_exists("Extra", $aFieldData)) return false;
+		//MyHelpers::debug_breakpoint($aFieldData);
+		return (strstr($aFieldData["Extra"], "auto_increment")); 
+	}
+
+	public static function IsField($sTable, $sField)
+	{
+		$aTableInfo = self::GetTableInfo($sTable);
+		if (empty($aTableInfo)) return false;
+		if (!array_key_exists($sField, $aTableInfo["Fields"])) return false;
+		return true;
+	}
+
+	public static function IsNullAllowed($sTable, $sField)
+	{
+		$aTableInfo = self::GetTableInfo($sTable);
+		if (empty($aTableInfo)) return false;
+		if (!array_key_exists($sField, $aTableInfo["Fields"])) return false;
+		$aFieldData = $aTableInfo["Fields"][$sField];
+		return (strtolower($aFieldData["Null"]) == "yes");
+	}
+
+	// Returns an array of (fieldname => array of field info)
+	public static function GetTableFieldsList($sTable)
+	{
+		assert(!empty($sTable));
+		
+		$aTableInfo = self::GetTableInfo($sTable);
+		if (empty($aTableInfo)) return array(); // #@# or an error ?
+
+		return array_keys($aTableInfo["Fields"]);
+	}
+	
+	// Cache the information about existing tables, and their fields
+	private static $m_aTablesInfo = array();
+	private static function _TablesInfoCacheReset()
+	{
+		self::$m_aTablesInfo = array();
+	}
+	private static function _TableInfoCacheInit($sTableName)
+	{
+		if (isset(self::$m_aTablesInfo[strtolower($sTableName)])
+			&& (self::$m_aTablesInfo[strtolower($sTableName)] != null)) return;
+
+		try
+		{
+			// Check if the table exists
+			$aFields = self::QueryToArray("SHOW COLUMNS FROM `$sTableName`");
+			// Note: without backticks, you get an error with some table names (e.g. "group")
+			foreach ($aFields as $aFieldData)
+			{
+				$sFieldName = $aFieldData["Field"];
+				self::$m_aTablesInfo[strtolower($sTableName)]["Fields"][$sFieldName] =
+					array
+					(
+						"Name"=>$aFieldData["Field"],
+						"Type"=>$aFieldData["Type"],
+						"Null"=>$aFieldData["Null"],
+						"Key"=>$aFieldData["Key"],
+						"Default"=>$aFieldData["Default"],
+						"Extra"=>$aFieldData["Extra"]
+					);
+			}
+		}
+		catch(MySQLException $e)
+		{
+			// Table does not exist
+			self::$m_aTablesInfo[strtolower($sTableName)] = null;
+		}
+	}
+	//public static function EnumTables()
+	//{
+	//	self::_TablesInfoCacheInit();
+	//	return array_keys(self::$m_aTablesInfo);
+	//}
+	public static function GetTableInfo($sTable)
+	{
+		self::_TableInfoCacheInit($sTable);
+
+		// perform a case insensitive match because on Windows the table names become lowercase :-(
+		//foreach(self::$m_aTablesInfo as $sTableName => $aInfo)
+		//{
+		//	if (strtolower($sTableName) == strtolower($sTable))
+		//	{
+		//		return $aInfo;
+		//	}
+		//}
+		return self::$m_aTablesInfo[strtolower($sTable)];
+		//return null;
+	}
+}
+
+
+?>

+ 272 - 0
core/config.class.inc.php

@@ -0,0 +1,272 @@
+<?php
+require_once('coreexception.class.inc.php');
+/**
+ * Config
+ * configuration data
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class ConfigException extends CoreException
+{
+}
+
+class Config
+{
+	//protected $m_bIsLoaded = false;
+	protected $m_sFile = '';
+
+	protected $m_aAppModules;
+	protected $m_aDataModels;
+	protected $m_aAddons;
+
+	protected $m_sDBHost;
+	protected $m_sDBUser;
+	protected $m_sDBPwd;
+	protected $m_sDBName;
+	protected $m_sDBSubname;
+
+	public function __construct($sConfigFile, $bLoadConfig = true)
+	{
+		$this->m_sFile = $sConfigFile;
+		$this->m_aAppModules = array();
+		$this->m_aDataModels = array();
+		$this->m_aAddons = array();
+
+		$this->m_sDBHost = '';
+		$this->m_sDBUser = '';
+		$this->m_sDBPwd = '';
+		$this->m_sDBName = '';
+		$this->m_sDBSubname = '';
+		if ($bLoadConfig)
+		{
+			$this->Load($sConfigFile);
+			$this->Verify();
+		}
+	}
+
+	protected function CheckFile($sPurpose, $sFileName)
+	{
+		if (!file_exists($sFileName))
+		{
+			throw new ConfigException("Could not find $sPurpose file", array('file' => $sFileName));
+		}
+	}
+
+	protected function Load($sConfigFile)
+	{
+		$this->CheckFile('configuration', $sConfigFile);
+
+		$sConfigCode = trim(file_get_contents($sConfigFile));
+
+		// This does not work on several lines
+		// preg_match('/^<\\?php(.*)\\?'.'>$/', $sConfigCode, $aMatches)...
+		// So, I've implemented a solution suggested in the PHP doc (search for phpWrapper)
+		try
+		{
+			ob_start();
+			$sCode = str_replace('<'.'?php','<'.'?', $sConfigCode);
+			eval('?'.'>'.trim($sCode).'<'.'?');
+			$sNoise = trim(ob_get_contents());
+			ob_end_clean();
+		}
+		catch (Exception $e)
+		{
+			// well, never reach in case of parsing error :-(
+			// will be improved in PHP 6 ?
+			throw new ConfigException('Error in configuration file', array('file' => $sConfigFile, 'error' => $e->getMessage()));
+		}
+		if (strlen($sNoise) > 0)
+		{
+			// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack) 
+			throw new ConfigException('Syntax error in configuration file', array('file' => $sConfigFile, 'error' => $sNoise));
+		}
+
+		if (!isset($MySettings) || !is_array($MySettings))
+		{
+			throw new ConfigException('Missing array in configuration file', array('file' => $sConfigFile, 'expected' => '$MySettings'));
+		}
+		if (!isset($MyModules) || !is_array($MyModules))
+		{
+			throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules'));
+		}
+		if (!array_key_exists('application', $MyModules))
+		{
+			throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'application\']'));
+		}
+		if (!array_key_exists('business', $MyModules))
+		{
+			throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'business\']'));
+		}
+		if (!array_key_exists('addons', $MyModules))
+		{
+			throw new ConfigException('Missing item in configuration file', array('file' => $sConfigFile, 'expected' => '$MyModules[\'addons\']'));
+		}
+		if (!array_key_exists('user rights', $MyModules['addons']))
+		{
+			$MyModules['addons']['user rights'] = '../addons/userrights/userrightsnull.class.inc.php';
+		}
+		$this->m_aAppModules = $MyModules['application'];
+		$this->m_aDataModels = $MyModules['business'];
+		$this->m_aAddons = $MyModules['addons'];
+
+		$this->m_sDBHost = trim($MySettings['db_host']);
+		$this->m_sDBUser = trim($MySettings['db_user']);
+		$this->m_sDBPwd = trim($MySettings['db_pwd']);
+		$this->m_sDBName = trim($MySettings['db_name']);
+		$this->m_sDBSubname = trim($MySettings['db_subname']);
+	}
+
+	protected function Verify()
+	{
+		foreach ($this->m_aAppModules as $sModule => $sToInclude)
+		{
+			$this->CheckFile('application module', $sToInclude);
+		}
+		foreach ($this->m_aDataModels as $sModule => $sToInclude)
+		{
+			$this->CheckFile('business model', $sToInclude);
+		}
+		foreach ($this->m_aAddons as $sModule => $sToInclude)
+		{
+			$this->CheckFile('addon module', $sToInclude);
+		}
+	}
+
+	public function GetAppModules()
+	{
+		return $this->m_aAppModules;
+	}
+
+	public function GetDataModels()
+	{
+		return $this->m_aDataModels;
+	}
+
+	public function GetAddons()
+	{
+		return $this->m_aAddons;
+	}
+
+	public function GetDBHost()
+	{
+		return $this->m_sDBHost;
+	}
+	
+	public function GetDBName()
+	{
+		return $this->m_sDBName;
+	}
+
+	public function GetDBSubname()
+	{
+		return $this->m_sDBSubname;
+	}
+
+	public function GetDBUser()
+	{
+		return $this->m_sDBUser;
+	}
+
+	public function GetDBPwd()
+	{
+		return $this->m_sDBPwd;
+	}
+
+	public function SetDBHost($sDBHost)
+	{
+		$this->m_sDBHost = $sDBHost;
+	}
+	
+	public function SetDBName($sDBName)
+	{
+		$this->m_sDBName = $sDBName;
+	}
+
+	public function SetDBSubname($sDBSubName)
+	{
+		$this->m_sDBSubname = $sDBSubName;
+	}
+
+	public function SetDBUser($sUser)
+	{
+		$this->m_sDBUser = $sUser;
+	}
+
+	public function SetDBPwd($sPwd)
+	{
+		$this->m_sDBPwd = $sPwd;
+	}
+	public function FileIsWritable()
+	{
+		return is_writable($this->m_sFile);
+	}
+	
+	/**
+	 * Write the configuration to a file (php format) that can be reloaded later
+	 * By default write to the same file that was specified when constructing the object
+	 * @param $sFileName string Name of the file to write to (emtpy to write to the same file)
+	 * @return boolean True otherwise throws an Exception
+	 */	 	 	 	 	
+	public function WriteToFile($sFileName = '')
+	{
+		if (empty($sFileName))
+		{
+			$sFileName = $this->m_sFile;
+		}
+		$hFile = @fopen($sFileName, 'w');
+		if ($hFile !== false)
+		{
+			fwrite($hFile, "<?php\n");
+			fwrite($hFile, "\n/**\n");
+			fwrite($hFile, " *\n");
+			fwrite($hFile, " * phpMyORM configuration file, generated by the iTop configuration wizard\n");
+			fwrite($hFile, " *\n");
+			fwrite($hFile, " * The file is used in MetaModel::LoadConfig() which does all the necessary initialization job\n");
+			fwrite($hFile, " *\n");
+			fwrite($hFile, " */\n");
+			fwrite($hFile, "\n");
+			
+			fwrite($hFile, "\$MySettings = array(\n");
+			fwrite($hFile, "\t'db_host' => '{$this->m_sDBHost}',\n");
+			fwrite($hFile, "\t'db_user' => '{$this->m_sDBUser}',\n");
+			fwrite($hFile, "\t'db_pwd' => '{$this->m_sDBPwd}',\n");
+			fwrite($hFile, "\t'db_name' => '{$this->m_sDBName}',\n");
+			fwrite($hFile, "\t'db_subname' => '{$this->m_sDBSubname}',\n");
+			fwrite($hFile, ");\n");
+			
+			fwrite($hFile, "\n/**\n");
+			fwrite($hFile, " *\n");
+			fwrite($hFile, " * Data model modules to be loaded. Names should be specified as absolute paths\n");
+			fwrite($hFile, " *\n");
+			fwrite($hFile, " */\n");
+			fwrite($hFile, "\$MyModules = array(\n");
+			fwrite($hFile, "\t'application' => array (\n");
+			fwrite($hFile, "\t\t'../application/menunode.class.inc.php',\n");
+			fwrite($hFile, "\t\t'../application/audit.rule.class.inc.php',\n");
+			fwrite($hFile, "\t\t// to be continued...\n");
+			fwrite($hFile, "\t),\n");
+			fwrite($hFile, "\t'business' => array (\n");
+			fwrite($hFile, "\t\t'../business/itop.business.class.inc.php'\n");
+			fwrite($hFile, "\t\t// to be continued...\n");
+			fwrite($hFile, "\t),\n");
+			fwrite($hFile, "\t'addons' => array (\n");
+			fwrite($hFile, "\t\t'user rights' => '../addons/userrights/userrightsmatrix.class.inc.php',\n");
+			fwrite($hFile, "\t\t// other modules to come later\n");
+			fwrite($hFile, "\t)\n");
+			fwrite($hFile, ");\n");
+			fwrite($hFile, '?'.'>'); // Avoid perturbing the syntax highlighting !
+			return fclose($hFile);
+		}
+		else
+		{
+			throw new ConfigException("Could not write to configuration file", array('file' => $sFileName));
+		}
+	}
+}
+?>

+ 63 - 0
core/coreexception.class.inc.php

@@ -0,0 +1,63 @@
+<?php
+
+class CoreException extends Exception
+{
+	public function __construct($sIssue, $aContextData = null, $sImpact = '')
+	{
+		$this->m_sIssue = $sIssue;
+		$this->m_sImpact = $sImpact;
+		$this->m_aContextData = $aContextData ? $aContextData : array();
+		
+		$sMessage = $sIssue;
+		if (!empty($sImpact)) $sMessage .= "($sImpact)";
+		if (count($this->m_aContextData) > 0)
+		{
+			$sMessage .= ": ";
+			$aContextItems = array();
+			foreach($this->m_aContextData as $sKey => $value)
+			{
+				if (is_array($value))
+				{
+					$aPairs = array();
+					foreach($value as $key => $val)
+					{
+						if (is_array($val))
+						{
+							$aPairs[$key] = '('.implode(', ', $val).')';
+						}
+						else
+						{
+							$aPairs[$key] = $val;
+						}
+					}
+					$sValue = '{'.implode('; ', $aPairs).'}';
+				}
+				else
+				{
+					$sValue = $value;
+				}
+				$aContextItems[] = "$sKey = $sValue";
+			}
+			$sMessage .= implode(', ', $aContextItems);
+		}
+		parent::__construct($sMessage, 0);
+	}
+
+	public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
+	{
+		return $this->getMessage();
+	}
+
+	public function getTraceAsHtml()
+	{
+		$aBackTrace = $this->getTrace();
+		return MyHelpers::get_callstack_html(0, $this->getTrace());
+		// return "<pre>\n".$this->getTraceAsString()."</pre>\n";
+	}
+}
+
+class CoreWarning extends CoreException
+{
+}
+
+?>

+ 192 - 0
core/csvparser.class.inc.php

@@ -0,0 +1,192 @@
+<?php
+
+/**
+ * CSVParser
+ * CSV interpreter helper, optionaly tries to guess column mapping and the separator, check the consistency 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class CSVParserException extends CoreException
+{
+}
+
+
+
+
+/**
+ * CSVParser
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class CSVParser
+{
+	private $m_sCSVData;
+	private $m_sSep;
+	private $m_iSkip;
+
+	public function __construct($sTxt)
+	{
+		$this->m_sCSVData = $sTxt;
+	}
+
+	public function SetSeparator($sSep)
+	{
+		$this->m_sSep = $sSep;
+	}
+	public function GetSeparator()
+	{
+		return $this->m_sSep;
+	}
+
+	public function SetSkipLines($iSkip)
+	{
+		$this->m_iSkip = $iSkip;
+	}
+	public function GetSkipLines()
+	{
+		return $this->m_iSkip;
+	}
+
+	public function GuessSeparator()
+	{
+		// Note: skip the first line anyway
+	
+		$aKnownSeps = array(';', ',', "\t"); // Use double quote for special chars!!!
+		$aStatsBySeparator = array();
+		foreach ($aKnownSeps as $sSep)
+		{
+			$aStatsBySeparator[$sSep] = array();
+		}
+	
+		foreach(split("\n", $this->m_sCSVData) as $sLine)
+		{
+			$sLine = trim($sLine);
+			if (substr($sLine, 0, 1) == '#') continue;
+			if (empty($sLine)) continue;
+	
+			$aLineCharsCount = count_chars($sLine, 0);
+			foreach ($aKnownSeps as $sSep)
+			{
+				$aStatsBySeparator[$sSep][] = $aLineCharsCount[ord($sSep)];
+			}
+		}
+	
+		// Default to ','
+		$this->SetSeparator(",");
+
+		foreach ($aKnownSeps as $sSep)
+		{
+			// Note: this function is NOT available :-( 
+			// stats_variance($aStatsBySeparator[$sSep]);
+			$iMin = min($aStatsBySeparator[$sSep]);
+			$iMax = max($aStatsBySeparator[$sSep]);
+			if (($iMin == $iMax) && ($iMax > 0))
+			{
+				$this->SetSeparator($sSep);
+				break;
+			}
+		}
+		return $this->GetSeparator();
+	}
+
+	public function GuessSkipLines()
+	{
+		// Take the FIRST -valuable- LINE ONLY
+		// If there is a number, then for sure this is not a header line
+		// Otherwise, we may consider that there is one line to skip
+		foreach(split("\n", $this->m_sCSVData) as $sLine)
+		{
+			$sLine = trim($sLine);
+			if (substr($sLine, 0, 1) == '#') continue;
+			if (empty($sLine)) continue;
+	
+			foreach (split($this->m_sSep, $sLine) as $value)
+			{
+				if (is_numeric($value))
+				{
+					$this->SetSkipLines(0);
+					return 0;
+				}
+			}
+			$this->SetSkipLines(1);
+			return 1;
+		}
+	}
+
+	function ToArray($aFieldMap = null, $iMax = 0)
+	{
+		// $aFieldMap is an array of col_index=>col_name
+		// $iMax is to limit the count of rows computed
+		$aRes = array();
+	
+		$iCount = 0;
+		$iSkipped = 0;
+		foreach(split("\n", $this->m_sCSVData) as $sLine)
+		{
+			$sLine = trim($sLine);
+			if (substr($sLine, 0, 1) == '#') continue;
+			if (empty($sLine)) continue;
+	
+			if ($iSkipped < $this->m_iSkip)
+			{
+				$iSkipped++;
+				continue;
+			}
+	
+			foreach (split($this->m_sSep, $sLine) as $iCol=>$sValue)
+			{
+				if (is_array($aFieldMap)) $sColRef = $aFieldMap[$iCol];
+				else                      $sColRef = $iCol;
+				$aRes[$iCount][$sColRef] = $sValue;
+			}
+	
+			$iCount++;
+			if (($iMax > 0) && ($iCount >= $iMax)) break;
+		}
+		return $aRes;
+	}
+
+	public function ListFields()
+	{
+		// Take the first valuable line
+		foreach(explode("\n", $this->m_sCSVData) as $sLine)
+		{
+			$sLine = trim($sLine);
+			if (substr($sLine, 0, 1) == '#') continue;
+			if (empty($sLine)) continue;
+			// We've got the first valuable line, that's it!
+			break;
+		}
+
+		$aRet = array();
+		foreach (explode($this->m_sSep, $sLine) as $iCol=>$value)
+		{
+			if ($this->m_iSkip == 0)
+			{
+				// No header to help us
+				$sLabel = "field $iCol";
+			}
+			else
+			{
+				$sLabel = "$value";
+			}
+			$aRet[] = $sLabel;
+		}
+		return $aRet;
+	}
+}
+
+
+?>

+ 362 - 0
core/data.generator.class.inc.php

@@ -0,0 +1,362 @@
+<?php
+
+/**
+ * data generator
+ * helps the consultants in creating dummy data sets, for various test purposes (validation, usability, scalability) 
+ *
+ * @package     tbd
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+/**
+ * Data Generator helper class
+ *
+ * This class is useful to generate a lot of sample data that look consistent
+ * for a given organization in order to simulate a real CMDB
+ */
+class cmdbDataGenerator
+{
+	protected $m_sOrganizationKey;
+	protected $m_sOrganizationCode;
+	protected $m_sOrganizationName;
+	protected $m_OrganizationDomains;
+	
+	/**
+	 * Constructor
+	 */
+	public function __construct($sOrganizationId = "")
+	{
+		global $aCompanies, $aCompaniesCode;
+		if ($sOrganizationId == '')
+		{
+			// No organization provided, pick a random and unused one from our predefined list
+			$retries = 5*count($aCompanies);
+			while ( ($retries > 0) && !isset($this->m_sOrganizationCode)) // Stupid algorithm, but I'm too lazy to do something bulletproof tonight
+			{
+				$index = rand(0, count($aCompanies) - 1);
+				if (!$this->OrganizationExists($aCompanies[$index]['code']))
+				{
+					$this->m_sOrganizationCode = $aCompanies[$index]['code'];
+					$this->m_sOrganizationName = $aCompanies[$index]['name'];
+					$this->m_OrganizationDomains = $aCompanies[$index]['domain'];
+				}
+				$retries--;
+			}
+		}
+		else
+		{
+			// A code has been provided, let's take the information we need from the organization itself
+			$this->m_sOrganizationId = $sOrganizationId;
+			$oOrg = $this->GetOrganization($sOrganizationId);
+			if ($oOrg == null)
+			{
+				echo "Unable to find the organization '$sOrganisationCode' in the database... can not add objects into this organization.<br/>\n";
+				exit();
+			}
+			$this->m_sOrganizationCode = $oOrg->Get('code');
+			$this->m_sOrganizationName = $oOrg->Get('name');
+			if (!isset($aCompaniesCode[$this->m_sOrganizationCode]['domain']))
+			{
+				// Generate some probable domain names for this organization
+				$this->m_OrganizationDomains = array(strtolower($this->m_sOrganizationCode).".com", strtolower($this->m_sOrganizationCode).".org", strtolower($this->m_sOrganizationCode)."corp.net",);
+			}
+			else
+			{
+				// Pick the domain names for this organization from the predefined list
+				$this->m_OrganizationDomains = $aCompaniesCode[$this->m_sOrganizationCode]['domain'];
+			}
+		}
+		
+		if (!isset($this->m_sOrganizationCode))
+		{
+			echo "Unable to find an organization code which is not already used... can not create a new organization. Enhance the list of fake organizations (\$aCompanies in data_sample.inc.php).<br/>\n";
+			exit();
+		}
+	}
+	
+	/**
+	 * Get the current organization id used by the generator
+	 *
+	 * @return string The organization id
+	 */
+	public function GetOrganizationId()
+	{
+		return $this->m_sOrganizationId;
+	}
+	
+	/**
+	 * Get the current organization id used by the generator
+	 *
+	 * @param string The organization id
+	 * @return none
+	 */
+	public function SetOrganizationId($sId)
+	{
+		$this->m_sOrganizationId = $sId;
+	}
+	
+	/**
+	 * Get the current organization code used by the generator
+	 *
+	 * @return string The organization code
+	 */
+	public function GetOrganizationCode()
+	{
+		return $this->m_sOrganizationCode;
+	}
+
+	/**
+	 * Get the current organization name used by the generator
+	 *
+	 * @return string The organization name
+	 */
+	function GetOrganizationName()
+	{
+		return $this->m_sOrganizationName;
+	}
+	
+	/**
+	 * Get a pseudo random first name taken from a (big) prefedined list
+	 *
+	 * @return string A random first name
+	 */
+	function GenerateFirstName()
+	{
+		global $aFirstNames;
+		return $aFirstNames[rand(0, count($aFirstNames) - 1)];
+	}
+	
+	/**
+	 * Get a pseudo random last name taken from a (big) prefedined list
+	 *
+	 * @return string A random last name
+	 */
+	function GenerateLastName()
+	{
+		global $aNames;
+		return $aNames[rand(0, count($aNames) - 1)];
+	}
+	
+	/**
+	 * Get a pseudo random country name taken from a prefedined list
+	 *
+	 * @return string A random city name
+	 */
+	function GenerateCountryName()
+	{
+		global $aCountries;
+		return $aCountries[rand(0, count($aCountries) - 1)];
+	}
+	
+	/**
+	 * Get a pseudo random city name taken from a (big) prefedined list
+	 *
+	 * @return string A random city name
+	 */
+	function GenerateCityName()
+	{
+		global $aCities;
+		return $aCities[rand(0, count($aCities) - 1)];
+	}
+	
+	/**
+	 * Get a pseudo random email address made of the first name, last name and organization's domain
+	 *
+	 * @return string A random email address
+	 */
+	function GenerateEmail($sFirstName, $sLastName)
+	{
+		if (rand(1, 20) > 18)
+		{
+			// some people (let's say 5~10%) have an irregular email address
+			$sEmail = strtolower($this->CleanForEmail($sLastName))."@".strtolower($this->GenerateDomain());
+		}
+		else
+		{
+			$sEmail = strtolower($this->CleanForEmail($sFirstName)).".".strtolower($this->CleanForEmail($sLastName))."@".strtolower($this->GenerateDomain());
+		}
+		return $sEmail;
+	}
+
+	/**
+	 * Generate (pseudo) random strings that follow a given pattern
+	 *
+	 *	The template is made of any number of 'parts' separated by pipes '|'
+	 *  Each part is either:
+	 *  - domain() => returns a domain name for the current organization
+	 *  - enum(aaa,bb,c,dddd) => returns randomly one of aaa,bb,c or dddd with the same
+	 *    probability of occurence. If you want to change the probability you can repeat some values
+	 *    i.e enum(most probable,most probable,most probable,most probable,most probable,rare) 
+	 *	- number(xxx-yyy) => a random number between xxx and yyy (bounds included)
+	 *   note that if the first number (xxx) begins with a zero, then the result will zero padded
+	 *   to the same number of digits as xxx.
+	 *  All other 'part' that does not follow one of the above mentioned pattern is returned as is
+	 *
+	 * Example: GenerateString("enum(sw,rtr,gw)|number(00-99)|.|domain()")
+	 *          will produce strings like "sw01.netcmdb.com" or "rtr45.itop.org"
+	 *
+	 * @param string $sTemplate The template used for generating the string
+	 * @return string The generated pseudo random the string
+	 */
+	function GenerateString($sTemplate)
+	{
+		$sResult = "";
+		$aParts = split("\|", $sTemplate);
+		foreach($aParts as $sPart)
+		{
+			if (preg_match("/domain\(\)/", $sPart, $aMatches))
+			{
+				$sResult .= strtolower($this->GenerateDomain());
+			}
+			elseif (preg_match("/enum\((.+)\)/", $sPart, $aMatches))
+			{
+				$sEnumValues = $aMatches[1];
+				$aEnumValues = split(",", $sEnumValues);
+				$sResult .= $aEnumValues[rand(0, count($aEnumValues) - 1)];
+			}
+			elseif (preg_match("/number\((\d+)-(\d+)\)/", $sPart, $aMatches))
+			{
+				$sStartNumber = $aMatches[1];
+				if ($sStartNumber[0] == '0')
+				{
+					// number must be zero padded
+					$sFormat = "%0".strlen($sStartNumber)."d";
+				}
+				else
+				{
+					$sFormat = "%d";
+				}
+				$sEndNumber = $aMatches[2];
+				$sResult .= sprintf($sFormat, rand($sStartNumber, $sEndNumber));
+			}
+			else
+			{
+				$sResult .= $sPart;
+			}
+		}
+		return $sResult;
+	}
+
+	/**
+	 * Generate a foreign key by picking a random element of the given class in a set limited by the given search criteria
+	 *
+	 * Example: GenerateKey("bizLocation", array('org_id', $oGenerator->GetOrganizationId());
+	 *          will produce the foreign key of a Location object picked at random in the same organization
+	 *
+	 * @param string $sClass The name of the class to search for
+	 * @param string $aFilterCriteria A hash array of filterCOde => FilterValue (the strict operator '=' is used )
+	 * @return mixed The key to an object of the given class, or null if none are found
+	 */
+	function GenerateKey($sClass, $aFilterCriteria)
+	{
+		$retKey = null;
+		$oFilter = new CMDBSearchFilter($sClass);
+		foreach($aFilterCriteria as $sFilterCode => $filterValue)
+		{
+			$oFilter->AddCondition($sFilterCode, $filterValue, '=');
+		}
+		$oSet = new CMDBObjectSet($oFilter);
+		if ($oSet->Count() > 0)
+		{
+			$max_count = $index = rand(1, $oSet->Count());
+			do
+			{
+				$oObj = $oSet->Fetch();
+				$index--;
+			}
+			while($index > 0);
+			
+			if (!is_object($oObj))
+			{
+				echo "<pre>";
+				echo "ERROR: non empty set, but invalid object picked! class='$sClass'\n";
+				echo "Index chosen: $max_count\n";
+				echo "The set is supposed to contain ".$oSet->Count()." object(s)\n";
+				echo "Filter criteria:\n";
+				print_r($aFilterCriteria);
+				echo "</pre>";
+			}
+			else
+			{
+				$retKey = $oObj->GetKey();
+			}
+		}
+		return $retKey;
+	}
+	///////////////////////////////////////////////////////////////////////////////
+	//
+	//  Protected methods
+	//
+	///////////////////////////////////////////////////////////////////////////////
+		
+	/**
+	 * Generate a (random) domain name consistent with the organization name & code
+	 *
+	 * The values are pulled from a (limited) predefined list. Note that a given
+	 * organization may have several domain names, so the result may be random
+	 *
+	 * @return string A domain name (like netcnmdb.com)
+	 */
+	protected function GenerateDomain()
+	{
+		if (is_array($this->m_OrganizationDomains))
+		{
+			$sDomain = $this->m_OrganizationDomains[rand(0, count($this->m_OrganizationDomains)-1)];
+		}
+		else
+		{
+			$sDomain = $this->m_OrganizationDomains;
+		}
+		return $sDomain;
+	}
+	
+	/**
+	 * Strips accented characters from a string in order to produce a suitable email address
+	 *
+	 * @param string The text string to clean
+	 * @return string The cleanified text string
+	 */
+	protected function CleanForEmail($sText)
+	{
+		return str_replace(array("'", "é", "è", "ê", "ç", "à", "â", "ñ", "ö", "ä"), array("", "e", "e", "e", "c", "a", "a", "n", "oe", "ae"), $sText);
+	}
+
+	/**
+	 * Check if an organization with the given code already exists in the database
+	 *
+	 * @param string $sCode The code to look for
+	 * @return boolean true if the given organization exists, false otherwise
+	 */
+	protected function OrganizationExists($sCode)
+	{
+		$oFilter = new CMDBSearchFilter('bizOrganization');
+		$oFilter->AddCondition('code', $sCode, '=');
+		$oSet = new CMDBObjectSet($oFilter);
+		return ($oSet->Count() > 0);
+	}
+
+	/**
+	 * Search for an organization with the given code in the database
+	 *
+	 * @param string $Id The organization Id to look for
+	 * @return cmdbOrganization the organization if it exists, null otherwise
+	 */
+	protected function GetOrganization($sId)
+	{
+		$oOrg = null;
+		$oFilter = new CMDBSearchFilter('bizOrganization');
+		$oFilter->AddCondition('pkey', $sId, '=');
+		$oSet = new CMDBObjectSet($oFilter);
+		if ($oSet->Count() > 0)
+		{
+			$oOrg = $oSet->Fetch(); // Let's take the first one found
+		}
+		return $oOrg;
+	}
+}
+?>

+ 831 - 0
core/dbobject.class.php

@@ -0,0 +1,831 @@
+<?php
+
+/**
+ * ???
+ * the class a persistent object must be derived from 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+require_once('metamodel.class.php');
+
+
+/**
+ * A persistent object, as defined by the metamodel 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class DBObject
+{
+	private static $m_aMemoryObjectsByClass = array();
+
+	private $m_bIsInDB = false; // true IIF the object is mapped to a DB record
+	private $m_iKey = null;
+	private $m_aCurrValues = array();
+	private $m_aOrigValues = array();
+
+	private $m_bFullyLoaded = false; // Compound objects can be partially loaded
+	private $m_aLoadedAtt = array(); // Compound objects can be partially loaded, array of sAttCode
+
+	// Use the MetaModel::NewObject to build an object (do we have to force it?)
+	public function __construct($aRow = null)
+	{
+		if (!empty($aRow))
+		{
+			$this->FromRow($aRow);
+			$this->m_bFullyLoaded = $this->IsFullyLoaded();
+			return;
+		}
+		// Creation of brand new object
+		//
+
+		$this->m_iKey = self::GetNextTempId(get_class($this));
+
+		// set default values
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+			$this->m_aOrigValues[$sAttCode] = null;
+			if ($oAttDef->IsExternalField())
+			{
+				// This field has to be read from the DB 
+				$this->m_aLoadedAtt[$sAttCode] = false;
+			}
+			else
+			{
+				// No need to trigger a reload for that attribute
+				// Let's consider it as being already fully loaded
+				$this->m_aLoadedAtt[$sAttCode] = true;
+			}
+		}
+	}
+
+	public function IsNew()
+	{
+		return (!$this->m_bIsInDB);
+	}
+
+	// Returns an Id for memory objects
+	static protected function GetNextTempId($sClass)
+	{
+		if (!array_key_exists($sClass, self::$m_aMemoryObjectsByClass))
+		{
+			self::$m_aMemoryObjectsByClass[$sClass] = 0;
+		}
+		self::$m_aMemoryObjectsByClass[$sClass]++;
+		return (- self::$m_aMemoryObjectsByClass[$sClass]);
+	}
+
+	public function __toString()
+	{
+		$sRet = '';
+		$sClass = get_class($this);
+		$sRootClass = MetaModel::GetRootClass($sClass);
+		$iPKey = $this->GetKey();
+		$sRet .= "<b title=\"$sRootClass\">$sClass</b>::$iPKey<br/>\n";
+		$sRet .= "<ul class=\"treeview\">\n";
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			$sRet .= "<li>".$oAttDef->GetLabel()." = ".$this->GetAsHtml($sAttCode)."</li>\n";
+		}
+		$sRet .= "</ul>";
+		return $sRet;
+	}
+	
+	// Restore initial values... mmmm, to be discussed
+	public function DBRevert()
+	{
+		$this->Reload();
+	}
+
+	protected function IsFullyLoaded()
+	{
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			@$bIsLoaded = $this->m_aLoadedAtt[$sAttCode];
+			if ($bIsLoaded !== true)
+			{
+				return false;
+			}
+		}
+		return true;
+	}
+
+	protected function Reload()
+	{
+		assert($this->m_bIsInDB);
+		$aRow = MetaModel::MakeSingleRow(get_class($this), $this->m_iKey);
+		if (empty($aRow))
+		{
+			throw new CoreException("Failed to reload object of class '".get_class($this)."', id = ".$this->m_iKey);
+		}
+		$this->FromRow($aRow);
+
+		// Process linked set attributes
+		//
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if (!$oAttDef->IsLinkSet()) continue;
+
+			// Load the link information
+			$sLinkClass = $oAttDef->GetLinkedClass();
+			$sExtKeyToMe = $oAttDef->GetExtKeyToMe();
+
+			// The class to target is not the current class, because if this is a derived class,
+			// it may differ from the target class, then things start to become confusing
+			$oRemoteExtKeyAtt = MetaModel::GetAttributeDef($sLinkClass, $sExtKeyToMe);
+			$sMyClass = $oRemoteExtKeyAtt->GetTargetClass();
+
+			$oMyselfSearch = new DBObjectSearch($sMyClass);
+			$oMyselfSearch->AddCondition('id', $this->m_iKey, '=');
+
+			$oLinkSearch = new DBObjectSearch($sLinkClass);
+			$oLinkSearch->AddCondition_PointingTo($oMyselfSearch, $sExtKeyToMe);
+			$oLinks = new DBObjectSet($oLinkSearch);
+
+			$this->m_aCurrValues[$sAttCode] = $oLinks;
+			$this->m_aOrigValues[$sAttCode] = clone $this->m_aCurrValues[$sAttCode];
+			$this->m_aLoadedAtt[$sAttCode] = true;
+		}
+
+		$this->m_bFullyLoaded = true;
+	}
+
+	protected function FromRow($aRow)
+	{
+		$this->m_iKey = null;
+		$this->m_bIsInDB = true;
+		$this->m_aCurrValues = array();
+		$this->m_aOrigValues = array();
+		$this->m_aLoadedAtt = array();
+
+		// Get the key
+		//
+		$sKeyField = "id";
+		if (!array_key_exists($sKeyField, $aRow))
+		{
+			// #@# Bug ?
+			throw new CoreException("Missing key for class '".get_class($this)."'");
+		}
+		else
+		{
+			$iPKey = $aRow[$sKeyField];
+			if (!self::IsValidPKey($iPKey))
+			{
+				throw new CoreWarning("An object id must be an integer value ($iPKey)");
+			}
+			$this->m_iKey = $iPKey;
+		}
+
+		// Build the object from an array of "attCode"=>"value")
+		//
+		$bFullyLoaded = true; // ... set to false if any attribute is not found
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			// Say something, whatever the type of attribute
+			$this->m_aLoadedAtt[$sAttCode] = false;
+
+			// Skip links (could not be loaded by the mean of this query)
+			if ($oAttDef->IsLinkSet()) continue;
+
+			if (array_key_exists($sAttCode, $aRow))
+			{
+				$sValue = $oAttDef->SQLValueToRealValue($aRow[$sAttCode]);
+				$this->m_aCurrValues[$sAttCode] = $sValue;
+				$this->m_aOrigValues[$sAttCode] = $sValue;
+				$this->m_aLoadedAtt[$sAttCode] = true;
+			}
+			else
+			{
+				// This attribute was expected and not found in the query columns
+				$bFullyLoaded = false;
+			}
+		}
+		return $bFullyLoaded;
+	}
+	
+	public function Set($sAttCode, $value)
+	{
+		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
+		{
+			throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
+		}
+		$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+		if ($this->m_bIsInDB && !$this->m_bFullyLoaded)
+		{
+			// First time Set is called... ensure that the object gets fully loaded
+			// Otherwise we would lose the values on a further Reload
+			//           + consistency does not make sense !
+			$this->Reload();
+		}
+		if($oAttDef->IsScalar() && !$oAttDef->IsNullAllowed() && is_null($value))
+		{
+			throw new CoreWarning("null not allowed for attribute '$sAttCode', setting default value");
+			$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+			return;
+		}
+		if ($oAttDef->IsExternalKey() && is_object($value))
+		{
+			// Setting an external key with a whole object (instead of just an ID)
+			// let's initialize also the external fields that depend on it
+			// (useful when building objects in memory and not from a query)
+			if ( (get_class($value) != $oAttDef->GetTargetClass()) && (!is_subclass_of($value, $oAttDef->GetTargetClass())))
+			{
+				throw new CoreWarning("Trying to set the value of '$sAttCode', to an object of class '".get_class($value)."', whereas it's an ExtKey to '".$oAttDef->GetTargetClass()."'. Ignored");
+				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+			}
+			else
+			{
+				$this->m_aCurrValues[$sAttCode] = $value->GetKey();
+				foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sCode => $oDef)
+				{
+					if ($oDef->IsExternalField() && ($oDef->GetKeyAttCode() == $sAttCode))
+					{
+						$this->m_aCurrValues[$sCode] = $value->Get($oDef->GetExtAttCode());
+					}
+				}
+			}
+			return;
+		}
+		if(!$oAttDef->IsScalar() && !is_object($value))
+		{
+			throw new CoreWarning("scalar not allowed for attribute '$sAttCode', setting default value (empty list)");
+			$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+			return;
+		}
+		if($oAttDef->IsLinkSet())
+		{
+			if((get_class($value) != 'DBObjectSet') && !is_subclass_of($value, 'DBObjectSet'))
+			{
+				throw new CoreWarning("expecting a set of persistent objects (found a '".get_class($value)."'), setting default value (empty list)");
+				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+				return;
+			}
+
+			$oObjectSet = $value;
+			$sSetClass = $oObjectSet->GetClass();
+			$sLinkClass = $oAttDef->GetLinkedClass();
+			// not working fine :-(   if (!is_subclass_of($sSetClass, $sLinkClass))
+			if ($sSetClass != $sLinkClass)
+			{
+				throw new CoreWarning("expecting a set of '$sLinkClass' objects (found a set of '$sSetClass'), setting default value (empty list)");
+				$this->m_aCurrValues[$sAttCode] = $oAttDef->GetDefaultValue();
+				return;
+			}
+		}
+		$this->m_aCurrValues[$sAttCode] = $oAttDef->MakeRealValue($value);
+	}
+	
+	public function Get($sAttCode)
+	{
+		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
+		{
+			throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
+		}
+		if ($this->m_bIsInDB && !$this->m_aLoadedAtt[$sAttCode])
+		{
+			// #@# non-scalar attributes.... handle that differentely
+			$this->Reload();
+		}
+		$this->ComputeFields();
+		return $this->m_aCurrValues[$sAttCode];
+	}
+
+	public function GetOriginal($sAttCode)
+	{
+		if (!array_key_exists($sAttCode, MetaModel::ListAttributeDefs(get_class($this))))
+		{
+			throw new CoreException("Unknown attribute code '$sAttCode' for the class ".get_class($this));
+		}
+		return $this->m_aOrigValues[$sAttCode];
+	}
+
+	public function ComputeFields()
+	{
+		if (is_callable(array($this, 'ComputeValues')))
+		{
+			// First check that we are not currently computing the fields
+			// (yes, we need to do some things like Set/Get to compute the fields which will in turn trigger the update...)
+			foreach (debug_backtrace() as $aCallInfo)
+			{
+				if (!array_key_exists("class", $aCallInfo)) continue;
+				if ($aCallInfo["class"] != get_class($this)) continue;
+				if ($aCallInfo["function"] != "ComputeValues") continue;
+				return; //skip!
+			}
+			
+			$this->ComputeValues();
+		}
+	}
+
+	public function GetAsHTML($sAttCode)
+	{
+		$sClass = get_class($this);
+		$oAtt = MetaModel::GetAttributeDef($sClass, $sAttCode);
+
+		$aExtKeyFriends = MetaModel::GetExtKeyFriends($sClass, $sAttCode);
+		if (count($aExtKeyFriends) > 0)
+		{
+			// This attribute is an ext key (in this class or in another class)
+			// The corresponding value is an id of the remote object
+			// Let's try to use the corresponding external fields for a sexy display
+
+			$aAvailableFields = array();
+			foreach ($aExtKeyFriends as $sDispAttCode => $oExtField)
+			{
+				$aAvailableFields[$oExtField->GetExtAttCode()] = $oExtField->GetAsHTML($this->Get($oExtField->GetCode()));
+			}
+
+			$sTargetClass = $oAtt->GetTargetClass(EXTKEY_ABSOLUTE);
+			$aMakeHLink = array(get_class($this), 'MakeHyperLink');
+			if (is_callable($aMakeHLink))
+			{
+				return call_user_func($aMakeHLink, $sTargetClass, $this->Get($sAttCode), $aAvailableFields);
+			}
+			else
+			{
+				return $this->Get($sAttCode);
+			}
+		}
+
+		// That's a standard attribute (might be an ext field or a direct field, etc.)
+		return $oAtt->GetAsHTML($this->Get($sAttCode));
+	}
+
+	public function GetAsXML($sAttCode)
+	{
+		$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+		return $oAtt->GetAsXML($this->Get($sAttCode));
+	}
+
+	public function GetAsCSV($sAttCode, $sSeparator = ';', $sSepEscape = ',')
+	{
+		$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+		return $oAtt->GetAsCSV($this->Get($sAttCode), $sSeparator, $sSepEscape);
+	}
+
+	// could be in the metamodel ?
+	public static function IsValidPKey($value)
+	{
+		return ((string)$value === (string)(int)$value);
+	}
+
+	public function GetKey()
+	{
+		return $this->m_iKey;
+	}
+	public function SetKey($iNewKey)
+	{
+		if (!self::IsValidPKey($iNewKey))
+		{
+			throw new CoreException("An object id must be an integer value ($iNewKey)");
+		}
+		
+		if ($this->m_bIsInDB && !empty($this->m_iKey) && ($this->m_iKey != $iNewKey))
+		{
+			throw new CoreException("Changing the key ({$this->m_iKey} to $iNewKey) on an object (class {".get_class($this).") wich already exists in the Database");
+		}
+		$this->m_iKey = $iNewKey;
+	}
+
+	public function GetName()
+	{
+		$sNameAttCode = MetaModel::GetNameAttributeCode(get_class($this));
+		if (empty($sNameAttCode))
+		{
+			return $this->m_iKey;
+		}
+		else
+		{
+			return $this->Get($sNameAttCode);
+		}
+	}
+
+	public function GetState()
+	{
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		if (empty($sStateAttCode))
+		{
+			return '';
+		}
+		else
+		{
+			$aStates = MetaModel::EnumStates(get_class($this));
+			return $aStates[$this->Get($sStateAttCode)]['label'];
+		}
+	}
+
+	/**
+	 * Returns the set of flags (OPT_ATT_HIDDEN, OPT_ATT_READONLY, OPT_ATT_MANDATORY...)
+	 * for the given attribute in the current state of the object
+	 * @param string $sAttCode The code of the attribute
+	 * @return integer Flags: the binary combination of the flags applicable to this attribute
+	 */	 	  	 	
+	public function GetAttributeFlags($sAttCode)
+	{
+		$iFlags = 0; // By default (if no life cycle) no flag at all
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		if (!empty($sStateAttCode))
+		{
+			$iFlags = MetaModel::GetAttributeFlags(get_class($this), $this->Get($sStateAttCode), $sAttCode);
+		}
+		return $iFlags;
+	}
+
+	// check if the given (or current) value is suitable for the attribute
+	public function CheckValue($sAttCode, $value = null)
+	{
+		if (!is_null($value))
+		{
+			$toCheck = $value;
+		}
+		else
+		{
+			$toCheck = $this->Get($sAttCode);
+		}
+
+		$oAtt = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+		if ($oAtt->IsExternalKey())
+		{
+			if (!$oAtt->IsNullAllowed() || ($toCheck != 0) )
+			{
+				try
+				{
+					$oTargetObj = MetaModel::GetObject($oAtt->GetTargetClass(), $toCheck);
+					return true;
+				}
+				catch (CoreException $e)
+				{
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+	
+	// check attributes together
+	public function CheckConsistency()
+	{
+		return true;
+	}
+	
+	// check if it is allowed to record the new object into the database
+	// a displayable error is returned
+	// Note: checks the values and consistency
+	public function CheckToInsert()
+	{
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if (!$this->CheckValue($sAttCode)) return false;
+		}
+		if (!$this->CheckConsistency()) return false;
+		return true;
+	}
+	
+	// check if it is allowed to update the existing object into the database
+	// a displayable error is returned
+	// Note: checks the values and consistency
+	public function CheckToUpdate()
+	{
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if (!$this->CheckValue($sAttCode)) return false;
+		}
+		if (!$this->CheckConsistency()) return false;
+		return true;
+	}
+	
+	// check if it is allowed to delete the existing object from the database
+	// a displayable error is returned
+	public function CheckToDelete()
+	{
+		return true;
+	}
+
+	protected function ListChangedValues(array $aProposal)
+	{
+		$aDelta = array();
+		foreach ($aProposal as $sAtt => $proposedValue)
+		{
+			if (!array_key_exists($sAtt, $this->m_aOrigValues) || ($this->m_aOrigValues[$sAtt] != $proposedValue))
+			{
+				$aDelta[$sAtt] = $proposedValue;
+			}
+		}
+		return $aDelta;
+	} 
+
+	// List the attributes that have been changed
+	// Returns an array of attname => currentvalue
+	public function ListChanges()
+	{
+		return $this->ListChangedValues($this->m_aCurrValues);
+	}
+
+	// used both by insert/update
+	private function DBWriteLinks()
+	{
+		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
+		{
+			if (!$oAttDef->IsLinkSet()) continue;
+
+			$oLinks = $this->Get($sAttCode);
+			$oLinks->Rewind();
+			while ($oLinkedObject = $oLinks->Fetch())
+			{
+				$oLinkedObject->Set($oAttDef->GetExtKeyToMe(), $this->m_iKey);
+				$oLinkedObject->DBWrite();
+			}
+
+			// Delete the objects that were initialy present and disappeared from the list
+			// (if any)
+			$oOriginalSet = $this->m_aOrigValues[$sAttCode];
+			if ($oOriginalSet != null)
+			{
+				$aOriginalList = $oOriginalSet->ToArray();
+				$aNewSet = $oLinks->ToArray();
+				$aToDelete = array_diff($aOriginalList, $aNewSet);
+				foreach ($aToDelete as $iKey => $oObject)
+				{
+					$oObject->DBDelete();
+				}
+			}
+		}
+	}
+
+	private function DBInsertSingleTable($sTableClass)
+	{
+		$sClass = get_class($this);
+
+		// fields in first array, values in the second
+		$aFieldsToWrite = array();
+		$aValuesToWrite = array();
+		
+		if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
+		{
+			// Add it to the list of fields to write
+			$aFieldsToWrite[] = MetaModel::DBGetKey($sTableClass);
+			$aValuesToWrite[] = CMDBSource::Quote($this->m_iKey);
+		}
+
+		foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
+		{
+			// Skip this attribute if not defined in this table
+			if (!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode)) continue;
+			if ($oAttDef->IsDirectField())
+			{
+				$aFieldsToWrite[] = $oAttDef->GetSQLExpr(); 
+				$aValuesToWrite[] = CMDBSource::Quote($oAttDef->RealValueToSQLValue($this->m_aCurrValues[$sAttCode]));
+			} 
+		}
+
+		if (count($aValuesToWrite) == 0) return false;
+
+		$sTable = MetaModel::DBGetTable($sTableClass);
+		$sInsertSQL = "INSERT INTO $sTable (".join(",", $aFieldsToWrite).") VALUES (".join(", ", $aValuesToWrite).")";
+
+		$iNewKey = CMDBSource::InsertInto($sInsertSQL);
+		// Note that it is possible to have a key defined here, and the autoincrement expected, this is acceptable in a non root class
+		if (empty($this->m_iKey))
+		{
+			// Take the autonumber
+			$this->m_iKey = $iNewKey;
+		}
+		return $this->m_iKey;
+	}
+	
+	// Insert of record for the new object into the database
+	// Returns the key of the newly created object
+	public function DBInsertNoReload()
+	{
+		if ($this->m_bIsInDB)
+		{
+			throw new CoreException("The object already exists into the Database, you may want to use the clone function");
+		}
+
+		$sClass = get_class($this);
+		$sRootClass = MetaModel::GetRootClass($sClass);
+
+		// Ensure the update of the values (we are accessing the data directly)
+		$this->ComputeFields();
+
+		if ($this->m_iKey < 0)
+		{
+			// This was a temporary "memory" key: discard it so that DBInsertSingleTable will not try to use it!
+			$this->m_iKey = null; 
+		}
+
+		// If not automatically computed, then check that the key is given by the caller
+		if (!MetaModel::IsAutoIncrementKey($sRootClass))
+		{
+			if (empty($this->m_iKey))
+			{
+				throw new CoreWarning("Missing key for the object to write - This class is supposed to have a user defined key, not an autonumber");
+			}
+		}
+
+		// First query built upon on the root class, because the ID must be created first
+		$this->m_iKey = $this->DBInsertSingleTable($sRootClass);
+
+		// Then do the leaf class, if different from the root class
+		if ($sClass != $sRootClass)
+		{
+			$this->DBInsertSingleTable($sClass);
+		}
+
+		// Then do the other classes
+		foreach(MetaModel::EnumParentClasses($sClass) as $sParentClass)
+		{
+			if ($sParentClass == $sRootClass) continue;
+			if (MetaModel::DBGetTable($sParentClass) == "") continue;
+			$this->DBInsertSingleTable($sParentClass);
+		}
+
+		$this->DBWriteLinks();
+
+		// Reload to update the external attributes
+		$this->m_bIsInDB = true;
+		return $this->m_iKey;
+	}
+
+	public function DBInsert()
+	{
+		$this->DBInsertNoReload();
+		$this->Reload();
+		return $this->m_iKey;
+	}
+
+	// Creates a copy of the current object into the database
+	// Returns the id of the newly created object
+	public function DBClone($iNewKey = null)
+	{
+		$this->m_bIsInDB = false;
+		$this->m_iKey = $iNewKey;
+		return $this->DBInsert();
+	}
+
+	// Update a record
+	public function DBUpdate()
+	{
+		if (!$this->m_bIsInDB)
+		{
+			throw new CoreException("DBUpdate: could not update a newly created object, please call DBInsert instead");
+		}
+		$aChanges = $this->ListChanges();
+		if (count($aChanges) == 0)
+		{
+			throw new CoreWarning("Attempting to update an unchanged object");
+			return;
+		}
+		$bHasANewExternalKeyValue = false;
+		foreach($aChanges as $sAttCode => $valuecurr)
+		{
+			$oAttDef = MetaModel::GetAttributeDef(get_class($this), $sAttCode);
+			if ($oAttDef->IsExternalKey()) $bHasANewExternalKeyValue = true;
+			if (!$oAttDef->IsDirectField()) unset($aChanges[$sAttCode]);
+		}
+
+		// Update scalar attributes
+		if (count($aChanges) != 0)
+		{
+			$oFilter = new DBObjectSearch(get_class($this));
+			$oFilter->AddCondition('id', $this->m_iKey, '=');
+	
+			$sSQL = MetaModel::MakeUpdateQuery($oFilter, $aChanges);
+			CMDBSource::Query($sSQL);
+		}
+
+		$this->DBWriteLinks();
+
+		// Reload to get the external attributes
+		if ($bHasANewExternalKeyValue) $this->Reload();
+
+		return $this->m_iKey;
+	}
+
+	// Make the current changes persistent - clever wrapper for Insert or Update
+	public function DBWrite()
+	{
+		if ($this->m_bIsInDB)
+		{
+			return $this->DBUpdate();
+		}
+		else
+		{
+			return $this->DBInsert();
+		}
+	}
+	
+	// Delete a record
+	public function DBDelete()
+	{
+		$oFilter = new DBObjectSearch(get_class($this));
+		$oFilter->AddCondition('id', $this->m_iKey, '=');
+
+		$sSQL = MetaModel::MakeDeleteQuery($oFilter);
+		CMDBSource::Query($sSQL);
+
+		$this->m_bIsInDB = false;
+		$this->m_iKey = null;
+	}
+
+	public function EnumTransitions()
+	{
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		if (empty($sStateAttCode)) return array();
+
+		$sState = $this->Get(MetaModel::GetStateAttributeCode(get_class($this)));
+		return MetaModel::EnumTransitions(get_class($this), $sState);
+	}
+
+	public function ApplyStimulus($sStimulusCode)
+	{
+		$sStateAttCode = MetaModel::GetStateAttributeCode(get_class($this));
+		if (empty($sStateAttCode)) return false;
+
+		MyHelpers::CheckKeyInArray('object lifecycle stimulus', $sStimulusCode, MetaModel::EnumStimuli(get_class($this)));
+
+		$aStateTransitions = $this->EnumTransitions();
+		$aTransitionDef = $aStateTransitions[$sStimulusCode];
+
+		// Change the state before proceeding to the actions, this is necessary because an action might
+		// trigger another stimuli (alternative: push the stimuli into a queue)
+		$this->Set($sStateAttCode, $aTransitionDef['target_state']);
+
+		// $aTransitionDef is an
+		//    array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD
+
+		$bSuccess = true;
+		foreach ($aTransitionDef['actions'] as $sActionHandler)
+		{
+			// std PHP spec
+			$aActionCallSpec = array($this, $sActionHandler);
+
+			if (!is_callable($aActionCallSpec))
+			{
+				throw new CoreException("Unable to call action: ".get_class($this)."::$sActionHandler");
+				return;
+			}
+			$bRet = call_user_func($aActionCallSpec, $sStimulusCode);
+			// if one call fails, the whole is considered as failed
+			if (!$bRet) $bSuccess = false;
+		}
+
+		return $bSuccess;
+	}
+
+	// Return an empty set for the parent of all
+	public static function GetRelationQueries($sRelCode)
+	{
+		return array();
+	}
+	
+	public function GetRelatedObjects($sRelCode, $iMaxDepth = 99, &$aResults = array())
+	{
+		foreach (MetaModel::EnumRelationQueries(get_class($this), $sRelCode) as $sDummy => $aQueryInfo)
+		{
+			MetaModel::DbgTrace("object=".$this->GetKey().", depth=$iMaxDepth, rel=".$aQueryInfo["sQuery"]);
+			$sQuery = $aQueryInfo["sQuery"];
+			$bPropagate = $aQueryInfo["bPropagate"];
+			$iDistance = $aQueryInfo["iDistance"];
+
+			$iDepth = $bPropagate ? $iMaxDepth - 1 : 0;
+
+			$oFlt = DBObjectSearch::FromSibusQL($sQuery, array(), $this);
+			$oObjSet = new DBObjectSet($oFlt);
+			while ($oObj = $oObjSet->Fetch())
+			{
+				$sRootClass = MetaModel::GetRootClass(get_class($oObj));
+				$sObjKey = $oObj->GetKey();
+				if (array_key_exists($sRootClass, $aResults))
+				{
+					if (array_key_exists($sObjKey, $aResults[$sRootClass]))
+					{
+						continue; // already visited, skip
+					}
+				}
+
+				$aResults[$sRootClass][$sObjKey] = $oObj;
+				if ($iDepth > 0)
+				{
+					$oObj->GetRelatedObjects($sRelCode, $iDepth, $aResults);
+				}
+			}
+		}
+		return $aResults;
+	}
+}
+
+
+?>

+ 973 - 0
core/dbobjectsearch.class.php

@@ -0,0 +1,973 @@
+<?php
+
+/**
+ * Define filters for a given class of objects (formerly named "filter") 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+
+/**
+ * Sibusql - value set start 
+ * @package     iTopORM
+ * @info        zis is private 
+ */
+define('VS_START', '{');
+/**
+ * Sibusql - value set end 
+ * @package     iTopORM
+ */
+define('VS_END', '}');
+
+
+define('SIBUSQLPARAMREGEXP', "/\\$\\[(.*)\\:(.*)\\:(.*)\\]/U");
+define('SIBUSQLTHISREGEXP', "/this\\.(.*)/U");
+
+
+/**
+ * Define filters for a given class of objects (formerly named "filter") 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @mytagrom    youpi
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class DBObjectSearch
+{
+	private $m_sClass;
+	private $m_sClassAlias;
+	private $m_aClasses; // queried classes (alias => class name)
+	private $m_oSearchCondition;
+	private $m_aFullText;
+	private $m_aPointingTo;
+	private $m_aReferencedBy;
+	private $m_aRelatedTo;
+
+	public function __construct($sClass, $sClassAlias = '')
+	{
+		if (empty($sClassAlias)) $sClassAlias = $sClass;
+		assert('is_string($sClass)');
+		assert('MetaModel::IsValidClass($sClass)'); // #@# could do better than an assert, or at least give the caller's reference
+		// => idee d'un assert avec call stack (autre utilisation = echec sur query SQL)
+		if (empty($sClassAlias)) $sClassAlias = $sClass;
+		$this->m_sClass = $sClass;
+		$this->m_sClassAlias = $sClassAlias;
+		$this->m_aClasses = array($sClassAlias => $sClass);
+		$this->m_oSearchCondition = new TrueExpression;
+		$this->m_aFullText = array();
+		$this->m_aPointingTo = array();
+		$this->m_aReferencedBy = array();
+		$this->m_aRelatedTo = array();
+	}
+
+	public function IsAny()
+	{
+		// #@# todo - if (!$this->m_oSearchCondition->IsTrue()) return false;
+		if (count($this->m_aFullText) > 0) return false;
+		if (count($this->m_aPointingTo) > 0) return false;
+		if (count($this->m_aReferencedBy) > 0) return false;
+		if (count($this->m_aRelatedTo) > 0) return false;
+		return true;
+	}
+	
+	public function Describe()
+	{
+		// To replace __Describe
+	}
+
+	public function DescribeConditionPointTo($sExtKeyAttCode)
+	{
+		if (!isset($this->m_aPointingTo[$sExtKeyAttCode])) return "";
+		$oFilter = $this->m_aPointingTo[$sExtKeyAttCode];
+		if ($oFilter->IsAny()) return "";
+		$oAtt = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
+		return $oAtt->GetLabel()." having ({$oFilter->DescribeConditions()})";
+	}
+
+	public function DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode)
+	{
+		if (!isset($this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode])) return "";
+		$oFilter = $this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode];
+		if ($oFilter->IsAny()) return "";
+		$oAtt = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode);
+		return "being ".$oAtt->GetLabel()." for ".$sForeignClass."s in ({$oFilter->DescribeConditions()})";
+	}
+
+	public function DescribeConditionRelTo($aRelInfo)
+	{
+		$oFilter = $aRelInfo['flt'];
+		$sRelCode = $aRelInfo['relcode'];
+		$iMaxDepth = $aRelInfo['maxdepth'];
+		return "related ($sRelCode... peut mieux faire !, $iMaxDepth dig depth) to a {$oFilter->GetClass()} ({$oFilter->DescribeConditions()})";
+	}
+
+	public function DescribeConditions()
+	{
+		$aConditions = array();
+
+		$aCondFT = array();
+		foreach($this->m_aFullText as $sFullText)
+		{
+			$aCondFT[] = " contain word(s) '$sFullText'";
+		}
+		if (count($aCondFT) > 0)
+		{
+			$aConditions[] = "which ".implode(" and ", $aCondFT);
+		}
+
+		// #@# todo - review textual description of the JOIN and search condition (is that still feasible?)
+		$aConditions[] = $this->RenderCondition();
+
+		$aCondPoint = array();
+		foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
+		{
+			if ($oFilter->IsAny()) continue;
+			$aCondPoint[] = $this->DescribeConditionPointTo($sExtKeyAttCode);
+		}
+		if (count($aCondPoint) > 0)
+		{
+			$aConditions[] = implode(" and ", $aCondPoint);
+		}
+
+		$aCondReferred= array();
+		foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
+		{
+			foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
+			{
+				if ($oForeignFilter->IsAny()) continue;
+				$aCondReferred[] = $this->DescribeConditionRefBy($sForeignClass, $sForeignExtKeyAttCode);
+			}
+		}
+		foreach ($this->m_aRelatedTo as $aRelInfo)
+		{
+			$aCondReferred[] = $this->DescribeConditionRelTo($aRelInfo);
+		}
+		if (count($aCondReferred) > 0)
+		{
+			$aConditions[] = implode(" and ", $aCondReferred);
+		}
+
+		return implode(" and ", $aConditions);		
+	}
+	
+	public function __DescribeHTML()
+	{
+		$sConditionDesc = $this->DescribeConditions();
+		if (!empty($sConditionDesc))
+		{
+			return "Objects of class '$this->m_sClass', $sConditionDesc";
+		}
+		return "Any object of class '$this->m_sClass'";
+	}
+
+	protected function TransferConditionExpression($oFilter, $aTranslation)
+	{
+		$oTranslated = $oFilter->GetCriteria()->Translate($aTranslation, false);
+		$this->AddConditionExpression($oTranslated);
+	}
+
+	public function ResetCondition()
+	{
+		$this->m_oSearchCondition = new TrueExpression();
+		// ? is that usefull/enough, do I need to rebuild the list after the subqueries ?
+		// $this->m_aClasses = array($this->m_sClassAlias => $this->m_sClass);
+	}
+
+	public function AddConditionExpression($oExpression)
+	{
+		$this->m_oSearchCondition = $this->m_oSearchCondition->LogAnd($oExpression); 
+	}
+
+	public function AddCondition($sFilterCode, $value, $sOpCode = null)
+	{
+	// #@# backward compatibility for pkey/id
+	if (strtolower(trim($sFilterCode)) == 'pkey') $sFilterCode = 'id';
+// #@# todo - obsolete smoothly, first send exceptions
+//		throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
+
+		MyHelpers::CheckKeyInArray('filter code', $sFilterCode, MetaModel::GetClassFilterDefs($this->m_sClass));
+		$oFilterDef = MetaModel::GetClassFilterDef($this->m_sClass, $sFilterCode);
+
+		if (empty($sOpCode))
+		{
+			$sOpCode = $oFilterDef->GetLooseOperator();
+		}
+		MyHelpers::CheckKeyInArray('operator', $sOpCode, $oFilterDef->GetOperators());
+
+		// Preserve backward compatibility - quick n'dirty way to change that API semantic
+		//
+		$oField = new FieldExpression($sFilterCode, $this->m_sClassAlias);
+		switch($sOpCode)
+		{
+		case 'SameDay':
+		case 'SameMonth':
+		case 'SameYear':
+		case 'Today':
+		case '>|':
+		case '<|':
+		case '=|':
+			throw new CoreException('Deprecated operator, please consider using OQL (SQL) expressions like "(TO_DAYS(NOW()) - TO_DAYS(x)) AS AgeDays"', array('operator' => $sOpCode));
+			break;
+
+		case "IN":
+			if (!is_array($value)) $value = array($value);
+			$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
+			$sOQLCondition = $oField->Render()." IN $sListExpr";
+			break;
+
+		case "NOTIN":
+			if (!is_array($value)) $value = array($value);
+			$sListExpr = '('.implode(', ', CMDBSource::Quote($value)).')';
+			$sOQLCondition = $oField->Render()." NOT IN $sListExpr";
+			break;
+
+		case 'Contains':
+			$oRightExpr = new ScalarExpression("%$value%");
+			$sOperator = 'LIKE';
+			break;
+
+		case 'Begins with':
+			$oRightExpr = new ScalarExpression("$value%");
+			$sOperator = 'LIKE';
+			break;
+
+		case 'Finishes with':
+			$oRightExpr = new ScalarExpression("%$value");
+			$sOperator = 'LIKE';
+			break;
+
+		default:
+			$oRightExpr = new ScalarExpression($value);
+			$sOperator = $sOpCode;
+		}
+
+		switch($sOpCode)
+		{
+		case "IN":
+		case "NOTIN":
+			$oNewCondition = Expression::FromOQL($sOQLCondition);
+			break;
+
+		case 'Contains':
+		case 'Begins with':
+		case 'Finishes with':
+		default:
+			$oNewCondition = new BinaryExpression($oField, $sOperator, $oRightExpr);
+		}
+
+		$this->AddConditionExpression($oNewCondition);
+	}
+
+	public function AddCondition_FullText($sFullText)
+	{
+		$this->m_aFullText[] = $sFullText;
+	}
+
+	protected function AddToNameSpace(&$aClassAliases, &$aAliasTranslation)
+	{
+		$sOrigAlias = $this->m_sClassAlias;
+		if (array_key_exists($sOrigAlias, $aClassAliases))
+		{
+			$this->m_sClassAlias = MetaModel::GenerateUniqueAlias($aClassAliases, $sOrigAlias, $this->m_sClass);
+			// Translate the condition expression with the new alias
+			$aAliasTranslation[$sOrigAlias]['*'] = $this->m_sClassAlias;
+		}
+
+		foreach($this->m_aPointingTo as $sExtKeyAttCode=>$oFilter)
+		{
+			$oFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
+		}
+
+		foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
+		{
+			foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
+			{
+				$oForeignFilter->AddToNameSpace($aClassAliases, $aAliasTranslation);
+			}
+		}
+	}
+
+	public function AddCondition_PointingTo(DBObjectSearch $oFilter, $sExtKeyAttCode)
+	{
+		$aAliasTranslation = array();
+		$res = $this->AddCondition_PointingTo_InNameSpace($oFilter, $sExtKeyAttCode, $this->m_aClasses, $aAliasTranslation);
+		$this->TransferConditionExpression($oFilter, $aAliasTranslation);
+		return $res;
+	}
+
+	protected function AddCondition_PointingTo_InNameSpace(DBObjectSearch $oFilter, $sExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
+	{
+		if (!MetaModel::IsValidKeyAttCode($this->GetClass(), $sExtKeyAttCode))
+		{
+			throw new CoreWarning("The attribute code '$sExtKeyAttCode' is not an external key of the class '{$this->GetClass()}' - the condition will be ignored");
+		}
+		$oAttExtKey = MetaModel::GetAttributeDef($this->GetClass(), $sExtKeyAttCode);
+		if(!MetaModel::IsSameFamilyBranch($oFilter->GetClass(), $oAttExtKey->GetTargetClass()))
+		{
+			throw new CoreException("The specified filter (pointing to {$oFilter->GetClass()}) is not compatible with the key '{$this->GetClass()}::$sExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
+		}
+
+		if (array_key_exists($sExtKeyAttCode, $this->m_aPointingTo))
+		{
+			$this->m_aPointingTo[$sExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
+		}
+		else
+		{
+			$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
+
+			// #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
+			// $oNewFilter = clone $oFilter;
+			// $oNewFilter->ResetCondition();
+
+			$this->m_aPointingTo[$sExtKeyAttCode] = $oFilter;
+		}
+	}
+
+	public function AddCondition_ReferencedBy(DBObjectSearch $oFilter, $sForeignExtKeyAttCode)
+	{
+		$aAliasTranslation = array();
+		$res = $this->AddCondition_ReferencedBy_InNameSpace($oFilter, $sForeignExtKeyAttCode, $this->m_aClasses, $aAliasTranslation);
+		$this->TransferConditionExpression($oFilter, $aAliasTranslation);
+		return $res;
+	}
+
+	protected function AddCondition_ReferencedBy_InNameSpace(DBObjectSearch $oFilter, $sForeignExtKeyAttCode, &$aClassAliases, &$aAliasTranslation)
+	{
+		$sForeignClass = $oFilter->GetClass();
+		$sForeignClassAlias = $oFilter->GetClassAlias();
+		if (!MetaModel::IsValidKeyAttCode($sForeignClass, $sForeignExtKeyAttCode))
+		{
+			throw new CoreException("The attribute code '$sForeignExtKeyAttCode' is not an external key of the class '{$sForeignClass}' - the condition will be ignored");
+		}
+		$oAttExtKey = MetaModel::GetAttributeDef($sForeignClass, $sForeignExtKeyAttCode);
+		if(!MetaModel::IsSameFamilyBranch($this->GetClass(), $oAttExtKey->GetTargetClass()))
+		{
+			throw new CoreException("The specified filter (objects referencing an object of class {$this->GetClass()}) is not compatible with the key '{$sForeignClass}::$sForeignExtKeyAttCode', which is pointing to {$oAttExtKey->GetTargetClass()}");
+		}
+		if (array_key_exists($sForeignClass, $this->m_aReferencedBy) && array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sForeignClass]))
+		{
+			$this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]->MergeWith_InNamespace($oFilter, $aClassAliases, $aAliasTranslation);
+		}
+		else
+		{
+			$oFilter->AddToNamespace($aClassAliases, $aAliasTranslation);
+
+			// #@# The condition expression found in that filter should not be used - could be another kind of structure like a join spec tree !!!!
+			//$oNewFilter = clone $oFilter;
+			//$oNewFilter->ResetCondition();
+
+			$this->m_aReferencedBy[$sForeignClass][$sForeignExtKeyAttCode]= $oFilter;
+		}
+	}
+
+	public function AddCondition_LinkedTo(DBObjectSearch $oLinkFilter, $sExtKeyAttCodeToMe, $sExtKeyAttCodeTarget, DBObjectSearch $oFilterTarget)
+	{
+		$oLinkFilterFinal = clone $oLinkFilter;
+		$oLinkFilterFinal->AddCondition_PointingTo($sExtKeyAttCodeToMe);
+
+		$this->AddCondition_ReferencedBy($oLinkFilterFinal, $sExtKeyAttCodeToMe);
+	}
+
+	public function AddCondition_RelatedTo(DBObjectSearch $oFilter, $sRelCode, $iMaxDepth)
+	{
+		MyHelpers::CheckValueInArray('relation code', $sRelCode, MetaModel::EnumRelations());
+		$this->m_aRelatedTo[] = array('flt'=>$oFilter, 'relcode'=>$sRelCode, 'maxdepth'=>$iMaxDepth);
+	}
+
+	public function MergeWith($oFilter)
+	{
+		$aAliasTranslation = array();
+		$res = $this->MergeWith_InNamespace($oFilter, $this->m_aClasses, $aAliasTranslation);
+		$this->TransferConditionExpression($oFilter, $aAliasTranslation);
+		return $res;
+	}
+
+	protected function MergeWith_InNamespace($oFilter, &$aClassAliases, &$aAliasTranslation)
+	{
+		if ($this->GetClass() != $oFilter->GetClass())
+		{
+			throw new CoreException("Attempting to merge a filter of class '{$this->GetClass()}' with a filter of class '{$oFilter->GetClass()}'");
+		}
+
+		// Translate search condition into our aliasing scheme
+		$aAliasTranslation[$oFilter->GetClassAlias()]['*'] = $this->GetClassAlias(); 
+
+		$this->m_aFullText = array_merge($this->m_aFullText, $oFilter->m_aFullText);
+		$this->m_aRelatedTo = array_merge($this->m_aRelatedTo, $oFilter->m_aRelatedTo);
+
+		foreach($oFilter->m_aPointingTo as $sExtKeyAttCode=>$oExtFilter)
+		{
+			$this->AddCondition_PointingTo_InNamespace($oExtFilter, $sExtKeyAttCode, $aClassAliases, $aAliasTranslation);
+		}
+		foreach($oFilter->m_aReferencedBy as $sForeignClass => $aReferences)
+		{
+			foreach($aReferences as $sForeignExtKeyAttCode => $oForeignFilter)
+			{
+				$this->AddCondition_ReferencedBy_InNamespace($oForeignFilter, $sForeignExtKeyAttCode, $aClassAliases, $aAliasTranslation);
+			}
+		}
+	}
+
+
+	public function GetClassName($sAlias) {return $this->m_aClasses[$sAlias];}
+	public function GetClasses() {return $this->m_aClasses;}
+
+	public function GetClass() {return $this->m_sClass;}
+	public function GetClassAlias() {return $this->m_sClassAlias;}
+	public function GetCriteria() {return $this->m_oSearchCondition;}
+	public function GetCriteria_FullText() {return $this->m_aFullText;}
+	public function GetCriteria_PointingTo($sKeyAttCode = "")
+	{
+		if (empty($sKeyAttCode))
+		{
+			return $this->m_aPointingTo;
+		}
+		if (!array_key_exists($sKeyAttCode, $this->m_aPointingTo)) return null;
+		return $this->m_aPointingTo[$sKeyAttCode];
+	}
+	public function GetCriteria_ReferencedBy($sRemoteClass = "", $sForeignExtKeyAttCode = "")
+	{
+		if (empty($sRemoteClass))
+		{
+			return $this->m_aReferencedBy;
+		}
+		if (!array_key_exists($sRemoteClass, $this->m_aReferencedBy)) return null;
+		if (empty($sForeignExtKeyAttCode))
+		{
+			return $this->m_aReferencedBy[$sRemoteClass];
+		}
+		if (!array_key_exists($sForeignExtKeyAttCode, $this->m_aReferencedBy[$sRemoteClass])) return null;
+		return $this->m_aReferencedBy[$sRemoteClass][$sForeignExtKeyAttCode];
+	}
+	public function GetCriteria_RelatedTo()
+	{
+		return $this->m_aRelatedTo;
+	}
+
+	public function RenderCondition()
+	{
+		return $this->m_oSearchCondition->Render();
+	}
+
+	public function serialize()
+	{
+		// Efficient but resulting in long strings:
+		// -> return (base64_encode(serialize($this)));
+
+		$sValue = $this->GetClass()."\n";
+		$sValue .= $this->GetClassAlias()."\n";
+
+		foreach($this->m_aClasses as $sClassAlias => $sClass)
+		{
+			// A stands for "Aliases"
+			$sValue .= "A:$sClassAlias:$sClass\n";
+		}
+		foreach($this->m_aFullText as $sFullText)
+		{
+			// F stands for "Full text"
+			$sValue .= "F:".$sFullText."\n";
+		}
+		$sValue .= "C:".$this->m_oSearchCondition->serialize()."\n";
+
+		foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
+		{
+			// P stands for "Pointing to"
+			$sValue .= "P:".$sExtKey.":".$oFilter->serialize()."\n";
+		}
+		foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
+		{
+			foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
+			{
+				// R stands for "Referenced by"
+				$sValue .= "R:".$sForeignExtKeyAttCode.":".$oForeignFilter->serialize()."\n";
+			}
+		}
+		foreach($this->m_aRelatedTo as $aRelatedTo)
+		{
+			$oFilter = $aRelatedTo['flt'];
+			$sRelCode = $aRelatedTo['relcode'];
+			$iMaxDepth = $aRelatedTo['maxdepth'];
+			
+			$sValue .= "T:".$oFilter->serialize().":$sRelCode:$iMaxDepth";
+		}
+		return base64_encode($sValue);
+	}
+	
+	static public function unserialize($sValue)
+	{
+		// See comment above...
+		// -> return (unserialize(base64_decode($sValue)));
+
+		$sClearText = base64_decode($sValue);
+		$aValues = split("\n", $sClearText);
+		$i = 0;
+		$sClass = $aValues[$i++];
+		$sClassAlias = $aValues[$i++];
+		$oFilter = new DBObjectSearch($sClass, $sClassAlias);
+		while($i < count($aValues) && !empty($aValues[$i]))
+		{
+			$aCondition = split(":", $aValues[$i++]);
+			switch ($aCondition[0])
+			{
+			case "A":
+				$oFilter->m_aClasses[$aCondition[1]] = $aCondition[2];
+				break;
+			case "F":
+				$oFilter->AddCondition_FullText($aCondition[1]);
+				break;
+			case "C":
+				$oFilter->m_oSearchCondition = Expression::unserialize($aCondition[1]);
+				break;
+			case "P":
+				//$oAtt = DBObject::GetAttributeDef($sClass, $aCondition[1]);
+				//$sRemoteClass = $oAtt->GetTargetClass();
+				$oSubFilter = self::unserialize($aCondition[2]);
+				$sExtKeyAttCode = $aCondition[1];
+				$oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode);
+				break;
+			case "R":
+				$oRemoteFilter = self::unserialize($aCondition[2]);
+				$sExtKeyAttCodeToMe = $aCondition[1];
+				$oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe);
+				break;
+			case "T":
+				$oSubFilter = self::unserialize($aCondition[1]);
+				$sRelCode = $aCondition[2];
+				$iMaxDepth = $aCondition[3];
+				$oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth);
+			default:
+				throw new CoreException("invalid filter definition (cannot unserialize the data, clear text = '$sClearText')");
+			}
+		}
+		return $oFilter;
+	}
+
+	// SImple BUt Structured Query Languag - SubuSQL
+	//
+	static private function Value2Expression($value)
+	{
+		$sRet = $value;
+		if (is_array($value))
+		{
+			$sRet = VS_START.implode(', ', $value).VS_END;
+		}
+		else if (!is_numeric($value))
+		{
+			$sRet = "'".addslashes($value)."'";
+		}
+		return $sRet;
+	}
+	static private function Expression2Value($sExpr)
+	{
+		$retValue = $sExpr;
+		if ((substr($sExpr, 0, 1) == "'") && (substr($sExpr, -1, 1) == "'"))
+		{
+			$sNoQuotes = substr($sExpr, 1, -1);
+			return stripslashes($sNoQuotes);
+		}
+		if ((substr($sExpr, 0, 1) == VS_START) && (substr($sExpr, -1, 1) == VS_END))
+		{
+			$sNoBracket = substr($sExpr, 1, -1);
+			$aRetValue = array();
+			foreach (explode(",", $sNoBracket) as $sItem)
+			{
+				$aRetValue[] = self::Expression2Value(trim($sItem));
+			}
+			return $aRetValue;
+		}
+		return $retValue;
+	}
+
+	public function ToOQL()
+	{
+		$sRes = "SELECT ".$this->GetClass().' AS '.$this->GetClassAlias();
+		$sRes .= $this->ToOQL_Joins();
+		$sRes .= " WHERE ".$this->m_oSearchCondition->Render();
+		return $sRes;
+	}
+
+	protected function ToOQL_Joins()
+	{
+		$sRes = '';
+		foreach($this->m_aPointingTo as $sExtKey=>$oFilter)
+		{
+			$sRes .= ' JOIN '.$oFilter->GetClass().' AS '.$oFilter->GetClassAlias().' ON '.$this->GetClassAlias().'.'.$sExtKey.' = '.$oFilter->GetClassAlias().'.id';
+			$sRes .= $oFilter->ToOQL_Joins();
+		}
+		foreach($this->m_aReferencedBy as $sForeignClass=>$aReferences)
+		{
+			foreach($aReferences as $sForeignExtKeyAttCode=>$oForeignFilter)
+			{
+				$sRes .= ' JOIN '.$oForeignFilter->GetClass().' AS '.$oForeignFilter->GetClassAlias().' ON '.$oForeignFilter->GetClassAlias().'.'.$sForeignExtKeyAttCode.' = '.$this->GetClassAlias().'.id';
+				$sRes .= $oForeignFilter->ToOQL_Joins();
+			}
+		}
+		return $sRes;
+	}
+
+	public function ToSibusQL()
+	{
+		return "NONONO";
+	}
+
+	static private function privProcessParams($sQuery, array $aParams, $oDbObject)
+	{
+		$iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER);
+		if ($iPlaceHoldersCount > 0)
+		{
+			foreach($aMatches as $aMatch)
+			{
+				$sStringToSearch = $aMatch[0];
+				$sParameterName = $aMatch[1];
+				$sDefaultValue = $aMatch[2];
+				$sDescription = $aMatch[3];
+				
+				$sValue = $sDefaultValue;
+				if (array_key_exists($sParameterName, $aParams))
+				{
+					$sValue = $aParams[$sParameterName];
+					unset($aParams[$sParameterName]);
+				}
+				else if (is_object($oDbObject))
+				{
+					if (strpos($sParameterName, "this.") === 0)
+					{
+						$sAttCode = substr($sParameterName, strlen("this."));
+						if ($sAttCode == 'id')
+						{
+							$sValue = $oDbObject->GetKey();
+						}
+						else if ($sAttCode == 'class')
+						{
+							$sValue = get_class($oDbObject);
+						}
+						else if (MetaModel::IsValidAttCode(get_class($oDbObject), $sAttCode))
+						{
+							$sValue = $oDbObject->Get($sAttCode);
+						}
+					}
+				}
+				$sQuery = str_replace($sStringToSearch, $sValue, $sQuery);
+			}
+		}
+		if (count($aParams) > 0)
+		{
+			throw new CoreException("Unused parameter(s) for this SibusQL expression: (".implode(', ', array_keys($aParams)).")");
+		}
+		return $sQuery;
+	}
+
+	static public function ListSibusQLParams($sQuery)
+	{
+		$aRet = array();
+		$iPlaceHoldersCount = preg_match_all(SIBUSQLPARAMREGEXP, $sQuery, $aMatches, PREG_SET_ORDER);
+		if ($iPlaceHoldersCount > 0)
+		{
+			foreach($aMatches as $aMatch)
+			{
+				$sStringToSearch = $aMatch[0];
+				$sParameterName = $aMatch[1];
+				$sDefaultValue = $aMatch[2];
+				$sDescription = $aMatch[3];
+				$aRet[$sParameterName]["description"] = $sDescription;
+				$aRet[$sParameterName]["default"] = $sDefaultValue;
+			}
+		}
+		return $aRet;
+	}
+
+	protected function OQLExpressionToCondition($sQuery, $oExpression, $aClassAliases)
+	{
+		if ($oExpression instanceof BinaryOqlExpression)
+		{
+			$sOperator = $oExpression->GetOperator();
+			$oLeft = $this->OQLExpressionToCondition($sQuery, $oExpression->GetLeftExpr(), $aClassAliases);
+			$oRight = $this->OQLExpressionToCondition($sQuery, $oExpression->GetRightExpr(), $aClassAliases);
+			return new BinaryExpression($oLeft, $sOperator, $oRight);
+		}
+		elseif ($oExpression instanceof FieldOqlExpression)
+		{
+			$sClassAlias = $oExpression->GetParent();
+			$sFltCode = $oExpression->GetName();
+			if (empty($sClassAlias))
+			{
+				// Try to find an alias
+				// Build an array of field => array of aliases
+				$aFieldClasses = array();
+				foreach($aClassAliases as $sAlias => $sReal)
+				{
+					foreach(MetaModel::GetFiltersList($sReal) as $sAnFltCode)
+					{
+						$aFieldClasses[$sAnFltCode][] = $sAlias;
+					}
+				}
+				if (!array_key_exists($sFltCode, $aFieldClasses))
+				{
+					throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), array_keys($aFieldClasses));
+				}
+				if (count($aFieldClasses[$sFltCode]) > 1)
+				{
+					throw new OqlNormalizeException('Ambiguous filter code', $sQuery, $oExpression->GetNameDetails());
+				}
+				$sClassAlias = $aFieldClasses[$sFltCode][0];
+			}
+			else
+			{
+				if (!array_key_exists($sClassAlias, $aClassAliases))
+				{
+					throw new OqlNormalizeException('Unknown class [alias]', $sQuery, $oExpression->GetParentDetails(), array_keys($aClassAliases));
+				}
+				$sClass = $aClassAliases[$sClassAlias];
+				if (!MetaModel::IsValidFilterCode($sClass, $sFltCode))
+				{
+					throw new OqlNormalizeException('Unknown filter code', $sQuery, $oExpression->GetNameDetails(), MetaModel::GetFiltersList($sClass));
+				}
+			}
+
+			return new FieldExpression($sFltCode, $sClassAlias);
+		}
+		elseif ($oExpression instanceof TrueOqlExpression)
+		{
+			return new TrueExpression;
+		}
+		elseif ($oExpression instanceof ScalarOqlExpression)
+		{
+			return new ScalarExpression($oExpression->GetValue());
+		}
+		elseif ($oExpression instanceof ListOqlExpression)
+		{
+			return new ListExpression($oExpression->GetItems());
+		}
+		elseif ($oExpression instanceof FunctionOqlExpression)
+		{
+			return new FunctionExpression($oExpression->GetVerb(), $oExpression->GetArgs());
+		}
+		else
+		{
+			throw new CoreException('Unknown expression type', array('class'=>get_class($oExpression), 'query'=>$sQuery));
+		}
+	}
+
+	static public function FromOQL($sQuery, array $aParams = array(), $oObject = null)
+	{
+		if (empty($sQuery)) return null;
+
+		$oOql = new OqlInterpreter($sQuery);
+		$oOqlQuery = $oOql->ParseQuery();
+		
+		$sClass = $oOqlQuery->GetClass();
+		$sClassAlias = $oOqlQuery->GetClassAlias();
+
+		if (!MetaModel::IsValidClass($sClass))
+		{
+			throw new OqlNormalizeException('Unknown class', $sQuery, $oOqlQuery->GetClassDetails(), MetaModel::GetClasses());
+		}
+
+		$oResultFilter = new DBObjectSearch($sClass, $sClassAlias);
+		$aAliases = array($sClassAlias => $sClass);
+
+		// Maintain an array of filters, because the flat list is in fact referring to a tree
+		// And this will be an easy way to dispatch the conditions
+		// $oResultFilter will be referenced by the other filters, or the other way around...
+		$aJoinItems = array($sClassAlias => $oResultFilter);
+
+		$aJoinSpecs = $oOqlQuery->GetJoins();
+		if (is_array($aJoinSpecs))
+		{
+			foreach ($aJoinSpecs as $oJoinSpec)
+			{
+				$sJoinClass = $oJoinSpec->GetClass();
+				$sJoinClassAlias = $oJoinSpec->GetClassAlias();
+				if (!MetaModel::IsValidClass($sJoinClass))
+				{
+					throw new OqlNormalizeException('Unknown class', $sQuery, $oJoinSpec->GetClassDetails(), MetaModel::GetClasses());
+				}
+				if (array_key_exists($sJoinClassAlias, $aAliases))
+				{
+					if ($sJoinClassAlias != $sJoinClass)
+					{
+						throw new OqlNormalizeException('Duplicate class alias', $sQuery, $oJoinSpec->GetClassAliasDetails());
+					}
+					else
+					{
+						throw new OqlNormalizeException('Duplicate class name', $sQuery, $oJoinSpec->GetClassDetails());
+					}
+				} 
+
+				// Assumption: ext key on the left only !!!
+				// normalization should take care of this
+				$oLeftField = $oJoinSpec->GetLeftField();
+				$sFromClass = $oLeftField->GetParent();
+				$sExtKeyAttCode = $oLeftField->GetName();
+
+				$oRightField = $oJoinSpec->GetRightField();
+				$sToClass = $oRightField->GetParent();
+				$sPKeyDescriptor = $oRightField->GetName();
+				if ($sPKeyDescriptor != 'id')
+				{
+					throw new OqlNormalizeException('Wrong format for Join clause (right hand), expecting an id', $sQuery, $oRightField->GetNameDetails(), array('id'));
+				}
+
+				$aAliases[$sJoinClassAlias] = $sJoinClass;
+				$aJoinItems[$sJoinClassAlias] = new DBObjectSearch($sJoinClass, $sJoinClassAlias);
+
+				if (!array_key_exists($sFromClass, $aJoinItems))
+				{
+					throw new OqlNormalizeException('Unknown class in join condition (left expression)', $sQuery, $oLeftField->GetParentDetails(), array_keys($aJoinItems));
+				}
+				if (!array_key_exists($sToClass, $aJoinItems))
+				{
+					throw new OqlNormalizeException('Unknown class in join condition (right expression)', $sQuery, $oRightField->GetParentDetails(), array_keys($aJoinItems));
+				}
+				$aExtKeys = array_keys(MetaModel::GetExternalKeys($aAliases[$sFromClass]));
+				if (!in_array($sExtKeyAttCode, $aExtKeys))
+				{
+					throw new OqlNormalizeException('Unknown external key in join condition (left expression)', $sQuery, $oLeftField->GetNameDetails(), $aExtKeys);
+				}
+
+				if ($sFromClass == $sJoinClassAlias)
+				{
+					$aJoinItems[$sToClass]->AddCondition_ReferencedBy($aJoinItems[$sFromClass], $sExtKeyAttCode);
+				}
+				else
+				{
+					$aJoinItems[$sFromClass]->AddCondition_PointingTo($aJoinItems[$sToClass], $sExtKeyAttCode);
+				}
+			}
+		}
+
+		$oConditionTree = $oOqlQuery->GetCondition();
+		if ($oConditionTree instanceof Expression)
+		{
+			$oResultFilter->m_oSearchCondition = $oResultFilter->OQLExpressionToCondition($sQuery, $oConditionTree, $aAliases);
+		}
+
+		return $oResultFilter;
+	}
+
+	static public function FromSibusQL($sQuery, array $aParams = array(), $oObject = null)
+	{
+		if (empty($sQuery)) return null;
+		$sQuery = self::privProcessParams($sQuery, $aParams, $oObject);
+
+		$iSepPos = strpos($sQuery, ":");
+		if ($iSepPos === false)
+		{
+			if (preg_match('@^\\s*SELECT@', $sQuery))
+			{
+				return self::FromOQL($sQuery, $aParams, $oObject);
+			}
+			// Only the class was specified -> all rows are required
+			$sClass = trim($sQuery);
+			$oFilter = new DBObjectSearch($sClass);
+		}
+		else
+		{
+			$sClass = trim(substr($sQuery, 0, $iSepPos));
+			$sConds = trim(substr($sQuery, $iSepPos + 1));
+			$aValues = split(" AND ", $sConds);
+	
+			$oFilter = new DBObjectSearch($sClass);
+	
+			foreach ($aValues as $sCond)
+			{
+				$sCond = trim($sCond);
+				
+				if (strpos($sCond, "* HAS ") === 0)
+				{
+					$sValue = self::Expression2Value(substr($sCond, strlen("* HAS ")));
+					$oFilter->AddCondition_FullText($sValue);
+				}
+				else if (preg_match("@^(\S+) IN \\((.+)\\)$@", $sCond, $aMatches))
+				{
+					$sExtKeyAttCode = $aMatches[1];
+					$sFilterExp = $aMatches[2];
+
+					$oSubFilter = self::FromSibuSQL($sFilterExp);
+					$oFilter->AddCondition_PointingTo($oSubFilter, $sExtKeyAttCode);
+				}
+				else if (strpos($sCond, "PKEY IS ") === 0)
+				{
+					if (preg_match("@^PKEY IS (\S+) IN \\((.+)\\)$@", $sCond, $aMatches))
+					{
+						$sExtKeyAttCodeToMe = $aMatches[1];
+						$sFilterExp = $aMatches[2];
+						$oRemoteFilter = self::FromSibuSQL($sFilterExp);
+						$oFilter->AddCondition_ReferencedBy($oRemoteFilter, $sExtKeyAttCodeToMe);
+					}
+				}
+				else if (strpos($sCond, "RELATED") === 0)
+				{
+					if (preg_match("@^RELATED\s*\\((.+)\\)\s*TO\s*\\((.+)\\)@", trim($sCond), $aMatches))
+					{
+						$aRelation = explode(',', trim($aMatches[1]));
+						$sRelCode = trim($aRelation[0]);
+						$iMaxDepth = intval(trim($aRelation[1]));
+						$sFilterExp = trim($aMatches[2]);
+
+						$oSubFilter = self::FromSibuSQL($sFilterExp);
+						$oFilter->AddCondition_RelatedTo($oSubFilter, $sRelCode, $iMaxDepth);
+					}
+				}
+				else
+				{
+					$sOperandExpr = "'.*'|\d+|-\d+|".VS_START.".+".VS_END;
+					if (preg_match("@^(\S+)\s+(.*)\s+($sOperandExpr)$@", $sCond, $aMatches))
+					{
+						$sFltCode = trim($aMatches[1]);
+						$sOpCode = trim($aMatches[2]);
+						$value = self::Expression2Value($aMatches[3]);
+						$oFilter->AddCondition($sFltCode, $value, $sOpCode);
+					}
+					else
+					{
+						throw new CoreException("Wrong format for filter definition: '$sQuery'");
+					}
+				}
+			}
+		}
+
+// #@# todo - obsolete smoothly, first give the OQL version !
+//		throw new CoreException('SibusQL has been obsoleted, please update your queries', array('sibusql'=>$sQuery, 'oql'=>$oFilter->ToOQL()));
+
+		return $oFilter;
+	}
+
+	// Sexy display of a SibuSQL expression
+	static public function SibuSQLAsHtml($sQuery)
+	{
+		$sQuery = htmlentities($sQuery);
+		$aParams = self::ListSibusQLParams($sQuery);
+		$aParamValues = array();
+		foreach ($aParams as $sParamName => $aParamInfo)
+		{
+			$sDescription = $aParamInfo["description"];
+			$sDefaultValue = $aParamInfo["default"];
+			$aParamValues[$sParamName] = "<span style=\"background-color:#aaa;\" title\"$sDescription (default to '$sDefaultValue')\">$sParamName</span>";
+		}
+		$sQuery = self::privProcessParams($sQuery, $aParamValues, null);
+		return $sQuery;
+	}
+
+	public function toxpath()
+	{
+		// #@# a voir...
+	}
+	static public function fromxpath()
+	{
+		// #@# a voir...
+	}
+}
+
+
+?>

+ 250 - 0
core/dbobjectset.class.php

@@ -0,0 +1,250 @@
+<?php
+
+/**
+ * A set of persistent objects, could be heterogeneous 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class DBObjectSet
+{
+	private $m_oFilter;
+	private $m_aOrderBy;
+	public $m_bLoaded;
+	private $m_aData;
+	private $m_aId2Row;
+	private $m_iCurrRow;
+
+	public function __construct($oFilter, $aOrderBy = array())
+	{
+		$this->m_oFilter = $oFilter;
+		$this->m_aOrderBy = $aOrderBy;
+
+		$this->m_bLoaded = false;
+		$this->m_aData = array();
+		$this->m_aId2Row = array();
+		$this->m_iCurrRow = 0;
+	}
+
+	public function __destruct()
+	{
+	}
+
+	public function __toString()
+	{
+		$sRet = '';
+		$this->Rewind();
+		$sRet .= "Set (".$this->m_oFilter->ToSibuSQL().")<br/>\n";
+		$sRet .= "Query: <pre style=\"font-size: smaller; display:inline;\">".MetaModel::MakeSelectQuery($this->m_oFilter, array()).")</pre>\n";
+		
+		$sRet .= $this->Count()." records<br/>\n";
+		if ($this->Count() > 0)
+		{
+			$sRet .= "<ul class=\"treeview\">\n";
+			while ($oObj = $this->Fetch())
+			{
+				$sRet .= "<li>".$oObj->__toString()."</li>\n";
+			}
+			$sRet .= "</ul>\n";
+		}
+		return $sRet;
+	}
+
+	static public function FromScratch($sClass)
+	{
+		$oFilter = new CMDBSearchFilter($sClass);
+		$oRetSet = new self($oFilter);
+		$oRetSet->m_bLoaded = true; // no DB load
+		return $oRetSet;
+	} 
+
+	static public function FromArray($sClass, $aObjects)
+	{
+		$oFilter = new CMDBSearchFilter($sClass);
+		$oRetSet = new self($oFilter);
+		$oRetSet->m_bLoaded = true; // no DB load
+		$oRetSet->AddObjectArray($aObjects);
+		return $oRetSet;
+	} 
+
+	public function ToArray($bWithId = true)
+	{
+		$aRet = array();
+		$this->Rewind();
+		while ($oObject = $this->Fetch())
+		{
+			if ($bWithId)
+			{
+				$aRet[$oObject->GetKey()] = $oObject;
+			}
+			else
+			{
+				$aRet[] = $oObject;
+			}
+		}
+		return $aRet;
+	} 
+
+	public function GetFilter()
+	{
+		return $this->m_oFilter;
+	}
+
+	public function GetClass()
+	{
+		return $this->m_oFilter->GetClass();
+	}
+
+	public function GetRootClass()
+	{
+		return MetaModel::GetRootClass($this->GetClass());
+	}
+
+	public function Load()
+	{
+		if ($this->m_bLoaded) return;
+// #@# debug - echo "Loading (".$this->m_oFilter->ToSibuSQL().")....</br>\n";
+		$sSQL = MetaModel::MakeSelectQuery($this->m_oFilter, $this->m_aOrderBy);
+		$resQuery = CMDBSource::Query($sSQL);
+		if (!$resQuery) return;
+
+		while ($aRow = CMDBSource::FetchArray($resQuery))
+		{
+			$sClass = $this->m_oFilter->GetClass();
+			$oObject = MetaModel::GetObjectByRow($sClass, $aRow);
+			$this->AddObject($oObject);
+		}
+		CMDBSource::FreeResult($resQuery);
+
+		$this->m_bLoaded = true;
+	}
+
+	public function Count()
+	{
+		if (!$this->m_bLoaded) $this->Load();
+		return count($this->m_aData);
+	}
+
+	public function Fetch()
+	{
+		if (!$this->m_bLoaded) $this->Load();
+
+		if ($this->m_iCurrRow >= count($this->m_aData))
+		{
+			return null;
+		}
+		$oRetObj = $this->m_aData[$this->m_iCurrRow];
+		$this->m_iCurrRow++;
+		return $oRetObj;
+	}
+
+	public function Rewind()
+	{
+		$this->Seek(0);
+	}
+
+	public function Seek($iRow)
+	{
+		if (!$this->m_bLoaded) $this->Load();
+
+		$this->m_iCurrRow = min($iRow, count($this->m_aData));
+		return $this->m_iCurrRow;
+	}
+
+	public function AddObject($oObject)
+	{
+		// ?usefull? if ($oObject->GetClass() != $this->GetClass()) return;
+
+		// it is mandatory to avoid duplicates
+		if (array_key_exists($oObject->GetKey(), $this->m_aId2Row)) return;
+
+		// Do not load here, because the load uses that method too
+		$iNextPos = count($this->m_aData);
+		$this->m_aData[$iNextPos] = $oObject;
+		$this->m_aId2Row[$oObject->GetKey()] = $iNextPos;
+	}
+
+	public function AddObjectArray($aObjects)
+	{
+		foreach ($aObjects as $oObj)
+		{
+			$this->AddObject($oObj);
+		}
+	}
+
+	public function Merge($oObjectSet)
+	{
+		if ($this->GetRootClass() != $oObjectSet->GetRootClass())
+		{
+			throw new CoreException("Could not merge two objects sets if they don't have the same root class");
+		}
+		if (!$this->m_bLoaded) $this->Load();
+
+		$oObjectSet->Seek(0);
+		while ($oObject = $oObjectSet->Fetch())
+		{
+			$this->AddObject($oObject);
+		}
+	}
+
+	public function CreateIntersect($oObjectSet)
+	{
+		if ($this->GetRootClass() != $oObjectSet->GetRootClass())
+		{
+			throw new CoreException("Could not 'intersect' two objects sets if they don't have the same root class");
+		}
+		if (!$this->m_bLoaded) $this->Load();
+
+		$oNewSet = DBObjectSet::FromScratch($this->GetClass());
+
+		$oObjectSet->Seek(0);
+		while ($oObject = $oObjectSet->Fetch())
+		{
+			if (array_key_exists($oObject->GetKey(), $this->m_aId2Row))
+			{
+				$oNewSet->AddObject($oObject);
+			}
+		}
+		return $oNewSet;
+	}
+
+	public function CreateDelta($oObjectSet)
+	{
+		if ($this->GetRootClass() != $oObjectSet->GetRootClass())
+		{
+			throw new CoreException("Could not 'delta' two objects sets if they don't have the same root class");
+		}
+		if (!$this->m_bLoaded) $this->Load();
+
+		$oNewSet = DBObjectSet::FromScratch($this->GetClass());
+
+		$oObjectSet->Seek(0);
+		while ($oObject = $oObjectSet->Fetch())
+		{
+			if (!array_key_exists($oObject->GetKey(), $this->m_aId2Row))
+			{
+				$oNewSet->AddObject($oObject);
+			}
+		}
+		return $oNewSet;
+	}
+
+	public function GetRelatedObjects($sRelCode, $iMaxDepth = 99)
+	{
+		$aVisited = array(); // optimization for consecutive calls of MetaModel::GetRelatedObjects
+		$this->Seek(0);
+		while ($oObject = $this->Fetch())
+		{
+			$aRelatedObjs = $oObject->GetRelatedObjects($sRelCode, $iMaxDepth, $aVisited);
+		}
+		return $aRelatedObjs;
+	}
+}
+
+?>

+ 485 - 0
core/expression.class.inc.php

@@ -0,0 +1,485 @@
+<?php
+
+/**
+ * General definition of an expression tree (could be OQL, SQL or whatever) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class Expression
+{
+	// recursive translation of identifiers
+	abstract public function Translate($aTranslationData, $bMatchAll = true);
+
+	// recursive rendering
+	abstract public function Render();
+
+	// recursively builds an array of class => fieldname
+	abstract public function ListRequiredFields();
+
+	abstract public function IsTrue();
+
+	public function RequiresField($sClass, $sFieldName)
+	{
+		// #@# todo - optimize : this is called quite often when building a single query !
+		$aRequired = $this->ListRequiredFields();
+		if (!in_array($sClass.'.'.$sFieldName, $aRequired)) return false;
+		return true;
+	}
+
+	public function serialize()
+	{
+		return base64_encode($this->Render());
+	}
+	
+	static public function unserialize($sValue)
+	{
+		return self::FromOQL(base64_decode($sValue));
+	}
+
+	static public function FromOQL($sConditionExpr)
+	{
+		$oOql = new OqlInterpreter($sConditionExpr);
+		$oExpression = $oOql->ParseExpression();
+		
+		return $oExpression;
+	}
+
+	public function LogAnd($oExpr)
+	{
+		if ($this->IsTrue()) return clone $oExpr;
+		if ($oExpr->IsTrue()) return clone $this;
+		return new BinaryExpression($this, 'AND', $oExpr);
+	}
+
+	public function LogOr($oExpr)
+	{
+		return new BinaryExpression($this, 'OR', $oExpr);
+	}
+}
+
+
+class BinaryExpression extends Expression
+{
+	protected $m_oLeftExpr; // filter code or an SQL expression (later?)
+	protected $m_oRightExpr;
+	protected $m_sOperator;
+
+	public function __construct($oLeftExpr, $sOperator, $oRightExpr)
+	{
+		if (!is_object($oLeftExpr))
+		{
+			throw new CoreException('Expecting an Expression object on the left hand', array('found_type' => gettype($oLeftExpr)));
+		}
+		if (!is_object($oRightExpr))
+		{
+			throw new CoreException('Expecting an Expression object on the right hand', array('found_type' => gettype($oRightExpr)));
+		}
+		if (!$oLeftExpr instanceof Expression)
+		{
+			throw new CoreException('Expecting an Expression object on the left hand', array('found_class' => get_class($oLeftExpr)));
+		}
+		if (!$oRightExpr instanceof Expression)
+		{
+			throw new CoreException('Expecting an Expression object on the right hand', array('found_class' => get_class($oRightExpr)));
+		}
+		$this->m_oLeftExpr  = $oLeftExpr;
+		$this->m_oRightExpr = $oRightExpr;
+		$this->m_sOperator  = $sOperator;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		if ($this->m_sOperator == 'AND')
+		{
+			if ($this->m_oLeftExpr->IsTrue() && $this->m_oLeftExpr->IsTrue()) return true;
+		}
+		return false;
+	}
+
+	public function GetLeftExpr()
+	{
+		return $this->m_oLeftExpr;
+	}
+
+	public function GetRightExpr()
+	{
+		return $this->m_oRightExpr;
+	}
+
+	public function GetOperator()
+	{
+		return $this->m_sOperator;
+	}
+
+	// recursive rendering
+	public function Render()
+	{
+		$sOperator = $this->GetOperator();
+		$sLeft = $this->GetLeftExpr()->Render();
+		$sRight = $this->GetRightExpr()->Render();
+		return "($sLeft $sOperator $sRight)";
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		$oLeft = $this->GetLeftExpr()->Translate($aTranslationData, $bMatchAll);
+		$oRight = $this->GetRightExpr()->Translate($aTranslationData, $bMatchAll);
+		return new BinaryExpression($oLeft, $this->GetOperator(), $oRight);
+	}
+
+	public function ListRequiredFields()
+	{
+		$aLeft = $this->GetLeftExpr()->ListRequiredFields();
+		$aRight = $this->GetRightExpr()->ListRequiredFields();
+		return array_merge($aLeft, $aRight);
+	}
+}
+
+
+class UnaryExpression extends Expression
+{
+	protected $m_value;
+
+	public function __construct($value)
+	{
+		$this->m_value = $value;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return ($this->m_value == 1);
+	}
+
+	public function GetValue()
+	{
+		return $this->m_value;
+	} 
+
+	// recursive rendering
+	public function Render()
+	{
+		return CMDBSource::Quote($this->m_value);
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		return clone $this;
+	}
+
+	public function ListRequiredFields()
+	{
+		return array();
+	}
+}
+
+class ScalarExpression extends UnaryExpression
+{
+	public function __construct($value)
+	{
+		if (!is_scalar($value))
+		{
+			throw new CoreException('Attempt to create a scalar expression from a non scalar', array('var_type'=>gettype($value)));
+		}
+		parent::__construct($value);
+	}
+}
+
+class TrueExpression extends ScalarExpression
+{
+	public function __construct()
+	{
+		parent::__construct(1);
+	}
+
+	public function IsTrue()
+	{
+		return true;
+	}
+}
+
+class FieldExpression extends UnaryExpression
+{
+	protected $m_sParent;
+	protected $m_sName;
+
+	public function __construct($sName, $sParent = '')
+	{
+		parent::__construct("$sParent.$sName");
+
+		$this->m_sParent = $sParent;
+		$this->m_sName = $sName;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return false;
+	}
+
+	public function GetParent() {return $this->m_sParent;}
+	public function GetName() {return $this->m_sName;}
+
+	// recursive rendering
+	public function Render()
+	{
+		if (empty($this->m_sParent))
+		{
+			return "`{$this->m_sName}`";
+		}
+		return "`{$this->m_sParent}`.`{$this->m_sName}`";
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		if (!array_key_exists($this->m_sParent, $aTranslationData))
+		{
+			if ($bMatchAll) throw new CoreException('Unknown parent id in translation table', array('parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData)));
+			return clone $this;
+		} 
+		if (!array_key_exists($this->m_sName, $aTranslationData[$this->m_sParent]))
+		{
+			if (!array_key_exists('*', $aTranslationData[$this->m_sParent]))
+			{
+				// #@# debug - if ($bMatchAll) MyHelpers::var_dump_html($aTranslationData, true);
+				if ($bMatchAll) throw new CoreException('Unknown name in translation table', array('name' => $this->m_sName, 'parent_id' => $this->m_sParent, 'translation_table' => array_keys($aTranslationData[$this->m_sParent])));
+				return clone $this;
+			}
+			$sNewParent = $aTranslationData[$this->m_sParent]['*'];
+			$sNewName = $this->m_sName;
+		}
+		else
+		{
+			$sNewParent = $aTranslationData[$this->m_sParent][$this->m_sName][0];
+			$sNewName = $aTranslationData[$this->m_sParent][$this->m_sName][1];
+		}
+		return new FieldExpression($sNewName, $sNewParent);
+	}
+
+	public function ListRequiredFields()
+	{
+		return array($this->m_sParent.'.'.$this->m_sName);
+	}
+}
+
+
+// Temporary, until we implement functions and expression casting!
+// ... or until we implement a real full text search based in the MATCH() expression
+class ListExpression extends Expression
+{
+	protected $m_aExpressions;
+
+	public function __construct($aExpressions)
+	{
+		$this->m_aExpressions = $aExpressions;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return false;
+	}
+
+	public function GetItems()
+	{
+		return $this->m_aExpressions;
+	}
+
+	// recursive rendering
+	public function Render()
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$aRes[] = $oExpr->Render();
+		}
+		return '('.implode(', ', $aRes).')';
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
+		}
+		return new ListExpression($aRes);
+	}
+
+	public function ListRequiredFields()
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
+		}
+		return $aRes;
+	}
+}
+
+
+class FunctionExpression extends Expression
+{
+	protected $m_sVerb;
+	protected $m_aArgs; // array of expressions
+
+	public function __construct($sVerb, $aArgExpressions)
+	{
+		$this->m_sVerb = $sVerb;
+		$this->m_aArgs = $aArgExpressions;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return false;
+	}
+
+	public function GetVerb()
+	{
+		return $this->m_sVerb;
+	}
+
+	public function GetArgs()
+	{
+		return $this->m_aArgs;
+	}
+
+	// recursive rendering
+	public function Render()
+	{
+		$aRes = array();
+		foreach ($this->m_aArgs as $oExpr)
+		{
+			$aRes[] = $oExpr->Render();
+		}
+		return $this->m_sVerb.'('.implode(', ', $aRes).')';
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		$aRes = array();
+		foreach ($this->m_aArgs as $oExpr)
+		{
+			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
+		}
+		return new FunctionExpression($this->m_sVerb, $aRes);
+	}
+
+	public function ListRequiredFields()
+	{
+		$aRes = array();
+		foreach ($this->m_aArgs as $oExpr)
+		{
+			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
+		}
+		return $aRes;
+	}
+}
+
+class IntervalExpression extends Expression
+{
+	protected $m_oValue; // expression
+	protected $m_sUnit;
+
+	public function __construct($oValue, $sUnit)
+	{
+		$this->m_oValue = $oValue;
+		$this->m_sUnit = $sUnit;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return false;
+	}
+
+	public function GetValue()
+	{
+		return $this->m_oValue;
+	}
+
+	public function GetUnit()
+	{
+		return $this->m_sUnit;
+	}
+
+	// recursive rendering
+	public function Render()
+	{
+		return 'INTERVAL '.$this->m_oValue->Render().' '.$this->m_sUnit;
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		return new IntervalExpression($this->m_oValue->Translate($aTranslationData, $bMatchAll), $this->m_sUnit);
+	}
+
+	public function ListRequiredFields()
+	{
+		return array();
+	}
+}
+
+class CharConcatExpression extends Expression
+{
+	protected $m_aExpressions;
+
+	public function __construct($aExpressions)
+	{
+		$this->m_aExpressions = $aExpressions;
+	}
+
+	public function IsTrue()
+	{
+		// return true if we are certain that it will be true
+		return false;
+	}
+
+	public function GetItems()
+	{
+		return $this->m_aExpressions;
+	}
+
+	// recursive rendering
+	public function Render()
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$sCol = $oExpr->Render();
+			// Concat will be globally NULL if one single argument is null ! 
+			$aRes[] = "COALESCE($sCol, '')";
+		}
+		return "CAST(CONCAT(".implode(', ', $aRes).") AS CHAR)";
+	}
+
+	public function Translate($aTranslationData, $bMatchAll = true)
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$aRes[] = $oExpr->Translate($aTranslationData, $bMatchAll);
+		}
+		return new CharConcatExpression($aRes);
+	}
+
+	public function ListRequiredFields()
+	{
+		$aRes = array();
+		foreach ($this->m_aExpressions as $oExpr)
+		{
+			$aRes = array_merge($aRes, $oExpr->ListRequiredFields());
+		}
+		return $aRes;
+	}
+}
+
+?>

+ 296 - 0
core/filterdef.class.inc.php

@@ -0,0 +1,296 @@
+<?php
+
+
+require_once('MyHelpers.class.inc.php');
+
+
+/**
+ * Definition of a filter (could be made out of an existing attribute, or from an expression) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class FilterDefinition
+{
+	abstract public function GetType();
+	abstract public function GetTypeDesc();
+
+	protected $m_sCode;
+	private $m_aParams = array();
+	protected function Get($sParamName) {return $this->m_aParams[$sParamName];}
+	
+	public function __construct($sCode, $aParams = array())
+	{
+		$this->m_sCode = $sCode;
+		$this->m_aParams = $aParams;
+		$this->ConsistencyCheck();
+	}
+
+	public function OverloadParams($aParams)
+	{
+		foreach ($aParams as $sParam => $value)
+		{
+			if (!array_key_exists($sParam, $this->m_aParams))
+			{
+				throw new CoreException("Unknown attribute definition parameter '$sParam', please select a value in {".implode(", ", $this->m_aParams)."}");
+			}
+			else
+			{
+				$this->m_aParams[$sParam] = $value;
+			}
+		}
+	}
+
+	// to be overloaded
+	static protected function ListExpectedParams()
+	{
+		return array();
+	}
+
+	private function ConsistencyCheck()
+	{
+		// Check that any mandatory param has been specified
+		//
+		$aExpectedParams = $this->ListExpectedParams();
+		foreach($aExpectedParams as $sParamName)
+		{
+			if (!array_key_exists($sParamName, $this->m_aParams))
+			{
+				$aBacktrace = debug_backtrace();
+				$sTargetClass = $aBacktrace[2]["class"];
+				$sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"];
+				throw new CoreException("ERROR missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
+			}
+		}
+	} 
+
+	public function GetCode() {return $this->m_sCode;} 
+	abstract public function GetLabel(); 
+	abstract public function GetValuesDef(); 
+
+	// returns an array of opcode=>oplabel (e.g. "differs from")
+	abstract public function GetOperators();
+	// returns an opcode
+	abstract public function GetLooseOperator();
+	abstract public function GetFilterSQLExpr($sOpCode, $value);
+	abstract public function TemporaryGetSQLCol();
+
+	// Wrapper - no need for overloading this one
+	public function GetOpDescription($sOpCode)
+	{
+		$aOperators = $this->GetOperators();
+		if (!array_key_exists($sOpCode, $aOperators))
+		{
+			throw new CoreException("Unknown operator '$sOpCode'");
+		}
+		
+		return $aOperators[$sOpCode];
+	}
+}
+
+/**
+ * Match against the object unique identifier 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class FilterPrivateKey extends FilterDefinition
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("id_field"));
+	}
+
+	public function GetType() {return "PrivateKey";}
+	public function GetTypeDesc() {return "Match against object identifier";}
+
+	public function GetLabel()
+	{
+		return "Object Private Key";
+	} 
+
+	public function GetValuesDef()
+	{
+		return null;
+	}
+
+	public function GetOperators()
+	{
+		return array(
+			"="=>"equals",
+			"!="=>"differs from",
+			"IN"=>"in",
+			"NOTIN"=>"not in"
+		);
+	}
+	public function GetLooseOperator()
+	{
+		return "IN";
+	}
+
+	public function GetFilterSQLExpr($sOpCode, $value)
+	{
+		$sFieldName = $this->Get("id_field");
+		// #@# not obliged to quote... these are numbers !!!
+		$sQValue = CMDBSource::Quote($value);
+		switch($sOpCode)
+		{
+			case "IN":
+				if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
+				return "$sFieldName IN (".implode(", ", $sQValue).")"; 
+
+			case "NOTIN":
+				if (!is_array($sQValue)) throw new CoreException("Expected an array for argument value (sOpCode='$sOpCode')");
+				return "$sFieldName NOT IN (".implode(", ", $sQValue).")"; 
+
+			case "!=":
+				return $sFieldName." != ".$sQValue;
+
+			case "=":
+			default:
+				return $sFieldName." = ".$sQValue;
+		}
+	}
+	public function TemporaryGetSQLCol()
+	{
+		return $this->Get("id_field");
+	}
+}
+
+/**
+ * Match against an existing attribute (the attribute type will determine the available operators) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class FilterFromAttribute extends FilterDefinition
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("refattribute"));
+	}
+
+	public function __construct($oRefAttribute, $aParam = array())
+	{
+		// In this very specific case, the code is the one of the attribute
+		 // (this to get a very very simple syntax upon declaration)
+		$aParam["refattribute"] = $oRefAttribute;
+		parent::__construct($oRefAttribute->GetCode(), $aParam);
+	}
+
+	public function GetType() {return "Basic";}
+	public function GetTypeDesc() {return "Match against field contents";}
+
+	public function __GetRefAttribute() // for checking purposes only !!!
+	{
+		return $oAttDef = $this->Get("refattribute");
+	}
+
+	public function GetLabel()
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetLabel();
+	} 
+
+	public function GetValuesDef()
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetValuesDef();
+	} 
+
+	public function GetOperators()
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetBasicFilterOperators();
+	}
+	public function GetLooseOperator()
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetBasicFilterLooseOperator();
+	}
+
+	public function GetFilterSQLExpr($sOpCode, $value)
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetBasicFilterSQLExpr($sOpCode, $value);
+	}
+
+	public function TemporaryGetSQLCol()
+	{
+		$oAttDef = $this->Get("refattribute");
+		return $oAttDef->GetSQLExpr();
+	}
+}
+
+/**
+ * Match against a given column (experimental -to be cleaned up later) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class FilterDBValues extends FilterDefinition
+{
+	static protected function ListExpectedParams()
+	{
+		return array_merge(parent::ListExpectedParams(), array("dbfield"));
+	}
+
+	public function GetType() {return "Values from DB";}
+	public function GetTypeDesc() {return "Match against the existing values in a field";}
+
+	public function GetLabel()
+	{
+		return "enum de valeurs DB";
+	} 
+
+	public function GetValuesDef()
+	{
+		return null;
+	}
+
+	public function GetOperators()
+	{
+		return array(
+			"IN"=>"in",
+		);
+	}
+	public function GetLooseOperator()
+	{
+		return "IN";
+	}
+
+	public function GetFilterSQLExpr($sOpCode, $value)
+	{
+		$sFieldName = $this->Get("dbfield");
+		if (is_array($value) && !empty($value))
+		{
+			$sValueList = "'".implode("', '", $value)."'";
+			return "$sFieldName IN ($sValueList)";
+		}
+		return "1=1";
+	}
+
+	public function TemporaryGetSQLCol()
+	{
+		return $this->Get("dbfield");
+	}
+}
+
+?>

+ 2643 - 0
core/metamodel.class.php

@@ -0,0 +1,2643 @@
+<?php
+
+
+// #@# todo: change into class const (see Doctrine)
+// Doctrine example
+// class toto
+// {
+//    /**
+//     * VERSION
+//     */
+//    const VERSION                   = '1.0.0';
+// }
+
+/**
+ * add some description here... 
+ *
+ * @package     iTopORM
+ */
+define('ENUM_CHILD_CLASSES_EXCLUDETOP', 1);
+/**
+ * add some description here... 
+ *
+ * @package     iTopORM
+ */
+define('ENUM_CHILD_CLASSES_ALL', 2);
+
+/**
+ * Specifies that this attribute is hidden in that state 
+ *
+ * @package     iTopORM
+ */
+define('OPT_ATT_HIDDEN', 1);
+/**
+ * Specifies that this attribute is not editable in that state 
+ *
+ * @package     iTopORM
+ */
+define('OPT_ATT_READONLY', 2);
+/**
+ * Specifieds that the attribute must be set (different than default value?) when arriving into that state 
+ *
+ * @package     iTopORM
+ */
+define('OPT_ATT_MANDATORY', 4);
+/**
+ * Specifies that the attribute must change when arriving into that state 
+ *
+ * @package     iTopORM
+ */
+define('OPT_ATT_MUSTCHANGE', 8);
+/**
+ * Specifies that the attribute must be proposed when arriving into that state 
+ *
+ * @package     iTopORM
+ */
+define('OPT_ATT_MUSTPROMPT', 16);
+
+
+
+
+/**
+ * (API) The objects definitions as well as their mapping to the database 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class MetaModel
+{
+	///////////////////////////////////////////////////////////////////////////
+	//
+	// STATIC Members
+	//
+	///////////////////////////////////////////////////////////////////////////
+
+	// Purpose: workaround the following limitation = PHP5 does not allow to know the class (derived from the current one)
+	// from which a static function is called (__CLASS__ and self are interpreted during parsing)
+	private static function GetCallersPHPClass($sExpectedFunctionName = null)
+	{
+		//var_dump(debug_backtrace());
+		$aBacktrace = debug_backtrace();
+		// $aBacktrace[0] is where we are
+		// $aBacktrace[1] is the caller of GetCallersPHPClass
+		// $aBacktrace[1] is the info we want
+		if (!empty($sExpectedFunctionName))
+		{
+			assert('$aBacktrace[2]["function"] == $sExpectedFunctionName');
+		}
+		return $aBacktrace[2]["class"];
+	}
+
+	// Static init -why and how it works
+	//
+	// We found the following limitations:
+	//- it is not possible to define non scalar constants
+	//- it is not possible to declare a static variable as '= new myclass()'
+	// Then we had do propose this model, in which a derived (non abstract)
+	// class should implement Init(), to call InheritAttributes or AddAttribute.
+
+	private static function _check_subclass($sClass)
+	{
+		// See also IsValidClass()... ???? #@#
+		// class is mandatory
+		// (it is not possible to guess it when called as myderived::...)
+		if (!array_key_exists($sClass, self::$m_aClassParams))
+		{
+			throw new CoreException("Unknown class '$sClass', expected a value in {".implode(', ', array_keys(self::$m_aClassParams))."}");
+		}
+	}
+
+	public static function static_var_dump()
+	{
+		var_dump(get_class_vars(__CLASS__));
+	}
+
+	private static $m_bDebugQuery = false;
+	private static $m_iStackDepthRef = 0;
+
+	public static function StartDebugQuery()
+	{
+		$aBacktrace = debug_backtrace();
+		self::$m_iStackDepthRef = count($aBacktrace);
+		self::$m_bDebugQuery = true;
+	}
+	public static function StopDebugQuery()
+	{
+		self::$m_bDebugQuery = false;
+	}
+	public static function DbgTrace($value)
+	{
+		if (!self::$m_bDebugQuery) return;
+		$aBacktrace = debug_backtrace();
+		$iCallStackPos = count($aBacktrace) - self::$m_bDebugQuery;
+		$sIndent = ""; 
+		for ($i = 0 ; $i < $iCallStackPos ; $i++)
+		{
+			$sIndent .= " .-=^=-. ";
+		}
+		$aCallers = array();
+		foreach($aBacktrace as $aStackInfo)
+		{
+			$aCallers[] = $aStackInfo["function"];
+		}
+		$sCallers = "Callstack: ".implode(', ', $aCallers);
+		$sFunction = "<b title=\"$sCallers\">".$aBacktrace[1]["function"]."</b>";
+
+		if (is_string($value))
+		{
+			echo "$sIndent$sFunction: $value<br/>\n";
+		}
+		else if (is_object($value))
+		{
+			echo "$sIndent$sFunction:\n<pre>\n";
+			print_r($value);
+			echo "</pre>\n";
+		}
+		else
+		{
+			echo "$sIndent$sFunction: $value<br/>\n";
+		}
+	}
+
+	private static $m_sDBName = "";
+	private static $m_sTablePrefix = ""; // table prefix for the current application instance (allow several applications on the same DB)
+	private static $m_Category2Class = array();
+	private static $m_aRootClasses = array(); // array of "classname" => "rootclass"
+	private static $m_aParentClasses = array(); // array of ("classname" => array of "parentclass") 
+	private static $m_aChildClasses = array(); // array of ("classname" => array of "childclass")
+
+	private static $m_aClassParams = array(); // array of ("classname" => array of class information)
+
+	static public function GetParentPersistentClass($sRefClass)
+	{
+		$sClass = get_parent_class($sRefClass);
+		if (!$sClass) return '';
+
+		if ($sClass == 'DBObject') return ''; // Warning: __CLASS__ is lower case in my version of PHP
+
+		// Note: the UI/business model may implement pure PHP classes (intermediate layers)
+		if (array_key_exists($sClass, self::$m_aClassParams))
+		{
+			return $sClass;
+		}
+		return self::GetParentPersistentClass($sClass);
+	}
+
+	final static public function GetName($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["name"];
+	}
+	final static public function GetCategory($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["category"];
+	}
+	final static public function HasCategory($sClass, $sCategory)
+	{
+		self::_check_subclass($sClass);	
+		return (strpos(self::$m_aClassParams[$sClass]["category"], $sCategory) !== false); 
+	}
+	final static public function GetClassDescription($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["description"];
+	}
+	final static public function IsAutoIncrementKey($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return (self::$m_aClassParams[$sClass]["key_type"] == "autoincrement");
+	}
+	final static public function GetKeyLabel($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["key_label"];
+	}
+	final static public function GetNameAttributeCode($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["name_attcode"];
+	}
+	final static public function GetStateAttributeCode($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["state_attcode"];
+	}
+	final static public function GetDefaultState($sClass)
+	{
+		$sDefaultState = '';
+		$sStateAttrCode = self::GetStateAttributeCode($sClass);
+		if (!empty($sStateAttrCode))
+		{
+			$oStateAttrDef = self::GetAttributeDef($sClass, $sStateAttrCode);
+			$sDefaultState = $oStateAttrDef->GetDefaultValue();
+		}
+		return $sDefaultState;
+	}
+	final static public function GetReconcKeys($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["reconc_keys"];
+	}
+	final static public function GetDisplayTemplate($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["display_template"];
+	}
+	final static public function GetAttributeOrigin($sClass, $sAttCode)
+	{
+		self::_check_subclass($sClass);
+		return self::$m_aAttribOrigins[$sClass][$sAttCode];
+	}
+	final static public function GetPrequisiteAttributes($sClass, $sAttCode)
+	{
+		self::_check_subclass($sClass);
+		$oAtt = self::GetAttributeDef($sClass, $sAttCode);
+		// Temporary implementation: later, we might be able to compute
+		// the dependencies, based on the attributes definition
+		// (allowed values and default values) 
+		if ($oAtt->IsWritable())
+		{
+			return $oAtt->GetPrerequisiteAttributes();
+		}
+		else
+		{
+			return array();
+		}
+	}
+	// #@# restore to private ?
+	final static public function DBGetTable($sClass, $sAttCode = null)
+	{
+		self::_check_subclass($sClass);
+		if (empty($sAttCode) || ($sAttCode == "id"))
+		{
+			$sTableRaw = self::$m_aClassParams[$sClass]["db_table"];
+			if (empty($sTableRaw))
+			{
+				// return an empty string whenever the table is undefined, meaning that there is no table associated to this 'abstract' class
+				return '';
+			}
+			else
+			{
+				return self::$m_sTablePrefix.$sTableRaw;
+			}
+		}
+		// This attribute has been inherited (compound objects)
+		return self::DBGetTable(self::$m_aAttribOrigins[$sClass][$sAttCode]);
+	}
+	final static public function DBGetKey($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["db_key_field"];
+	}
+	final static public function DBGetClassField($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aClassParams[$sClass]["db_finalclass_field"];
+	}
+	final static public function HasFinalClassField($sClass)
+	{
+		self::_check_subclass($sClass);	
+		if (!array_key_exists("db_finalclass_field", self::$m_aClassParams[$sClass])) return false;
+		return (self::$m_aClassParams[$sClass]["db_finalclass_field"]);
+	}
+	final static public function IsStandaloneClass($sClass)
+	{
+		self::_check_subclass($sClass);
+
+		$sRootClass = self::GetRootClass($sClass);
+		return (!self::HasFinalClassField($sRootClass));
+	}
+	final static public function IsSameFamilyBranch($sClassA, $sClassB)
+	{
+		self::_check_subclass($sClassA);	
+		self::_check_subclass($sClassB);	
+		if (in_array($sClassA, self::$m_aParentClasses[$sClassB])) return true;
+		if (in_array($sClassB, self::$m_aParentClasses[$sClassA])) return true;
+		if ($sClassA == $sClassB) return true;
+		return false;
+	}
+	final static public function IsSameFamily($sClassA, $sClassB)
+	{
+		self::_check_subclass($sClassA);	
+		self::_check_subclass($sClassB);	
+		return (self::GetRootClass($sClassA) == self::GetRootClass($sClassB));
+	}
+
+	// Attributes of a given class may contain attributes defined in a parent class
+	// - Some attributes are a copy of the definition
+	// - Some attributes correspond to the upper class table definition (compound objects)
+	// (see also filters definition)
+	private static $m_aAttribDefs = array(); // array of ("classname" => array of attributes)
+	private static $m_aAttribOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass"))
+	private static $m_aExtKeyFriends = array(); // array of ("classname" => array of ("indirect ext key attcode"=> array of ("relative ext field")))
+	final static public function ListAttributeDefs($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aAttribDefs[$sClass];
+	}
+
+	final public static function GetAttributesList($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return array_keys(self::$m_aAttribDefs[$sClass]);
+	}
+
+	final public static function GetFiltersList($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return array_keys(self::$m_aFilterDefs[$sClass]);
+	}
+
+	final public static function GetKeysList($sClass)
+	{
+		self::_check_subclass($sClass);
+		$aExtKeys = array();
+		foreach(self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef)
+		{
+			if ($oAttDef->IsExternalKey())
+			{
+				$aExtKeys[] = $sAttCode;
+			}
+		}	
+		return $aExtKeys;
+	}
+	
+	final static public function IsValidKeyAttCode($sClass, $sAttCode)
+	{
+		if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false;
+		if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass])) return false;
+		return (self::$m_aAttribDefs[$sClass][$sAttCode]->IsExternalKey());
+	}
+	final static public function IsValidAttCode($sClass, $sAttCode)
+	{
+		if (!array_key_exists($sClass, self::$m_aAttribDefs)) return false;
+		return (array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]));
+	}
+	final static public function IsAttributeOrigin($sClass, $sAttCode)
+	{
+		return (self::$m_aAttribOrigins[$sClass][$sAttCode] == $sClass);
+	}
+
+	final static public function IsValidFilterCode($sClass, $sFilterCode)
+	{
+		if (!array_key_exists($sClass, self::$m_aFilterDefs)) return false;
+		return (array_key_exists($sFilterCode, self::$m_aFilterDefs[$sClass]));
+	}
+	public static function IsValidClass($sClass)
+	{
+		return (array_key_exists($sClass, self::$m_aAttribDefs));
+	}
+
+	public static function IsReconcKey($sClass, $sAttCode)
+	{
+		return (in_array($sAttCode, self::GetReconcKeys($sClass)));
+	}
+
+	final static public function GetAttributeDef($sClass, $sAttCode)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aAttribDefs[$sClass][$sAttCode];
+	}
+
+	final static public function GetExternalKeys($sClass)
+	{
+		$aExtKeys = array();
+		foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt)
+		{
+			if ($oAtt->IsExternalKey())
+			{
+				$aExtKeys[$sAttCode] = $oAtt;
+			}
+		}
+		return $aExtKeys;
+	}
+
+	final static public function GetExternalFields($sClass, $sKeyAttCode)
+	{
+		$aExtFields = array();
+		foreach (self::ListAttributeDefs($sClass) as $sAttCode => $oAtt)
+		{
+			if ($oAtt->IsExternalField() && ($oAtt->GetKeyAttCode() == $sKeyAttCode))
+			{
+				$aExtFields[] = $oAtt;
+			}
+		}
+		return $aExtFields;
+	}
+
+	final static public function GetExtKeyFriends($sClass, $sExtKeyAttCode)
+	{
+		if (array_key_exists($sExtKeyAttCode, self::$m_aExtKeyFriends[$sClass]))
+		{
+			return self::$m_aExtKeyFriends[$sClass][$sExtKeyAttCode];
+		}
+		else
+		{
+			return array();
+		}
+	}
+
+	public static function GetLabel($sClass, $sAttCode)
+	{
+		$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
+		if ($oAttDef) return $oAttDef->GetLabel();
+		return "";
+	}
+
+	public static function GetDescription($sClass, $sAttCode)
+	{
+		$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
+		if ($oAttDef) return $oAttDef->GetDescription();
+		return "";
+	}
+
+	// Filters of a given class may contain filters defined in a parent class
+	// - Some filters are a copy of the definition
+	// - Some filters correspond to the upper class table definition (compound objects)
+	// (see also attributes definition)
+	private static $m_aFilterDefs = array(); // array of ("classname" => array filterdef)
+	private static $m_aFilterOrigins = array(); // array of ("classname" => array of ("attcode"=>"sourceclass"))
+
+	public static function GetClassFilterDefs($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aFilterDefs[$sClass];
+	}
+
+	final static public function GetClassFilterDef($sClass, $sFilterCode)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aFilterDefs[$sClass][$sFilterCode];
+	}
+
+	public static function GetFilterLabel($sClass, $sFilterCode)
+	{
+		$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
+		if ($oFilter) return $oFilter->GetLabel();
+		return "";
+	}
+
+	public static function GetFilterDescription($sClass, $sFilterCode)
+	{
+		$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
+		if ($oFilter) return $oFilter->GetDescription();
+		return "";
+	}
+
+	// returns an array of opcode=>oplabel (e.g. "differs from")
+	public static function GetFilterOperators($sClass, $sFilterCode)
+	{
+		$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
+		if ($oFilter) return $oFilter->GetOperators();
+		return array();
+	}
+
+	// returns an opcode
+	public static function GetFilterLooseOperator($sClass, $sFilterCode)
+	{
+		$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
+		if ($oFilter) return $oFilter->GetLooseOperator();
+		return array();
+	}
+
+	public static function GetFilterOpDescription($sClass, $sFilterCode, $sOpCode)
+	{
+		$oFilter = self::GetClassFilterDef($sClass, $sFilterCode);
+		if ($oFilter) return $oFilter->GetOpDescription($sOpCode);
+		return "";
+	}
+
+	public static function GetFilterHTMLInput($sFilterCode)
+	{
+		return "<INPUT name=\"$sFilterCode\">";
+	}
+
+	// Lists of attributes/search filters
+	//
+	private static $m_aListInfos = array(); // array of ("listcode" => various info on the list, common to every classes)
+	private static $m_aListData = array(); // array of ("classname" => array of "listcode" => list)
+	// list may be an array of attcode / fltcode
+	// list may be an array of "groupname" => (array of attcode / fltcode) 
+
+	public static function EnumZLists()
+	{
+		return array_keys(self::$m_aListInfos);
+	}
+
+	final static public function GetZListInfo($sListCode)
+	{
+		return self::$m_aListInfos[$sListCode];
+	}
+
+	public static function GetZListItems($sClass, $sListCode)
+	{
+		if (array_key_exists($sClass, self::$m_aListData))
+		{
+			if (array_key_exists($sListCode, self::$m_aListData[$sClass]))
+			{
+				return self::$m_aListData[$sClass][$sListCode];
+			}
+		}
+		$sParentClass = self::GetParentPersistentClass($sClass);
+		if (empty($sParentClass)) return array(); // nothing for the mother of all classes
+		// Dig recursively
+		return self::GetZListItems($sParentClass, $sListCode);
+	}
+
+	public static function IsAttributeInZList($sClass, $sListCode, $sAttCodeOrFltCode, $sGroup = null)
+	{
+		$aZList = self::GetZListItems($sClass, $sListCode);
+		if (!$sGroup)
+		{
+			return (in_array($sAttCodeOrFltCode, $aZList));
+		}
+		return (in_array($sAttCodeOrFltCode, $aZList[$sGroup]));
+	}
+
+	//
+	// Relations
+	//
+	private static $m_aRelationInfos = array(); // array of ("relcode" => various info on the list, common to every classes)
+
+	public static function EnumRelations()
+	{
+		return array_keys(self::$m_aRelationInfos);
+	}
+
+	public static function EnumRelationProperties($sRelCode)
+	{
+		MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos);
+		return self::$m_aRelationInfos[$sRelCode];
+	}
+
+	final static public function GetRelationProperty($sRelCode, $sProperty)
+	{
+		MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos);
+		MyHelpers::CheckKeyInArray('relation property', $sProperty, self::$m_aRelationInfos[$sRelCode]);
+	
+		return self::$m_aRelationInfos[$sRelCode][$sProperty];
+	}
+
+	public static function EnumRelationQueries($sClass, $sRelCode)
+	{
+		MyHelpers::CheckKeyInArray('relation code', $sRelCode, self::$m_aRelationInfos);
+		return call_user_func_array(array($sClass, 'GetRelationQueries'), array($sRelCode));
+	}
+
+	//
+	// Object lifecycle model
+	//
+	private static $m_aStates = array(); // array of ("classname" => array of "statecode"=>array('label'=>..., 'description'=>..., attribute_inherit=> attribute_list=>...))
+	private static $m_aStimuli = array(); // array of ("classname" => array of ("stimuluscode"=>array('label'=>..., 'description'=>...)))
+	private static $m_aTransitions = array(); // array of ("classname" => array of ("statcode_from"=>array of ("stimuluscode" => array('target_state'=>..., 'actions'=>array of handlers procs, 'user_restriction'=>TBD)))
+
+	public static function EnumStates($sClass)
+	{
+		if (array_key_exists($sClass, self::$m_aStates))
+		{
+			return self::$m_aStates[$sClass];
+		}
+		else
+		{
+			return array();
+		}
+	}
+
+	public static function EnumStimuli($sClass)
+	{
+		if (array_key_exists($sClass, self::$m_aStimuli))
+		{
+			return self::$m_aStimuli[$sClass];
+		}
+		else
+		{
+			return array();
+		}
+	}
+
+	public static function EnumTransitions($sClass, $sStateCode)
+	{
+		if (array_key_exists($sClass, self::$m_aTransitions))
+		{
+			if (array_key_exists($sStateCode, self::$m_aTransitions[$sClass]))
+			{
+				return self::$m_aTransitions[$sClass][$sStateCode];
+			}
+		}
+		return array();
+	}
+	public static function GetAttributeFlags($sClass, $sState, $sAttCode)
+	{
+		$iFlags = 0; // By default (if no life cycle) no flag at all
+		$sStateAttCode = self::GetStateAttributeCode($sClass);
+		if (!empty($sStateAttCode))
+		{
+			$aStates = MetaModel::EnumStates($sClass);
+			$aCurrentState = $aStates[$sState];
+			if ( (array_key_exists('attribute_list', $aCurrentState)) && (array_key_exists($sAttCode, $aCurrentState['attribute_list'])) )
+			{
+				$iFlags = $aCurrentState['attribute_list'][$sAttCode];
+			}
+		}
+		return $iFlags;
+	}
+	
+	//
+	// Allowed values
+	//
+
+	public static function GetAllowedValues_att($sClass, $sAttCode, $aArgs = array(), $sBeginsWith = '')
+	{
+		$oAttDef = self::GetAttributeDef($sClass, $sAttCode);
+		if (!$oAttDef) return null;
+		$oValSetDef = $oAttDef->GetValuesDef();
+		if (!$oValSetDef) return null;
+		return $oValSetDef->GetValues($aArgs, $sBeginsWith);
+	}
+
+	public static function GetAllowedValues_flt($sClass, $sFltCode, $aArgs = array(), $sBeginsWith = '')
+	{
+		$oFltDef = self::GetClassFilterDef($sClass, $sFltCode);
+		if (!$oFltDef) return null;
+		$oValSetDef = $oFltDef->GetValuesDef();
+		if (!$oValSetDef) return null;
+		return $oValSetDef->GetValues($aArgs, $sBeginsWith);
+	}
+
+	//
+	// Businezz model declaration verbs (should be static)
+	//
+
+	public static function RegisterZList($sListCode, $aListInfo)
+	{
+		// Check mandatory params
+		$aMandatParams = array(
+			"description" => "detailed (though one line) description of the list",
+			"type" => "attributes | filters",
+		);		
+		foreach($aMandatParams as $sParamName=>$sParamDesc)
+		{
+			if (!array_key_exists($sParamName, $aListInfo))
+			{
+				throw new CoreException("Declaration of list $sListCode - missing parameter $sParamName");
+			}
+		}
+		
+		self::$m_aListInfos[$sListCode] = $aListInfo;
+	}
+
+	public static function RegisterRelation($sRelCode, $aRelationInfo)
+	{
+		// Check mandatory params
+		$aMandatParams = array(
+			"description" => "detailed (though one line) description of the list",
+			"verb_down" => "e.g.: 'impacts'",
+			"verb_up" => "e.g.: 'is impacted by'",
+		);		
+		foreach($aMandatParams as $sParamName=>$sParamDesc)
+		{
+			if (!array_key_exists($sParamName, $aRelationInfo))
+			{
+				throw new CoreException("Declaration of relation $sRelCode - missing parameter $sParamName");
+			}
+		}
+		
+		self::$m_aRelationInfos[$sRelCode] = $aRelationInfo;
+	}
+
+	// Must be called once and only once...
+	public static function InitClasses($sTablePrefix)
+	{
+		if (count(self::GetClasses()) > 0)
+		{
+			throw new CoreException("InitClasses should not be called more than once -skipped");
+			return;
+		}
+
+		self::$m_sTablePrefix = $sTablePrefix;
+
+		foreach(get_declared_classes() as $sPHPClass) {
+			if (is_subclass_of($sPHPClass, 'DBObject'))
+			{
+				if (method_exists($sPHPClass, 'Init'))
+				{
+					call_user_func(array($sPHPClass, 'Init'));
+				}
+			}
+		}
+		foreach (self::GetClasses() as $sClass)
+		{
+			// Compute the fields that will be used to display a pointer to another object
+			//
+			self::$m_aExtKeyFriends[$sClass] = array();
+			foreach (self::$m_aAttribDefs[$sClass] as $sAttCode => $oAttDef)
+			{
+				if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
+				{
+					// oAttDef is either
+					// - an external KEY / FIELD (direct),
+					// - an external field pointing to an external KEY / FIELD
+					// - an external field pointing to an external field pointing to....
+
+					// Get the real external key attribute
+					// It will be our reference to determine the other ext fields related to the same ext key
+					$oFinalKeyAttDef = $oAttDef->GetKeyAttDef(EXTKEY_ABSOLUTE);
+
+					self::$m_aExtKeyFriends[$sClass][$sAttCode] = array();
+					foreach (self::GetExternalFields($sClass, $oAttDef->GetKeyAttCode($sAttCode)) as $oExtField)
+					{
+						// skip : those extfields will be processed as external keys
+						if ($oExtField->IsExternalKey(EXTKEY_ABSOLUTE)) continue;
+
+						// Note: I could not compare the objects by the mean of '==='
+						// because they are copied for the inheritance, and the internal references are NOT updated
+						if ($oExtField->GetKeyAttDef(EXTKEY_ABSOLUTE) == $oFinalKeyAttDef)
+						{
+							self::$m_aExtKeyFriends[$sClass][$sAttCode][$oExtField->GetCode()] = $oExtField;
+						}
+					}
+				}
+			}
+
+			// Add a 'id' filter
+			//
+			if (array_key_exists('id', self::$m_aAttribDefs[$sClass]))
+			{
+				throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as an attribute code");
+			}
+			if (array_key_exists('id', self::$m_aFilterDefs[$sClass]))
+			{
+				throw new CoreException("Class $sClass, 'id' is a reserved keyword, it cannot be used as a filter code");
+			}
+			$oFilter = new FilterPrivateKey('id', array('id_field' => self::DBGetKey($sClass)));
+			self::$m_aFilterDefs[$sClass]['id'] = $oFilter;
+			self::$m_aFilterOrigins[$sClass]['id'] = $sClass;
+
+			// Add a 'class' attribute/filter to the root classes and their children
+			//
+			if (!self::IsStandaloneClass($sClass))
+			{
+				if (array_key_exists('finalclass', self::$m_aAttribDefs[$sClass]))
+				{
+					throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as an attribute code");
+				}
+				if (array_key_exists('finalclass', self::$m_aFilterDefs[$sClass]))
+				{
+					throw new CoreException("Class $sClass, 'finalclass' is a reserved keyword, it cannot be used as a filter code");
+				}
+				$sClassAttCode = 'finalclass';
+				$sRootClass = self::GetRootClass($sClass);
+				$sDbFinalClassField = self::DBGetClassField($sRootClass);
+				$oClassAtt = new AttributeString($sClassAttCode, array(
+						"label"=>"Class",
+						"description"=>"Real (final) object class",
+						"allowed_values"=>null,
+						"sql"=>$sDbFinalClassField,
+						"default_value"=>$sClass,
+						"is_null_allowed"=>false,
+						"depends_on"=>array()
+				));
+				self::$m_aAttribDefs[$sClass][$sClassAttCode] = $oClassAtt;
+				self::$m_aAttribOrigins[$sClass][$sClassAttCode] = $sRootClass;
+
+				$oClassFlt = new FilterFromAttribute($oClassAtt);
+				self::$m_aFilterDefs[$sClass][$sClassAttCode] = $oClassFlt;
+				self::$m_aFilterOrigins[$sClass][$sClassAttCode] = self::GetRootClass($sClass);
+			}
+		}
+
+	}
+
+	// To be overriden, must be called for any object class (optimization)
+	public static function Init()
+	{
+		// In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation)
+	}
+	// To be overloaded by biz model declarations
+	public static function GetRelationQueries($sRelCode)
+	{
+		// In fact it is an ABSTRACT function, but this is not compatible with the fact that it is STATIC (error in E_STRICT interpretation)
+		return array();
+	}
+
+	public static function Init_Params($aParams)
+	{
+		// Check mandatory params
+		$aMandatParams = array(
+			"category" => "group classes by modules defining their visibility in the UI",
+			"name" => "internal class name, may be different than the PHP class name",
+			"description" => "detailed (though one line) description of the class",
+			"key_type" => "autoincrement | string",
+			"key_label" => "if set, then display the key as an attribute",
+			"name_attcode" => "define wich attribute is the class name, may be an inherited attribute",
+			"state_attcode" => "define wich attribute is representing the state (object lifecycle)",
+			"reconc_keys" => "define the attributes that will 'almost uniquely' identify an object in batch processes",
+			"db_table" => "database table",
+			"db_key_field" => "database field which is the key",
+			"db_finalclass_field" => "database field wich is the reference to the actual class of the object, considering that this will be a compound class",
+		);		
+
+		$sClass = self::GetCallersPHPClass("Init");
+		if (!array_key_exists("name", $aParams))
+		{
+			throw new CoreException("Declaration of class $sClass: missing name ({$aMandatParams["name"]})");
+		}
+
+		foreach($aMandatParams as $sParamName=>$sParamDesc)
+		{
+			if (!array_key_exists($sParamName, $aParams))
+			{
+				throw new CoreException("Declaration of class $sClass - missing parameter $sParamName");
+			}
+		}
+		
+		$aCategories = explode(',', $aParams['category']);
+		foreach ($aCategories as $sCategory)
+		{
+			self::$m_Category2Class[$sCategory][] = $sClass;
+		}
+		self::$m_Category2Class[''][] = $sClass; // all categories, include this one
+		
+
+		self::$m_aRootClasses[$sClass] = $sClass; // first, let consider that I am the root... updated on inheritance
+		self::$m_aParentClasses[$sClass] = array();
+		self::$m_aChildClasses[$sClass] = array();
+
+		self::$m_aClassParams[$sClass]= $aParams;
+
+		self::$m_aAttribDefs[$sClass] = array();
+		self::$m_aAttribOrigins[$sClass] = array();
+		self::$m_aExtKeyFriends[$sClass] = array();
+		self::$m_aFilterDefs[$sClass] = array();
+		self::$m_aFilterOrigins[$sClass] = array();
+	}
+
+	protected static function object_array_mergeclone($aSource1, $aSource2)
+	{
+		$aRes = array();
+		foreach ($aSource1 as $key=>$object)
+		{
+			$aRes[$key] = clone $object;
+		}
+		foreach ($aSource2 as $key=>$object)
+		{
+			$aRes[$key] = clone $object;
+		}
+		return $aRes;
+	}
+
+	public static function Init_InheritAttributes($sSourceClass = null)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (empty($sSourceClass))
+		{
+			// Default: inherit from parent class
+			$sSourceClass = self::GetParentPersistentClass($sTargetClass);
+			if (empty($sSourceClass)) return; // no attributes for the mother of all classes
+		}
+		if (isset(self::$m_aAttribDefs[$sSourceClass]))
+		{
+			if (!isset(self::$m_aAttribDefs[$sTargetClass]))
+			{
+				self::$m_aAttribDefs[$sTargetClass] = array();
+				self::$m_aAttribOrigins[$sTargetClass] = array();
+			}
+			self::$m_aAttribDefs[$sTargetClass] = self::object_array_mergeclone(self::$m_aAttribDefs[$sTargetClass], self::$m_aAttribDefs[$sSourceClass]);
+			self::$m_aAttribOrigins[$sTargetClass] = array_merge(self::$m_aAttribOrigins[$sTargetClass], self::$m_aAttribOrigins[$sSourceClass]);
+		}
+		// later on, we might consider inheritance in different ways !!!
+		//if (strlen(self::DBGetTable($sSourceClass)) != 0)
+		if (self::HasFinalClassField(self::$m_aRootClasses[$sSourceClass]))
+		{
+			// Inherit the root class
+			self::$m_aRootClasses[$sTargetClass] = self::$m_aRootClasses[$sSourceClass];
+		}
+		else
+		{
+			// I am a root class, standalone as well !
+			// ????
+			//self::$m_aRootClasses[$sTargetClass] = $sTargetClass;
+		}
+		self::$m_aParentClasses[$sTargetClass] += self::$m_aParentClasses[$sSourceClass];
+		self::$m_aParentClasses[$sTargetClass][] = $sSourceClass;
+		// I am the child of each and every parent...
+		foreach(self::$m_aParentClasses[$sTargetClass] as $sAncestorClass)
+		{
+			self::$m_aChildClasses[$sAncestorClass][] = $sTargetClass;
+		}
+	}
+	public static function Init_OverloadAttributeParams($sAttCode, $aParams)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		
+		if (!self::IsValidAttCode($sTargetClass, $sAttCode))
+		{
+			throw new CoreException("Could not overload '$sAttCode', expecting a code from {".implode(", ", self::GetAttributesList($sTargetClass))."}");
+		}
+		self::$m_aAttribDefs[$sTargetClass][$sAttCode]->OverloadParams($aParams);
+	}
+	public static function Init_AddAttribute(AttributeDefinition $oAtt)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		self::$m_aAttribDefs[$sTargetClass][$oAtt->GetCode()] = $oAtt;
+		self::$m_aAttribOrigins[$sTargetClass][$oAtt->GetCode()] = $sTargetClass;
+		// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
+		
+		// Specific case of external fields:
+		// I wanted to simplify the syntax of the declaration of objects in the biz model
+		// Therefore, the reference to the host class is set there 
+		$oAtt->SetHostClass($sTargetClass);
+	}
+
+	public static function Init_InheritFilters($sSourceClass = null)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (empty($sSourceClass))
+		{
+			// Default: inherit from parent class
+			$sSourceClass = self::GetParentPersistentClass($sTargetClass);
+			if (empty($sSourceClass)) return; // no filters for the mother of all classes
+		}
+		if (isset(self::$m_aFilterDefs[$sSourceClass]))
+		{
+			if (!isset(self::$m_aFilterDefs[$sTargetClass]))
+			{
+				self::$m_aFilterDefs[$sTargetClass] = array();
+				self::$m_aFilterOrigins[$sTargetClass] = array();
+			}
+
+			foreach (self::$m_aFilterDefs[$sSourceClass] as $sFltCode=>$oFilter)
+			{
+				if ($oFilter instanceof FilterFromAttribute)
+				{
+					// In that case, cloning is not enough:
+					//  we must ensure that we will point to the correct
+					//  attribute definition (in case some properties are overloaded)
+					$oAttDef1 = $oFilter->__GetRefAttribute();
+					$oAttDef2 = self::GetAttributeDef($sTargetClass, $oAttDef1->GetCode());
+					$oNewFilter = new FilterFromAttribute($oAttDef2);
+				}
+				else
+				{
+					$oNewFilter = clone $oFilter;
+				}
+				self::$m_aFilterDefs[$sTargetClass][$sFltCode] = $oNewFilter;
+			}
+
+			self::$m_aFilterOrigins[$sTargetClass] = array_merge(self::$m_aFilterOrigins[$sTargetClass], self::$m_aFilterOrigins[$sSourceClass]);
+		}
+	}
+
+	public static function Init_OverloadFilterParams($sFltCode, $aParams)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		
+		if (!self::IsValidFilterCode($sTargetClass, $sFltCode))
+		{
+			throw new CoreException("Could not overload '$sFltCode', expecting a code from {".implode(", ", self::GetFiltersList($sTargetClass))."}");
+		}
+		self::$m_aFilterDefs[$sTargetClass][$sFltCode]->OverloadParams($aParams);
+	}
+
+	public static function Init_AddFilter(FilterDefinition $oFilter)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		self::$m_aFilterDefs[$sTargetClass][$oFilter->GetCode()] = $oFilter;
+		self::$m_aFilterOrigins[$sTargetClass][$oFilter->GetCode()] = $sTargetClass;
+		// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
+	}
+	public static function Init_AddFilterFromAttribute($sAttCode)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		
+		$oAttDef = self::GetAttributeDef($sTargetClass, $sAttCode);
+
+		$sFilterCode = $sAttCode;
+		$oNewFilter = new FilterFromAttribute($oAttDef);
+		self::$m_aFilterDefs[$sTargetClass][$sFilterCode] = $oNewFilter;
+
+		if ($oAttDef->IsExternalField())
+		{
+			$sKeyAttCode = $oAttDef->GetKeyAttCode();
+			$oKeyDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode);
+			self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $oKeyDef->GetTargetClass();
+		}
+		else
+		{
+			self::$m_aFilterOrigins[$sTargetClass][$sFilterCode] = $sTargetClass;
+		}
+		// Note: it looks redundant to put targetclass there, but a mix occurs when inheritance is used
+	}
+
+	public static function Init_SetZListItems($sListCode, $aItems)
+	{
+		MyHelpers::CheckKeyInArray('list code', $sListCode, self::$m_aListInfos);
+
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		self::$m_aListData[$sTargetClass][$sListCode] = $aItems;
+	}
+
+	public static function Init_DefineState($sStateCode, $aStateDef)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (is_null($aStateDef['attribute_list'])) $aStateDef['attribute_list'] = array(); 
+
+		$sParentState = $aStateDef['attribute_inherit'];
+		if (!empty($sParentState))
+		{
+			// Inherit from the given state (must be defined !)
+			$aToInherit = self::$m_aStates[$sTargetClass][$sParentState];
+			// The inherited configuration could be overriden
+			$aStateDef['attribute_list'] = array_merge($aToInherit, $aStateDef['attribute_list']);
+		}
+		self::$m_aStates[$sTargetClass][$sStateCode] = $aStateDef;
+
+		// by default, create an empty set of transitions associated to that state
+		self::$m_aTransitions[$sTargetClass][$sStateCode] = array();
+	}
+
+	public static function Init_DefineStimulus($sStimulusCode, $oStimulus)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		self::$m_aStimuli[$sTargetClass][$sStimulusCode] = $oStimulus;
+	}
+
+	public static function Init_DefineTransition($sStateCode, $sStimulusCode, $aTransitionDef)
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (is_null($aTransitionDef['actions'])) $aTransitionDef['actions'] = array(); 
+		self::$m_aTransitions[$sTargetClass][$sStateCode][$sStimulusCode] = $aTransitionDef;
+	}
+
+	public static function Init_InheritLifecycle($sSourceClass = '')
+	{
+		$sTargetClass = self::GetCallersPHPClass("Init");
+		if (empty($sSourceClass))
+		{
+			// Default: inherit from parent class
+			$sSourceClass = self::GetParentPersistentClass($sTargetClass);
+			if (empty($sSourceClass)) return; // no attributes for the mother of all classes
+		}
+
+		self::$m_aClassParams[$sTargetClass]["state_attcode"] = self::$m_aClassParams[$sSourceClass]["state_attcode"];
+		self::$m_aStates[$sTargetClass] = clone self::$m_aStates[$sSourceClass];
+		self::$m_aStimuli[$sTargetClass] = clone self::$m_aStimuli[$sSourceClass];
+		self::$m_aTransitions[$sTargetClass] = clone self::$m_aTransitions[$sSourceClass];
+	}
+
+	//
+	// Static API
+	//
+
+	public static function GetRootClass($sClass = null)
+	{
+		self::_check_subclass($sClass);
+		return self::$m_aRootClasses[$sClass];
+	}
+	public static function IsRootClass($sClass)
+	{
+		self::_check_subclass($sClass);
+		return (self::GetRootClass($sClass) == $sClass);
+	}
+	public static function EnumParentClasses($sClass)
+	{
+		self::_check_subclass($sClass);	
+		return self::$m_aParentClasses[$sClass];
+	}
+	public static function EnumChildClasses($sClass, $iOption = ENUM_CHILD_CLASSES_EXCLUDETOP)
+	{
+		self::_check_subclass($sClass);
+
+		$aRes = self::$m_aChildClasses[$sClass];
+		if ($iOption != ENUM_CHILD_CLASSES_EXCLUDETOP)
+		{
+			// Add it to the list
+			$aRes[] = $sClass;
+		}
+		return $aRes;
+	}
+
+	public static function EnumCategories()
+	{
+		return array_keys(self::$m_Category2Class);
+	}
+
+	// Note: use EnumChildClasses to take the compound objects into account
+	public static function GetSubclasses($sClass)
+	{
+		self::_check_subclass($sClass);	
+		$aSubClasses = array();
+		foreach(get_declared_classes() as $sSubClass) {
+			if (is_subclass_of($sSubClass, $sClass))
+			{
+				$aSubClasses[] = $sSubClass;
+			}
+		}
+		return $aSubClasses;
+	}
+	public static function GetClasses($sCategory = '')
+	{
+		if (array_key_exists($sCategory, self::$m_Category2Class))
+		{
+			return self::$m_Category2Class[$sCategory];
+		}
+
+		if (count(self::$m_Category2Class) > 0)
+		{
+			throw new CoreException("unkown class category '$sCategory', expecting a value in {".implode(', ', array_keys(self::$m_Category2Class))."}");
+		}
+		return array();
+	}
+
+	public static function IsAbstract($sClass)
+	{
+		if (strlen(self::DBGetTable($sClass)) == 0) return true;
+		return false;
+	}
+
+	public static function MakeSelectQuery(DBObjectSearch $oFilter, $aOrderBy = array())
+	{
+		$aTranslation = array();
+		$aClassAliases = array();
+		$aTableAliases = array();
+		$oConditionTree = $oFilter->GetCriteria();
+		$oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter);
+
+		// Check the order by specification
+		foreach ($aOrderBy as $sFieldAlias => $bAscending)
+		{
+			MyHelpers::CheckValueInArray('field name in ORDER BY spec', $sFieldAlias, self::GetAttributesList($oFilter->GetClass()));
+			if (!is_bool($bAscending))
+			{
+				throw new CoreException("Wrong direction in ORDER BY spec, found '$bAscending' and expecting a boolean value");
+			}
+		}
+		if (empty($aOrderBy))
+		{
+			$sNameAttCode = self::GetNameAttributeCode($oFilter->GetClass());
+			if (!empty($sNameAttCode))
+			{
+				// By default, simply order on the "name" attribute, ascending
+				$aOrderBy = array($sNameAttCode => true);
+			}
+		}
+		
+		//MyHelpers::var_dump_html($oSelect->RenderSelect($aOrderBy));
+		return $oSelect->RenderSelect($aOrderBy);
+	}
+
+	public static function MakeDeleteQuery(DBObjectSearch $oFilter)
+	{
+		$aTranslation = array();
+		$aClassAliases = array();
+		$aTableAliases = array();
+		$oConditionTree = $oFilter->GetCriteria();
+		$oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter);
+		return $oSelect->RenderDelete();
+	}
+
+	public static function MakeUpdateQuery(DBObjectSearch $oFilter, $aValues)
+	{
+		// $aValues is an array of $sAttCode => $value
+		$aTranslation = array();
+		$aClassAliases = array();
+		$aTableAliases = array();
+		$oConditionTree = $oFilter->GetCriteria();
+		$oSelect = self::MakeQuery($oFilter->GetClassAlias(), $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, array(), $aValues);
+		return $oSelect->RenderUpdate();
+	}
+
+	private static function MakeQuery($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, DBObjectSearch $oFilter, $aExpectedAtts = array(), $aValues = array())
+	{
+		// Note: query class might be different than the class of the filter
+		// -> this occurs when we are linking our class to an external class (referenced by, or pointing to)
+		// $aExpectedAtts is an array of sAttCode=>sAlias
+		$sClass = $oFilter->GetClass();
+		$sClassAlias = $oFilter->GetClassAlias();
+
+		$bIsOnQueriedClass = ($sClassAlias == $sGlobalTargetAlias);
+		if ($bIsOnQueriedClass)
+		{
+			$aClassAliases = array_merge($aClassAliases, $oFilter->GetClasses());
+		}
+
+		self::DbgTrace("Entering: ".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts));
+
+		$sRootClass = self::GetRootClass($sClass);
+		$sKeyField = self::DBGetKey($sClass);
+
+		if (empty($aExpectedAtts) && $bIsOnQueriedClass)
+		{
+			// default to the whole list of attributes + the very std id/finalclass
+			$aExpectedAtts['id'] = 'id';
+			foreach (self::GetAttributesList($sClass) as $sAttCode)
+			{
+				$aExpectedAtts[$sAttCode] = $sAttCode; // alias == attcode 
+			}
+		}
+
+		// Compute a clear view of external keys, and external attributes
+		// Build the list of external keys:
+		// -> ext keys required by a closed join ???
+		// -> ext keys mentionned in a 'pointing to' condition
+		// -> ext keys required for an external field
+		//
+		$aExtKeys = array(); // array of sTableClass => array of (sAttCode (keys) => array of (sAttCode (fields)=> oAttDef))
+		//
+		// Optimization: could be computed once for all (cached)
+		// Could be done in MakeQuerySingleTable ???
+		//  
+
+		if ($bIsOnQueriedClass)
+		{
+			// Get all Ext keys for the queried class (??)
+			foreach(self::GetKeysList($sClass) as $sKeyAttCode)
+			{
+				$sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode];
+				$aExtKeys[$sKeyTableClass][$sKeyAttCode] = array();
+			}
+		}
+		// Get all Ext keys used by the filter
+		foreach ($oFilter->GetCriteria_PointingTo() as $sKeyAttCode => $trash)
+		{
+			$sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode];
+			$aExtKeys[$sKeyTableClass][$sKeyAttCode] = array();
+		}
+		// Add the ext fields used in the select (eventually adds an external key)
+		foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
+		{
+			if ($oAttDef->IsExternalField())
+			{
+				$sKeyAttCode = $oAttDef->GetKeyAttCode();
+				if (array_key_exists($sAttCode, $aExpectedAtts) || $oConditionTree->RequiresField($sClassAlias, $sAttCode))
+				{
+					// Add the external attribute
+					$sKeyTableClass = self::$m_aAttribOrigins[$sClass][$sKeyAttCode];
+					$aExtKeys[$sKeyTableClass][$sKeyAttCode][$sAttCode] = $oAttDef;
+				}
+			}
+		}
+
+		// First query built upon on the leaf (ie current) class
+		//
+		self::DbgTrace("Main (=leaf) class, call MakeQuerySingleTable()");
+		$oSelectBase = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sClass, $aExpectedAtts, $aExtKeys, $aValues);
+
+		// Then we join the queries of the eventual parent classes (compound model)
+		foreach(self::EnumParentClasses($sClass) as $sParentClass)
+		{
+			if (self::DBGetTable($sParentClass) == "") continue;
+			self::DbgTrace("Parent class: $sParentClass... let's call MakeQuerySingleTable()");
+			$oSelectParentTable = self::MakeQuerySingleTable($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oFilter, $sParentClass, $aExpectedAtts, $aExtKeys, $aValues);
+			$oSelectBase->AddInnerJoin($oSelectParentTable, $sKeyField, self::DBGetKey($sParentClass));
+		}
+
+		// Filter on objects referencing me
+		foreach ($oFilter->GetCriteria_ReferencedBy() as $sForeignClass => $aKeysAndFilters)
+		{
+			foreach ($aKeysAndFilters as $sForeignKeyAttCode => $oForeignFilter)
+			{
+				$oForeignKeyAttDef = self::GetAttributeDef($sForeignClass, $sForeignKeyAttCode);
+	
+				// We don't want any attribute from the foreign class, just filter on an inner join
+				$aExpAtts = array();
+	
+				self::DbgTrace("Referenced by foreign key: $sForeignKeyAttCode... let's call MakeQuery()");
+				//self::DbgTrace($oForeignFilter);
+				//self::DbgTrace($oForeignFilter->ToSibuSQL());
+				//self::DbgTrace($oSelectForeign);
+				//self::DbgTrace($oSelectForeign->RenderSelect(array()));
+				$oSelectForeign = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oForeignFilter, $aExpAtts);
+
+				$sForeignClassAlias = $oForeignFilter->GetClassAlias();
+				$sForeignKeyTable = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][0];
+				$sForeignKeyColumn = $aTranslation[$sForeignClassAlias][$sForeignKeyAttCode][1];
+				$oSelectBase->AddInnerJoin($oSelectForeign, $sKeyField, $sForeignKeyColumn, $sForeignKeyTable);
+			}
+		}
+
+		// Filter on related objects
+		//
+		foreach ($oFilter->GetCriteria_RelatedTo() as $aCritInfo)
+		{
+			$oSubFilter = $aCritInfo['flt'];
+			$sRelCode = $aCritInfo['relcode'];
+			$iMaxDepth = $aCritInfo['maxdepth'];
+
+			// Get the starting point objects
+			$oStartSet = new CMDBObjectSet($oSubFilter);
+
+			// Get the objects related to those objects... recursively...
+			$aRelatedObjs = $oStartSet->GetRelatedObjects($sRelCode, $iMaxDepth);
+			$aRestriction = array_key_exists($sRootClass, $aRelatedObjs) ? $aRelatedObjs[$sRootClass] : array();
+
+			// #@# todo - related objects and expressions...
+			// Create condition
+			if (count($aRestriction) > 0)
+			{
+				$oSelectBase->AddCondition($sKeyField.' IN ('.implode(', ', CMDBSource::Quote(array_keys($aRestriction), true)).')');
+			}
+			else
+			{
+				// Quick N'dirty -> generate an empty set
+				$oSelectBase->AddCondition('false');
+			}
+		}
+
+		// Translate the conditions... and go
+		//
+		if ($bIsOnQueriedClass)
+		{
+			$oConditionTranslated = $oConditionTree->Translate($aTranslation);
+			$oSelectBase->SetCondition($oConditionTranslated);
+		}
+
+		// That's all... cross fingers and we'll get some working query
+
+		//MyHelpers::var_dump_html($oSelectBase, true);
+		//MyHelpers::var_dump_html($oSelectBase->RenderSelect(), true);
+		if (self::$m_bDebugQuery) $oSelectBase->DisplayHtml();
+		return $oSelectBase;
+	}
+
+	protected static function MakeQuerySingleTable($sGlobalTargetAlias, &$oConditionTree, &$aClassAliases, &$aTableAliases, &$aTranslation, $oFilter, $sTableClass, $aExpectedAtts, $aExtKeys, $aValues)
+	{
+		// $aExpectedAtts is an array of sAttCode=>sAlias
+		// $aExtKeys is an array of sTableClass => array of (sAttCode (keys) => array of sAttCode (fields))
+
+		// Prepare the query for a single table (compound objects)
+		// Ignores the items (attributes/filters) that are not on the target table
+		// Perform an (inner or left) join for every external key (and specify the expected fields)
+		//
+		// Returns an SQLQuery
+		//
+		$sTargetClass = $oFilter->GetClass();
+		$sTargetAlias = $oFilter->GetClassAlias();
+		$sTable = self::DBGetTable($sTableClass);
+		$sTableAlias = self::GenerateUniqueAlias($aTableAliases, $sTargetAlias.'_'.$sTable, $sTable);
+
+		$bIsOnQueriedClass = ($sTargetAlias == $sGlobalTargetAlias);
+		
+		self::DbgTrace("Entering: tableclass=$sTableClass, filter=".$oFilter->ToSibuSQL().", ".($bIsOnQueriedClass ? "MAIN" : "SECONDARY").", expectedatts=".count($aExpectedAtts).": ".implode(",", $aExpectedAtts));
+
+		// 1 - SELECT and UPDATE
+		//
+		// Note: no need for any values nor fields for foreign Classes (ie not the queried Class)
+		//
+		$aSelect = array();
+		$aUpdateValues = array();
+
+		// 1/a - Get the key
+		//
+		if ($bIsOnQueriedClass)
+		{
+			$aSelect[$aExpectedAtts['id']] = new FieldExpression(self::DBGetKey($sTableClass), $sTableAlias);
+		}
+		// We need one pkey to be the key, let's take the one corresponding to the leaf
+		if ($sTableClass == $sTargetClass)
+		{
+			$aTranslation[$sTargetAlias]['id'] = array($sTableAlias, self::DBGetKey($sTableClass));
+		}
+	
+		// 1/b - Get the other attributes
+		// 
+		foreach(self::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef)
+		{
+			// Skip this attribute if not defined in this table
+			if (self::$m_aAttribOrigins[$sTargetClass][$sAttCode] != $sTableClass) continue;
+
+			// Skip this attribute if not writable (means that it does not correspond 
+			if (count($oAttDef->DBGetUsedFields()) == 0) continue;
+
+			// Update...
+			//
+			if ($bIsOnQueriedClass && array_key_exists($sAttCode, $aValues))
+			{
+				assert ($oAttDef->IsDirectField());
+				// Later, we'll have to use $oAttDef->GetDBField();
+				$aUpdateValues[$oAttDef->GetSQLExpr()] = $oAttDef->RealValueToSQLValue($aValues[$sAttCode]);
+			}
+
+			// Select...
+			//
+			// Skip, if a list of fields has been specified and it is not there
+			if (!array_key_exists($sAttCode, $aExpectedAtts)) continue;
+			$sAttAlias = $aExpectedAtts[$sAttCode];
+
+			if ($oAttDef->IsExternalField())
+			{
+				// skip, this will be handled in the joined tables
+			}
+			else
+			{
+				// standard field, or external key
+				// add it to the output
+				$aSelect[$sAttAlias] = new FieldExpression($oAttDef->GetSQLExpr(), $sTableAlias);
+			}
+		}
+
+		// 2 - WHERE
+		//
+		foreach(self::$m_aFilterDefs[$sTargetClass] as $sFltCode => $oFltAtt)
+		{
+			// Skip this filter if not defined in this table
+			if (self::$m_aFilterOrigins[$sTargetClass][$sFltCode] != $sTableClass) continue;
+
+			// #@# todo - aller plus loin... a savoir que la table de translation doit contenir une "Expression"
+			// non-sens: $aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->GetFilterSQLExpr(opcode, operand));
+			$aTranslation[$sTargetAlias][$sFltCode] = array($sTableAlias, $oFltAtt->TemporaryGetSQLCol());
+		}
+
+		// #@# todo - See what a full text search condition should be
+		// 2' - WHERE / Full text search condition
+		//
+		if ($bIsOnQueriedClass)
+		{
+			$aFullText = $oFilter->GetCriteria_FullText();
+		}
+		else
+		{
+			// Pourquoi ???
+			$aFullText = array();
+		}
+
+		// 3 - The whole stuff, for this table only
+		//
+		$oSelectBase = new SQLQuery($sTable, $sTableAlias, $aSelect, null, $aFullText, $bIsOnQueriedClass, $aUpdateValues);
+
+		// 4 - The external keys -> joins...
+		//
+		if (array_key_exists($sTableClass, $aExtKeys))
+		{
+			foreach ($aExtKeys[$sTableClass] as $sKeyAttCode => $aExtFields)
+			{
+				$oKeyAttDef = self::GetAttributeDef($sTargetClass, $sKeyAttCode);
+
+				$oExtFilter = $oFilter->GetCriteria_PointingTo($sKeyAttCode);
+
+				// In case the join was not explicitely defined in the filter,
+				// we need to do it now
+				if (empty($oExtFilter))
+				{
+					$sKeyClass =  $oKeyAttDef->GetTargetClass();
+					$sKeyClassAlias = self::GenerateUniqueAlias($aClassAliases, $sKeyClass.'_'.$sKeyAttCode, $sKeyClass);
+					$oExtFilter = new DBObjectSearch($sKeyClass, $sKeyClassAlias);
+				}
+				else
+				{
+					// The aliases should not conflict because normalization occured while building the filter
+					$sKeyClass =  $oExtFilter->GetClass();
+					$sKeyClassAlias = $oExtFilter->GetClassAlias();
+					
+					// Note: there is no search condition in $oExtFilter, because normalization did merge the condition onto the top of the filter tree 
+				}
+
+				// Specify expected attributes for the target class query
+				// ... and use the current alias !
+				$aExpAtts = array();
+				$aIntermediateTranslation = array();
+				foreach($aExtFields as $sAttCode => $oAtt)
+				{
+
+					$sExtAttCode = $oAtt->GetExtAttCode();
+					if (array_key_exists($sAttCode, $aExpectedAtts))
+					{
+						// Request this attribute... transmit the alias !
+						$aExpAtts[$sExtAttCode] = $aExpectedAtts[$sAttCode];
+					}
+					// Translate mainclass.extfield => remoteclassalias.remotefieldcode
+					$oRemoteAttDef = self::GetAttributeDef($sKeyClass, $sExtAttCode);
+					$sRemoteAttExpr = $oRemoteAttDef->GetSQLExpr(); 
+					$aIntermediateTranslation[$sTargetAlias][$sAttCode] = array($sKeyClassAlias, $sRemoteAttExpr);
+					//#@# debug - echo "<p>$sTargetAlias.$sAttCode to $sKeyClassAlias.$sRemoteAttExpr (class: $sKeyClass)</p>\n";
+				}
+				$oConditionTree = $oConditionTree->Translate($aIntermediateTranslation, false);
+
+				self::DbgTrace("External key $sKeyAttCode (class: $sKeyClass), call MakeQuery()");
+				$oSelectExtKey = self::MakeQuery($sGlobalTargetAlias, $oConditionTree, $aClassAliases, $aTableAliases, $aTranslation, $oExtFilter, $aExpAtts);
+
+				$sLocalKeyField = $oKeyAttDef->GetSQLExpr();
+				$sExternalKeyField = self::DBGetKey($sKeyClass);
+				self::DbgTrace("External key $sKeyAttCode, Join on $sLocalKeyField = $sExternalKeyField");
+				if ($oKeyAttDef->IsNullAllowed())
+				{
+					$oSelectBase->AddLeftJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField);
+				}
+				else
+				{
+					$oSelectBase->AddInnerJoin($oSelectExtKey, $sLocalKeyField, $sExternalKeyField);
+				}
+			}
+		}
+
+		//MyHelpers::var_dump_html($oSelectBase->RenderSelect());
+		return $oSelectBase;
+	}
+
+	public static function GenerateUniqueAlias(&$aAliases, $sNewName, $sRealName)
+	{
+		if (!array_key_exists($sNewName, $aAliases))
+		{
+			$aAliases[$sNewName] = $sRealName;
+			return $sNewName;
+		}
+
+		for ($i = 1 ; $i < 100 ; $i++)
+		{
+			$sAnAlias = $sNewName.$i;
+			if (!array_key_exists($sAnAlias, $aAliases))
+			{
+				// Create that new alias
+				$aAliases[$sAnAlias] = $sRealName;
+				return $sAnAlias;
+			}
+		}
+		throw new CoreException('Failed to create an alias', array('aliases' => $aAliases, 'new'=>$sNewName));
+	}
+
+	public static function CheckDefinitions()
+	{
+		if (count(self::GetClasses()) == 0)
+		{
+			throw new CoreException("MetaModel::InitClasses() has not been called, or no class has been declared ?!?!");
+		}
+
+		$aErrors = array();
+		$aSugFix = array();
+		foreach (self::GetClasses() as $sClass)
+		{
+			if (self::IsAbstract($sClass)) continue;
+
+			$sNameAttCode = self::GetNameAttributeCode($sClass);
+			if (empty($sNameAttCode))
+			{
+			//  let's try this !!!
+				// $aErrors[$sClass][] = "Missing value for name definition: the framework will (should...) replace it by the id";
+				// $aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass));
+			}
+			else if(!self::IsValidAttCode($sClass, $sNameAttCode))
+			{
+				$aErrors[$sClass][] = "Unkown attribute code '".$sNameAttCode."' for the name definition";
+				$aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass));
+			}
+
+			foreach(self::GetReconcKeys($sClass) as $sReconcKeyAttCode)
+			if (!empty($sReconcKeyAttCode) && !self::IsValidAttCode($sClass, $sReconcKeyAttCode))
+			{
+				$aErrors[$sClass][] = "Unkown attribute code '".$sReconcKeyAttCode."' in the list of reconciliation keys";
+				$aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass));
+			}
+
+			foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
+			{
+				// It makes no sense to check the attributes again and again in the subclasses
+				if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue;
+
+				if ($oAttDef->IsExternalKey())
+				{
+					if (!self::IsValidClass($oAttDef->GetTargetClass()))
+					{
+						$aErrors[$sClass][] = "Unkown class '".$oAttDef->GetTargetClass()."' for the external key '$sAttCode'";
+						$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetClasses())."}";
+					}
+				}
+				elseif ($oAttDef->IsExternalField())
+				{
+					$sKeyAttCode = $oAttDef->GetKeyAttCode();
+					if (!self::IsValidAttCode($sClass, $sKeyAttCode) || !self::IsValidKeyAttCode($sClass, $sKeyAttCode))
+					{
+						$aErrors[$sClass][] = "Unkown key attribute code '".$sKeyAttCode."' for the external field $sAttCode";
+						$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sClass))."}";
+					}
+					else
+					{
+						$oKeyAttDef = self::GetAttributeDef($sClass, $sKeyAttCode);
+						$sTargetClass = $oKeyAttDef->GetTargetClass();
+						$sExtAttCode = $oAttDef->GetExtAttCode();
+						if (!self::IsValidAttCode($sTargetClass, $sExtAttCode))
+						{
+							$aErrors[$sClass][] = "Unkown key attribute code '".$sExtAttCode."' for the external field $sAttCode";
+							$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetKeysList($sTargetClass))."}";
+						}
+					}
+				}
+				else // standard attributes
+				{
+					// Check that the default values definition is a valid object!
+					$oValSetDef = $oAttDef->GetValuesDef();
+					if (!is_null($oValSetDef) && !$oValSetDef instanceof ValueSetDefinition)
+					{
+							$aErrors[$sClass][] = "Allowed values for attribute $sAttCode is not of the relevant type";
+							$aSugFix[$sClass][] = "Please set it as an instance of a ValueSetDefinition object.";
+					}
+					else
+					{
+						// Default value must be listed in the allowed values (if defined)
+						$aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode);
+						if (!is_null($aAllowedValues))
+						{
+							$sDefaultValue = $oAttDef->GetDefaultValue();
+							if (!array_key_exists($sDefaultValue, $aAllowedValues))
+							{
+								$aErrors[$sClass][] = "Default value '".$sDefaultValue."' for attribute $sAttCode is not an allowed value";
+								$aSugFix[$sClass][] = "Please pickup the default value out of {'".implode(", ", array_keys($aAllowedValues))."'}";
+							}
+						}
+					}
+				}
+				// Check dependencies
+				if ($oAttDef->IsWritable())
+				{
+					foreach ($oAttDef->GetPrerequisiteAttributes() as $sDependOnAttCode)
+					{
+						if (!self::IsValidAttCode($sClass, $sDependOnAttCode))
+						{
+							$aErrors[$sClass][] = "Unkown attribute code '".$sDependOnAttCode."' in the list of prerequisite attributes";
+							$aSugFix[$sClass][] = "Expecting a value in ".implode(", ", self::GetAttributesList($sClass));
+						}
+					}
+				}
+			}
+			foreach(self::GetClassFilterDefs($sClass) as $sFltCode=>$oFilterDef)
+			{
+				if (method_exists($oFilterDef, '__GetRefAttribute'))
+				{ 
+					$oAttDef = $oFilterDef->__GetRefAttribute();
+					if (!self::IsValidAttCode($sClass, $oAttDef->GetCode()))
+					{
+						$aErrors[$sClass][] = "Wrong attribute code '".$oAttDef->GetCode()."' (wrong class) for the \"basic\" filter $sFltCode";
+						$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}";
+					}
+				}
+			}
+
+			// Lifecycle
+			//
+			$sStateAttCode = self::GetStateAttributeCode($sClass);
+			if (strlen($sStateAttCode) > 0)
+			{
+				// Lifecycle - check that the state attribute does exist as an attribute
+				if (!self::IsValidAttCode($sClass, $sStateAttCode))
+				{
+					$aErrors[$sClass][] = "Unkown attribute code '".$sStateAttCode."' for the state definition";
+					$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}";
+				}
+				else
+				{
+					// Lifecycle - check that there is a value set constraint on the state attribute
+					$aAllowedValuesRaw = self::GetAllowedValues_att($sClass, $sStateAttCode);
+					$aStates = array_keys(self::EnumStates($sClass));
+					if (is_null($aAllowedValuesRaw))
+					{
+						$aErrors[$sClass][] = "Attribute '".$sStateAttCode."' will reflect the state of the object. It must be restricted to a set of values";
+						$aSugFix[$sClass][] = "Please define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')]";
+					}
+					else
+					{
+						$aAllowedValues = array_keys($aAllowedValuesRaw);
+	
+						// Lifecycle - check the the state attribute allowed values are defined states
+						foreach($aAllowedValues as $sValue)
+						{
+							if (!in_array($sValue, $aStates))
+							{
+								$aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has an allowed value ($sValue) which is not a known state";
+								$aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states";
+							}
+						}
+	
+						// Lifecycle - check that defined states are allowed values
+						foreach($aStates as $sStateValue)
+						{
+							if (!in_array($sStateValue, $aAllowedValues))
+							{
+								$aErrors[$sClass][] = "Attribute '".$sStateAttCode."' (object state) has a state ($sStateValue) which is not an allowed value";
+								$aSugFix[$sClass][] = "You may define its allowed_values property as [new ValueSetEnum('".implode(", ", $aStates)."')], or reconsider the list of states";
+							}
+						}
+					}
+	
+					// Lifcycle - check that the action handlers are defined
+					foreach (self::EnumStates($sClass) as $sStateCode => $aStateDef)
+					{
+						foreach(self::EnumTransitions($sClass, $sStateCode) as $sStimulusCode => $aTransitionDef)
+						{
+							foreach ($aTransitionDef['actions'] as $sActionHandler)
+							{
+								if (!method_exists($sClass, $sActionHandler))
+								{
+									$aErrors[$sClass][] = "Unknown function '$sActionHandler' in transition [$sStateCode/$sStimulusCode] for state attribute '$sStateAttCode'";
+									$aSugFix[$sClass][] = "Specify a function which prototype is in the form [public function $sActionHandler(\$sStimulusCode){return true;}]";
+								}
+							}
+						}
+					}
+				}
+			}
+
+			// ZList
+			//
+			foreach(self::EnumZLists() as $sListCode)
+			{
+				foreach (self::GetZListItems($sClass, $sListCode) as $sMyAttCode)
+				{
+					if (!self::IsValidAttCode($sClass, $sMyAttCode))
+					{
+						$aErrors[$sClass][] = "Unkown attribute code '".$sMyAttCode."' from ZList '$sListCode'";
+						$aSugFix[$sClass][] = "Expecting a value in {".implode(", ", self::GetAttributesList($sClass))."}";
+					}
+				}
+			}
+		}
+		if (count($aErrors) > 0)
+		{
+			echo "<div style=\"width:100%;padding:10px;background:#FFAAAA;display:;\">";
+			echo "<h3>Business model inconsistencies have been found</h3>\n";
+			// #@# later -> this is the responsibility of the caller to format the output
+			foreach ($aErrors as $sClass => $aMessages)
+			{
+				echo "<p>Wrong declaration for class <b>$sClass</b></p>\n";
+				echo "<ul class=\"treeview\">\n";
+				$i = 0;
+				foreach ($aMessages as $sMsg)
+				{
+					echo "<li>$sMsg ({$aSugFix[$sClass][$i]})</li>\n";
+					$i++;
+				}
+				echo "</ul>\n";
+			}
+			echo "<p>Aborting...</p>\n";
+			echo "</div>\n";
+			exit;
+		}
+	}
+
+	public static function DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes)
+	{
+		if (empty($sRepairUrl)) return;
+		if (count($aSQLFixes) == 0) return;
+
+		echo "<form action=\"$sRepairUrl\" method=\"POST\">\n";
+		echo "   <input type=\"hidden\" name=\"$sSQLStatementArgName\" value=\"".htmlentities(implode("##SEP##", $aSQLFixes))."\">\n";
+		echo "   <input type=\"submit\" value=\" Apply the changes (".count($aSQLFixes)." queries) \">\n";
+		echo "</form>\n";
+	}
+
+	public static function DBExists()
+	{
+		// returns true if at least one table exists (taking into account the DB sharing)
+		// then some tables might be missing, but that is made in DBCheckFormat
+		//
+		if (empty(self::$m_sTablePrefix))
+		{
+			return CMDBSource::IsDB(self::$m_sDBName);
+		}
+
+		// DB sharing
+		// Check if there is at least one table with the prefix
+		//
+		if (!CMDBSource::IsDB(self::$m_sDBName))
+		{
+			return false;
+		}
+		CMDBSource::SelectDB(self::$m_sDBName);
+
+		// If a table matches the prefix, then consider that the database already exists
+		$sSQL = "SHOW TABLES LIKE '".strtolower(self::$m_sTablePrefix)."%' ";
+		$result = CMDBSource::Query($sSQL);
+		return (CMDBSource::NbRows($result) > 0);
+	}
+
+	public static function DBDrop()
+	{
+		$bDropEntireDB = true;
+
+		if (!empty(self::$m_sTablePrefix))
+		{
+			// Do drop only tables corresponding to the sub-database (table prefix)
+			//           then possibly drop the DB itself (if no table remain)
+			foreach (CMDBSource::EnumTables() as $sTable)
+			{
+				// perform a case insensitive test because on Windows the table names become lowercase :-(
+				if (strtolower(substr($sTable, 0, strlen(self::$m_sTablePrefix))) == strtolower(self::$m_sTablePrefix))
+				{
+					CMDBSource::DropTable($sTable);
+				}
+				else
+				{
+					// There is at least one table which is out of the scope of the current application
+					$bDropEntireDB = false;
+				}
+			}
+		}
+
+		if ($bDropEntireDB)
+		{
+			CMDBSource::DropDB(self::$m_sDBName);
+		}
+	}
+
+
+	public static function DBCreate()
+	{
+		// Note: we have to check if the DB does exist, because we may share the DB
+		//       with other applications (in which case the DB does exist, not the tables with the given prefix)
+		if (!CMDBSource::IsDB(self::$m_sDBName))
+		{
+			CMDBSource::CreateDB(self::$m_sDBName);
+		}
+		self::DBCreateTables();
+	}
+
+	protected static function DBCreateTables()
+	{
+		list($aErrors, $aSugFix) = self::DBCheckFormat();
+
+		$aSQL = array();
+		foreach ($aSugFix as $sClass => $aQueries)
+		{
+			foreach ($aQueries as $sQuery)
+			{
+				//$aSQL[] = $sQuery;
+				// forces a refresh of cached information
+				CMDBSource::CreateTable($sQuery);
+			}
+		}
+		// does not work -how to have multiple statements in a single query?
+		// $sDoCreateAll = implode(" ; ", $aSQL);
+	}
+
+	public static function DBCheckFormat()
+	{
+		$aErrors = array();
+		$aSugFix = array();
+		foreach (self::GetClasses() as $sClass)
+		{
+			if (self::IsAbstract($sClass)) continue;
+
+			// Check that the table exists
+			//
+			$sTable = self::DBGetTable($sClass);
+			$sKeyField = self::DBGetKey($sClass);
+			$sAutoIncrement = (self::IsAutoIncrementKey($sClass) ? "AUTO_INCREMENT" : "");
+			if (!CMDBSource::IsTable($sTable))
+			{
+				$aErrors[$sClass][] = "table '$sTable' could not be found into the DB";
+				$aSugFix[$sClass][] = "CREATE TABLE `$sTable` (`$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY) ENGINE = innodb";
+			}
+			// Check that the key field exists
+			//
+			elseif (!CMDBSource::IsField($sTable, $sKeyField))
+			{
+				$aErrors[$sClass][] = "key '$sKeyField' (table $sTable) could not be found";
+				$aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sKeyField` INT(11) NOT NULL $sAutoIncrement PRIMARY KEY";
+			}
+			else
+			{
+				// Check the key field properties
+				//
+				if (!CMDBSource::IsKey($sTable, $sKeyField))
+				{
+					$aErrors[$sClass][] = "key '$sKeyField' is not a key for table '$sTable'";
+					$aSugFix[$sClass][] = "ALTER TABLE `$sTable`, DROP PRIMARY KEY, ADD PRIMARY key(`$sKeyField`)";
+				}
+				if (self::IsAutoIncrementKey($sClass) && !CMDBSource::IsAutoIncrement($sTable, $sKeyField))
+				{
+					$aErrors[$sClass][] = "key '$sKeyField' (table $sTable) is not automatically incremented";
+					$aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sKeyField` `$sKeyField` INT(11) NOT NULL AUTO_INCREMENT";
+				}
+			}
+			
+			// Check that any defined field exists
+			//
+			$aTableInfo = CMDBSource::GetTableInfo($sTable);
+
+			foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
+			{
+				// Skip this attribute if not originaly defined in this class
+				if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue;
+
+				foreach($oAttDef->DBGetUsedFields() as $sField)
+				{
+					$sDBFieldType = $oAttDef->GetDBFieldType();
+					$sFieldSpecs = $oAttDef->IsNullAllowed() ? "$sDBFieldType NULL" : "$sDBFieldType NOT NULL";
+					if (!CMDBSource::IsField($sTable, $sField))
+					{
+						$aErrors[$sClass][] = "field '$sField' could not be found in table '$sTable'";
+						$aSugFix[$sClass][] = "ALTER TABLE `$sTable` ADD `$sField` $sFieldSpecs";
+					}
+					elseif ($oAttDef->IsNullAllowed() != CMDBSource::IsNullAllowed($sTable, $sField))
+					{
+						if ($oAttDef->IsNullAllowed())
+						{
+							$aErrors[$sClass][] = "field '$sField' in table '$sTable' could be NULL";
+							$aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs";
+						}
+						else
+						{
+							$aErrors[$sClass][] = "field '$sField' in table '$sTable' could NOT be NULL";
+							$aSugFix[$sClass][] = "ALTER TABLE `$sTable` CHANGE `$sField` `$sField` $sFieldSpecs";
+						}
+					}
+				}
+			}
+		}
+		return array($aErrors, $aSugFix);
+	}
+
+
+	private static function DBCheckIntegrity_Check2Delete($sSelWrongRecs, $sErrorDesc, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel, $bProcessingFriends = false)
+	{
+		$sRootClass = self::GetRootClass($sClass);
+		$sTable = self::DBGetTable($sClass);
+		$sKeyField = self::DBGetKey($sClass);
+
+		if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0)
+		{
+			$sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')";
+		}
+		$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id");
+		if (count($aWrongRecords) == 0) return;
+
+		if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array();
+		if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array();
+
+		foreach ($aWrongRecords as $iRecordId)
+		{
+			if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable]))
+			{
+				switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'])
+				{
+				case 'Delete':
+					// Already planned for a deletion
+					// Let's concatenate the errors description together
+					$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc;
+					break;
+
+				case 'Update':
+					// Let's plan a deletion
+					break;
+				}
+			}
+			else
+			{
+				$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc;
+			}
+
+			if (!$bProcessingFriends)
+			{
+				if (!array_key_exists($sTable, $aPlannedDel) || !in_array($iRecordId, $aPlannedDel[$sTable]))
+				{
+					// Something new to be deleted...
+					$iNewDelCount++;
+				}
+			}
+
+			$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Delete';
+			$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array();
+			$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123;
+			$aPlannedDel[$sTable][] = $iRecordId;
+		}
+
+		// Now make sure that we would delete the records of the other tables for that class
+		//
+		if (!$bProcessingFriends)
+		{
+			$sDeleteKeys = "'".implode("', '", $aWrongRecords)."'";
+			foreach (self::EnumChildClasses($sRootClass, ENUM_CHILD_CLASSES_ALL) as $sFriendClass)
+			{
+				$sFriendTable = self::DBGetTable($sFriendClass);
+				$sFriendKey = self::DBGetKey($sFriendClass);
+	
+				// skip the current table
+				if ($sFriendTable == $sTable) continue; 
+	
+				$sFindRelatedRec = "SELECT DISTINCT maintable.`$sFriendKey` AS id FROM `$sFriendTable` AS maintable WHERE maintable.`$sFriendKey` IN ($sDeleteKeys)";
+				self::DBCheckIntegrity_Check2Delete($sFindRelatedRec, "Cascading deletion of record in friend table `<em>$sTable</em>`", $sFriendClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel, true);
+			}
+		}
+	}
+
+	private static function DBCheckIntegrity_Check2Update($sSelWrongRecs, $sErrorDesc, $sColumn, $sNewValue, $sClass, &$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel)
+	{
+		$sRootClass = self::GetRootClass($sClass);
+		$sTable = self::DBGetTable($sClass);
+		$sKeyField = self::DBGetKey($sClass);
+
+		if (array_key_exists($sTable, $aPlannedDel) && count($aPlannedDel[$sTable]) > 0)
+		{
+			$sSelWrongRecs .= " AND maintable.`$sKeyField` NOT IN ('".implode("', '", $aPlannedDel[$sTable])."')";
+		}
+		$aWrongRecords = CMDBSource::QueryToCol($sSelWrongRecs, "id");
+		if (count($aWrongRecords) == 0) return;
+
+		if (!array_key_exists($sRootClass, $aErrorsAndFixes)) $aErrorsAndFixes[$sRootClass] = array();
+		if (!array_key_exists($sTable, $aErrorsAndFixes[$sRootClass])) $aErrorsAndFixes[$sRootClass][$sTable] = array();
+
+		foreach ($aWrongRecords as $iRecordId)
+		{
+			if (array_key_exists($iRecordId, $aErrorsAndFixes[$sRootClass][$sTable]))
+			{
+				switch ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'])
+				{
+				case 'Delete':
+				// No need to update, the record will be deleted!
+				break;
+
+				case 'Update':
+				// Already planned for an update
+				// Add this new update spec to the list
+				$bFoundSameSpec = false;
+				foreach ($aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] as $aUpdateSpec)
+				{
+					if (($sColumn == $aUpdateSpec['column']) && ($sNewValue == $aUpdateSpec['newvalue']))
+					{
+						$bFoundSameSpec = true;
+					}
+				}
+				if (!$bFoundSameSpec)
+				{
+					$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'][] = (array('column' => $sColumn, 'newvalue'=>$sNewValue));
+					$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] .= ', '.$sErrorDesc;
+				}
+				break;
+				}
+			}
+			else
+			{
+				$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Reason'] = $sErrorDesc;
+				$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action'] = 'Update';
+				$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Action_Details'] = array(array('column' => $sColumn, 'newvalue'=>$sNewValue));
+				$aErrorsAndFixes[$sRootClass][$sTable][$iRecordId]['Pass'] = 123;
+			}
+
+		}
+	}
+
+	// returns the count of records found for deletion
+	public static function DBCheckIntegrity_SinglePass(&$aErrorsAndFixes, &$iNewDelCount, &$aPlannedDel)
+	{
+		foreach (self::GetClasses() as $sClass)
+		{
+			if (self::IsAbstract($sClass)) continue;
+			$sRootClass = self::GetRootClass($sClass);
+			$sTable = self::DBGetTable($sClass);
+			$sKeyField = self::DBGetKey($sClass);
+
+			// Check that the final class field contains the name of a class which inherited from the current class
+			//
+			if (self::HasFinalClassField($sClass))
+			{
+				$sFinalClassField = self::DBGetClassField($sClass);
+
+				$aAllowedValues = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL);
+				$sAllowedValues = implode(",", CMDBSource::Quote($aAllowedValues, true));
+
+				$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE `$sFinalClassField` NOT IN ($sAllowedValues)";
+				self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "final class (field `<em>$sFinalClassField</em>`) is wrong (expected a value in {".$sAllowedValues."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+			}
+
+			// Compound objects model - node/leaf classes (not the root itself)
+			//
+			if (!self::IsStandaloneClass($sClass) && !self::HasFinalClassField($sClass))
+			{
+				$sRootTable = self::DBGetTable($sRootClass);
+				$sRootKey = self::DBGetKey($sRootClass);
+				$sFinalClassField = self::DBGetClassField($sRootClass);
+
+				$aExpectedClasses = self::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL);
+				$sExpectedClasses = implode(",", CMDBSource::Quote($aExpectedClasses, true));
+
+				// Check that any record found here has its counterpart in the root table
+				// and which refers to a child class
+				//
+				$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` as maintable LEFT JOIN `$sRootTable` ON maintable.`$sKeyField` = `$sRootTable`.`$sRootKey` AND `$sRootTable`.`$sFinalClassField` IN ($sExpectedClasses) WHERE `$sRootTable`.`$sRootKey` IS NULL";
+				self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in `<em>$sTable</em>`, but no counterpart in root table `<em>$sRootTable</em>` (inc. records pointing to a class in {".$sExpectedClasses."})", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+
+				// Check that any record found in the root table and referring to a child class
+				// has its counterpart here (detect orphan nodes -root or in the middle of the hierarchy)
+				//
+				$sSelWrongRecs = "SELECT DISTINCT maintable.`$sRootKey` AS id FROM `$sRootTable` AS maintable LEFT JOIN `$sTable` ON maintable.`$sRootKey` = `$sTable`.`$sKeyField` WHERE `$sTable`.`$sKeyField` IS NULL AND maintable.`$sFinalClassField` IN ($sExpectedClasses)";
+				self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Found a record in root table `<em>$sRootTable</em>`, but no counterpart in table `<em>$sTable</em>` (root records pointing to a class in {".$sExpectedClasses."})", $sRootClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+			}
+
+			foreach(self::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
+			{
+				// Skip this attribute if not defined in this table
+				if (self::$m_aAttribOrigins[$sClass][$sAttCode] != $sClass) continue;
+
+				if ($oAttDef->IsExternalKey())
+				{
+					// Check that any external field is pointing to an existing object
+					//
+					$sRemoteClass = $oAttDef->GetTargetClass();
+					$sRemoteTable = self::DBGetTable($sRemoteClass);
+					$sRemoteKey = self::DBGetKey($sRemoteClass);
+
+					$sExtKeyField = $oAttDef->GetSQLExpr();
+
+					// Note: a class/table may have an external key on itself
+					$sSelBase = "SELECT DISTINCT maintable.`$sKeyField` AS id, maintable.`$sExtKeyField` AS extkey FROM `$sTable` AS maintable LEFT JOIN `$sRemoteTable` ON maintable.`$sExtKeyField` = `$sRemoteTable`.`$sRemoteKey`";
+
+					$sSelWrongRecs = $sSelBase." WHERE `$sRemoteTable`.`$sRemoteKey` IS NULL";
+					if ($oAttDef->IsNullAllowed())
+					{
+						// Exclude the records pointing to 0/null from the errors
+						$sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL";
+						$sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0";
+						self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '<em>$sAttCode</em>') non existing objects", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+					}
+					else
+					{
+						self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '<em>$sAttCode</em>') non existing objects", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+					}
+
+					// Do almost the same, taking into account the records planned for deletion
+					if (array_key_exists($sRemoteTable, $aPlannedDel) && count($aPlannedDel[$sRemoteTable]) > 0)
+					{
+						// This could be done by the mean of a 'OR ... IN (aIgnoreRecords)
+						// but in that case you won't be able to track the root cause (cascading)
+						$sSelWrongRecs = $sSelBase." WHERE maintable.`$sExtKeyField` IN ('".implode("', '", $aPlannedDel[$sRemoteTable])."')";
+						if ($oAttDef->IsNullAllowed())
+						{
+							// Exclude the records pointing to 0/null from the errors
+							$sSelWrongRecs .= " AND maintable.`$sExtKeyField` IS NOT NULL";
+							$sSelWrongRecs .= " AND maintable.`$sExtKeyField` != 0";
+							self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record pointing to (external key '<em>$sAttCode</em>') a record planned for deletion", $sExtKeyField, 'null', $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+						}
+						else
+						{
+							self::DBCheckIntegrity_Check2Delete($sSelWrongRecs, "Record pointing to (external key '<em>$sAttCode</em>') a record planned for deletion", $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+						}
+					}
+				}
+				else if ($oAttDef->IsDirectField())
+				{
+					// Check that the values fit the allowed values
+					//
+					$aAllowedValues = self::GetAllowedValues_att($sClass, $sAttCode);
+					if (!is_null($aAllowedValues) && count($aAllowedValues) > 0)
+					{
+						$sExpectedValues = implode(",", CMDBSource::Quote(array_keys($aAllowedValues), true));
+	
+						$sMyAttributeField = $oAttDef->GetSQLExpr();
+						$sDefaultValue = $oAttDef->GetDefaultValue();
+						$sSelWrongRecs = "SELECT DISTINCT maintable.`$sKeyField` AS id FROM `$sTable` AS maintable WHERE maintable.`$sMyAttributeField` NOT IN ($sExpectedValues)";
+						self::DBCheckIntegrity_Check2Update($sSelWrongRecs, "Record having a column ('<em>$sAttCode</em>') with an unexpected value", $sMyAttributeField, CMDBSource::Quote($sDefaultValue), $sClass, $aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+					}
+				}
+			}
+		}
+	}
+
+	public static function DBCheckIntegrity($sRepairUrl = "", $sSQLStatementArgName = "")
+	{
+		// Records in error, and action to be taken: delete or update
+		// by RootClass/Table/Record
+		$aErrorsAndFixes = array();
+
+		// Records to be ignored in the current/next pass
+		// by Table = array of RecordId
+		$aPlannedDel = array();
+	
+		// Count of errors in the next pass: no error means that we can leave...
+		$iErrorCount = 0;
+		// Limit in case of a bug in the algorythm
+		$iLoopCount = 0;
+
+		$iNewDelCount = 1; // startup...
+		while ($iNewDelCount > 0)
+		{
+			$iNewDelCount = 0;
+			self::DBCheckIntegrity_SinglePass($aErrorsAndFixes, $iNewDelCount, $aPlannedDel);
+			$iErrorCount += $iNewDelCount;
+
+			// Safety net #1 - limit the planned deletions
+			//
+			$iMaxDel = 1000;
+			$iPlannedDel = 0;
+			foreach ($aPlannedDel as $sTable => $aPlannedDelOnTable)
+			{
+				$iPlannedDel += count($aPlannedDelOnTable);
+			}
+			if ($iPlannedDel > $iMaxDel)
+			{
+				throw new CoreWarning("DB Integrity Check safety net - Exceeding the limit of $iMaxDel planned record deletion");
+				break;
+			}
+			// Safety net #2 - limit the iterations
+			//
+			$iLoopCount++;
+			$iMaxLoops = 10;
+			if ($iLoopCount > $iMaxLoops)
+			{
+				throw new CoreWarning("DB Integrity Check safety net - Reached the limit of $iMaxLoops loops");
+				break;
+			}
+		}
+
+		// Display the results
+		//
+		$iIssueCount = 0;
+		$aFixesDelete = array();
+		$aFixesUpdate = array();
+
+		foreach ($aErrorsAndFixes as $sRootClass => $aTables)
+		{
+			foreach ($aTables as $sTable => $aRecords)
+			{
+				foreach ($aRecords as $iRecord => $aError)
+				{
+					$sAction = $aError['Action'];
+					$sReason = $aError['Reason'];
+					$iPass = $aError['Pass'];
+
+					switch ($sAction)
+					{
+						case 'Delete':
+						$sActionDetails = "";
+						$aFixesDelete[$sTable][] = $iRecord;
+						break;
+
+						case 'Update':
+						$aUpdateDesc = array();
+						foreach($aError['Action_Details'] as $aUpdateSpec)
+						{
+							$aUpdateDesc[] = $aUpdateSpec['column']." -&gt; ".$aUpdateSpec['newvalue'];
+							$aFixesUpdate[$sTable][$aUpdateSpec['column']][$aUpdateSpec['newvalue']][] = $iRecord;
+						}
+						$sActionDetails = "Set ".implode(", ", $aUpdateDesc);
+
+						break;
+
+						default:
+						$sActionDetails = "bug: unknown action '$sAction'";
+					}
+					$aIssues[] = "$sRootClass / $sTable / $iRecord / $sReason / $sAction / $sActionDetails";
+ 					$iIssueCount++;
+				}
+			}
+		}
+
+		if ($iIssueCount > 0)
+		{
+			// Build the queries to fix in the database
+			//
+			// First step, be able to get class data out of the table name
+			// Could be optimized, because we've made the job earlier... but few benefits, so...
+			$aTable2ClassProp = array();
+			foreach (self::GetClasses() as $sClass)
+			{
+				if (self::IsAbstract($sClass)) continue;
+
+				$sRootClass = self::GetRootClass($sClass);
+				$sTable = self::DBGetTable($sClass);
+				$sKeyField = self::DBGetKey($sClass);
+	
+				$aErrorsAndFixes[$sRootClass][$sTable] = array();
+				$aTable2ClassProp[$sTable] = array('rootclass'=>$sRootClass, 'class'=>$sClass, 'keyfield'=>$sKeyField);
+			}
+			// Second step, build a flat list of SQL queries
+			$aSQLFixes = array();
+			$iPlannedUpdate = 0;
+			foreach ($aFixesUpdate as $sTable => $aColumns)
+			{
+				foreach ($aColumns as $sColumn => $aNewValues)
+				{
+					foreach ($aNewValues as $sNewValue => $aRecords)
+					{
+						$iPlannedUpdate += count($aRecords);
+						$sWrongRecords = "'".implode("', '", $aRecords)."'";
+						$sKeyField = $aTable2ClassProp[$sTable]['keyfield'];
+
+						$aSQLFixes[] = "UPDATE `$sTable` SET `$sColumn` = $sNewValue WHERE `$sKeyField` IN ($sWrongRecords)";
+					}
+				}
+			}
+			$iPlannedDel = 0;
+			foreach ($aFixesDelete as $sTable => $aRecords)
+			{
+				$iPlannedDel += count($aRecords);
+				$sWrongRecords = "'".implode("', '", $aRecords)."'";
+				$sKeyField = $aTable2ClassProp[$sTable]['keyfield'];
+
+				$aSQLFixes[] = "DELETE FROM `$sTable` WHERE `$sKeyField` IN ($sWrongRecords)";
+			}
+
+			// Report the results
+			//
+			echo "<div style=\"width:100%;padding:10px;background:#FFAAAA;display:;\">";
+			echo "<h3>Database corruption error(s): $iErrorCount issues have been encountered. $iPlannedDel records will be deleted, $iPlannedUpdate records will be updated:</h3>\n";
+			// #@# later -> this is the responsibility of the caller to format the output
+			echo "<ul class=\"treeview\">\n";
+			foreach ($aIssues as $sIssueDesc)
+			{
+				echo "<li>$sIssueDesc</li>\n";
+			}
+			echo "</ul>\n";
+			self::DBShowApplyForm($sRepairUrl, $sSQLStatementArgName, $aSQLFixes);
+			echo "<p>Aborting...</p>\n";
+			echo "</div>\n";
+			exit;
+		}
+	}
+
+	public static function Startup($sConfigFile, $bAllowMissingDB = false)
+	{
+		self::LoadConfig($sConfigFile);
+		if (self::DBExists())
+		{
+			CMDBSource::SelectDB(self::$m_sDBName);
+		}
+		else
+		{
+			if (!$bAllowMissingDB)
+			{
+				throw new CoreException('Database not found, check your configuration file', array('config_file'=>$sConfigFile, 'db_name'=>self::$m_sDBName));
+			}
+		}
+	}
+
+	public static function LoadConfig($sConfigFile)
+	{
+		$oConfig = new Config($sConfigFile);
+
+		foreach ($oConfig->GetAppModules() as $sModule => $sToInclude)
+		{
+			self::Plugin($sConfigFile, 'application', $sToInclude);
+		}
+		foreach ($oConfig->GetDataModels() as $sModule => $sToInclude)
+		{
+			self::Plugin($sConfigFile, 'business', $sToInclude);
+		}
+		foreach ($oConfig->GetAddons() as $sModule => $sToInclude)
+		{
+			self::Plugin($sConfigFile, 'addons', $sToInclude);
+		}
+
+		$sServer = $oConfig->GetDBHost();
+		$sUser = $oConfig->GetDBUser();
+		$sPwd = $oConfig->GetDBPwd();
+		$sSource = $oConfig->GetDBName();
+		$sTablePrefix = $oConfig->GetDBSubname();
+
+		// The include have been included, let's browse the existing classes and
+		// develop some data based on the proposed model
+		self::InitClasses($sTablePrefix);
+
+		self::$m_sDBName = $sSource;
+		self::$m_sTablePrefix = $sTablePrefix;
+
+		CMDBSource::Init($sServer, $sUser, $sPwd); // do not select the DB (could not exist)
+	}
+
+	protected static function Plugin($sConfigFile, $sModuleType, $sToInclude)
+	{
+		if (!file_exists($sToInclude))
+		{
+			throw new CoreException('Wrong filename in configuration file', array('file' => $sConfigFile, 'module' => $sModuleType, 'filename' => $sToInclude));
+		}
+		require_once($sToInclude);
+	}
+
+	// Building an object
+	//
+	//
+	private static $aQueryCacheGetObject = array();
+	private static $aQueryCacheGetObjectHits = array();
+	public static function GetQueryCacheStatus()
+	{
+		$aRes = array();
+		$iTotalHits = 0;
+		foreach(self::$aQueryCacheGetObjectHits as $sClass => $iHits)
+		{
+			$aRes[] = "$sClass: $iHits";
+			$iTotalHits += $iHits;
+		}
+		return $iTotalHits.' ('.implode(', ', $aRes).')';
+	}
+
+	public static function MakeSingleRow($sClass, $iKey)
+	{
+		if (!array_key_exists($sClass, self::$aQueryCacheGetObject))
+		{
+			// NOTE: Quick and VERY dirty caching mechanism which relies on
+			//       the fact that the string '987654321' will never appear in the
+			//       standard query
+			//       This will be replaced for sure with a prepared statement
+			//       or a view... next optimization to come! 
+			$oFilter = new DBObjectSearch($sClass);
+			$oFilter->AddCondition('id', 987654321, '=');
+	
+			$sSQL = self::MakeSelectQuery($oFilter);
+			self::$aQueryCacheGetObject[$sClass] = $sSQL;
+			self::$aQueryCacheGetObjectHits[$sClass] = 0;
+		}
+		else
+		{
+			$sSQL = self::$aQueryCacheGetObject[$sClass];
+			self::$aQueryCacheGetObjectHits[$sClass] += 1;
+//			echo " -load $sClass/$iKey- ".self::$aQueryCacheGetObjectHits[$sClass]."<br/>\n";
+		}
+		$sSQL = str_replace('987654321', CMDBSource::Quote($iKey), $sSQL);
+		$res = CMDBSource::Query($sSQL);
+		
+		$aRow = CMDBSource::FetchArray($res);
+		CMDBSource::FreeResult($res);
+		if (empty($aRow))
+		{
+			throw new CoreException("No result for the single row query: '$sSQL'");
+		}
+		return $aRow;
+	}
+
+	public static function GetObjectByRow($sClass, $aRow)
+	{
+		self::_check_subclass($sClass);	
+
+		// Compound objects: if available, get the final object class
+		//
+		if (!array_key_exists("finalclass", $aRow))
+		{
+			// Either this is a bug (forgot to specify a root class with a finalclass field
+			// Or this is the expected behavior, because the object is not made of several tables
+		}
+		elseif (empty($aRow["finalclass"]))
+		{
+			// The data is missing in the DB
+			// @#@ possible improvement: check that the class is valid !
+			$sRootClass = self::GetRootClass($sClass);
+			$sFinalClassField = self::DBGetClassField($sRootClass);
+			throw new CoreException("Empty class name for object $sClass::{$aRow["id"]} (root class '$sRootClass', field '{$sFinalClassField}' is empty)");
+		}
+		else
+		{
+			// do the job for the real target class
+			$sClass = $aRow["finalclass"];
+		}
+		return new $sClass($aRow);
+	}
+
+	public static function GetObject($sClass, $iKey)
+	{
+		self::_check_subclass($sClass);	
+		$aRow = self::MakeSingleRow($sClass, $iKey);
+		if (empty($aRow))
+		{
+			return null;
+		}
+		return self::GetObjectByRow($sClass, $aRow);
+	}
+
+	public static function NewObject($sClass)
+	{
+		self::_check_subclass($sClass);
+		return new $sClass();
+	}	
+
+	public static function BulkDelete(DBObjectSearch $oFilter)
+	{
+		$sSQL = self::MakeDeleteQuery($oFilter);
+		CMDBSource::Query($sSQL);
+	}
+
+	public static function BulkUpdate(DBObjectSearch $oFilter, array $aValues)
+	{
+		// $aValues is an array of $sAttCode => $value
+		$sSQL = self::MakeUpdateQuery($oFilter, $aValues);
+		CMDBSource::Query($sSQL);
+	}
+
+	// Links
+	//
+	//
+	public static function EnumReferencedClasses($sClass)
+	{
+		self::_check_subclass($sClass);	
+
+		// 1-N links (referenced by my class), returns an array of sAttCode=>sClass
+		$aResult = array();
+		foreach(self::$m_aAttribDefs[$sClass] as $sAttCode=>$oAttDef)
+		{
+			if ($oAttDef->IsExternalKey())
+			{
+				$aResult[$sAttCode] = $oAttDef->GetTargetClass();
+			}
+		}
+		return $aResult;
+	}
+	public static function EnumReferencingClasses($sClass, $bSkipLinkingClasses = false)
+	{
+		self::_check_subclass($sClass);	
+
+		$aLinksClasses = self::EnumLinksClasses();
+
+		// 1-N links (referencing my class), array of sClass => array of sAttcode
+		$aResult = array();
+		foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes)
+		{
+			if ($bSkipLinkingClasses && in_array($sSomeClass, $aLinksClasses)) continue;
+
+			$aExtKeys = array();
+			foreach ($aClassAttributes as $sAttCode=>$oAttDef)
+			{
+				if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue;
+				if ($oAttDef->IsExternalKey() && ($oAttDef->GetTargetClass() == $sClass))
+				{
+					$aExtKeys[] = $sAttCode;
+				}
+			}
+			if (count($aExtKeys) != 0)
+			{
+				$aResult[$sSomeClass] = $aExtKeys;
+			}
+		}
+		return $aResult;
+	}
+	public static function EnumLinksClasses()
+	{
+		// Returns a flat array of classes having at least two external keys
+		$aResult = array();
+		foreach (self::$m_aAttribDefs as $sSomeClass=>$aClassAttributes)
+		{
+			$iExtKeyCount = 0;
+			foreach ($aClassAttributes as $sAttCode=>$oAttDef)
+			{
+				if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue;
+				if ($oAttDef->IsExternalKey())
+				{
+					$iExtKeyCount++;
+				}
+			}
+			if ($iExtKeyCount >= 2)
+			{
+				$aResult[] = $sSomeClass;
+			}
+		}
+		return $aResult;
+	}
+	public static function EnumLinkingClasses($sClass = "")
+	{
+		// N-N links, array of sLinkClass => (array of sAttCode=>sClass)
+		$aResult = array();
+		foreach (self::EnumLinksClasses() as $sSomeClass)
+		{
+			$aTargets = array();
+			$bFoundClass = false;
+			foreach (self::ListAttributeDefs($sSomeClass) as $sAttCode=>$oAttDef)
+			{
+				if (self::$m_aAttribOrigins[$sSomeClass][$sAttCode] != $sSomeClass) continue;
+				if ($oAttDef->IsExternalKey())
+				{
+					$sRemoteClass = $oAttDef->GetTargetClass();
+					if (empty($sClass))
+					{
+						$aTargets[$sAttCode] = $sRemoteClass;
+					}
+					elseif ($sClass == $sRemoteClass)
+					{
+						$bFoundClass = true;
+					}
+					else
+					{
+						$aTargets[$sAttCode] = $sRemoteClass;
+					}
+				}
+			}
+			if (empty($sClass) || $bFoundClass)
+			{
+				$aResult[$sSomeClass] = $aTargets;
+			}
+		}
+		return $aResult;
+	}
+
+	public static function GetLinkLabel($sLinkClass, $sAttCode)
+	{
+		self::_check_subclass($sLinkClass);	
+
+		// e.g. "supported by" (later: $this->GetLinkLabel(), computed on link data!)
+		return self::GetLabel($sLinkClass, $sAttCode);
+	}
+
+} // class MetaModel
+
+
+// Standard attribute lists
+MetaModel::RegisterZList("noneditable", array("description"=>"non editable fields", "type"=>"attributes"));
+
+MetaModel::RegisterZList("details", array("description"=>"All attributes to be displayed for the 'details' of an object", "type"=>"attributes"));
+MetaModel::RegisterZList("list", array("description"=>"All attributes to be displayed for a list of objects", "type"=>"attributes"));
+MetaModel::RegisterZList("preview", array("description"=>"All attributes visible in preview mode", "type"=>"attributes"));
+
+MetaModel::RegisterZList("standard_search", array("description"=>"List of criteria for the standard search", "type"=>"filters"));
+MetaModel::RegisterZList("advanced_search", array("description"=>"List of criteria for the advanced search", "type"=>"filters"));
+
+
+?>

+ 3 - 0
core/oql/build.cmd

@@ -0,0 +1,3 @@
+c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\LexerGenerator\cli.php" oql-lexer.plex
+c:\itop\php-5.2.3\php.exe -q "C:\itop\PHP-5.2.3\PEAR\PHP\ParserGenerator\cli.php" oql-parser.y
+pause

+ 522 - 0
core/oql/oql-lexer.php

@@ -0,0 +1,522 @@
+<?php
+
+// Notes (from the source file: oql-lexer.plex) - Romain
+//
+// The strval rule is a little bit cryptic.
+// This is due to both a bug in the lexer generator and the complexity of our need
+// The rule means: either a quoted string with ", or a quoted string with '
+//                 literal " (resp. ') must be escaped by a \
+//                 \ must be escaped by an additional \
+// 
+// Here are the issues and limitation found in the lexer generator:
+// * Matching simple quotes is an issue, because regexp are not correctly escaped (and the ESC code is escaped itself)
+//    Workaround: insert '.chr(39).' which will be a real ' in the end
+// * Matching an alternate regexp is an issue because you must specify  "|^...."
+//   and the regexp parser will not accept that syntax
+//    Workaround: insert '.chr(94).' which will be a real ^
+//
+// Let's analyze an overview of the regexp, we have
+// 1) The strval rule in the lexer definition
+//     /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
+// 2) Becomes the php expression in the lexer
+//    (note the escaped double quotes, hopefully having no effect, but showing where the issue is!)
+//     $myRegexp = '/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/';
+//
+// To be fixed in LexerGenerator/Parser.y, in doLongestMatch (doFirstMatch is ok)
+//
+//
+// Now, let's explain how the regexp has been designed.
+// Here is a simplified version, dealing with simple quotes, and based on the assumption that the lexer generator has been fixed!
+// The strval rule in the lexer definition
+//     /'([^\\']*(\\')*(\\\\)*)*'/
+// This means anything containing \\ or \' or any other char but a standalone ' or \
+// This means ' or \ could not be found without a preceding \
+//
+class OQLLexerRaw
+{
+    protected $data;  // input string
+    public $token;  // token id
+    public $value;  // token string representation
+    protected $line;  // current line
+    protected $count; // current column
+
+    function __construct($data)
+    {
+        $this->data  = $data;
+        $this->count = 0;
+        $this->line  = 1;
+    }
+
+
+    private $_yy_state = 1;
+    private $_yy_stack = array();
+
+    function yylex()
+    {
+        return $this->{'yylex' . $this->_yy_state}();
+    }
+
+    function yypushstate($state)
+    {
+        array_push($this->_yy_stack, $this->_yy_state);
+        $this->_yy_state = $state;
+    }
+
+    function yypopstate()
+    {
+        $this->_yy_state = array_pop($this->_yy_stack);
+    }
+
+    function yybegin($state)
+    {
+        $this->_yy_state = $state;
+    }
+
+
+
+
+    function yylex1()
+    {
+        if ($this->count >= strlen($this->data)) {
+            return false; // end of input
+        }
+    	do {
+	    	$rules = array(
+    			'/^[ \t\n]+/',
+    			'/^SELECT/',
+    			'/^AS/',
+    			'/^WHERE/',
+    			'/^JOIN/',
+    			'/^ON/',
+    			'/^\//',
+    			'/^\\*/',
+    			'/^\\+/',
+    			'/^-/',
+    			'/^AND/',
+    			'/^OR/',
+    			'/^,/',
+    			'/^\\(/',
+    			'/^\\)/',
+    			'/^=/',
+    			'/^!=/',
+    			'/^>/',
+    			'/^</',
+    			'/^>=/',
+    			'/^<=/',
+    			'/^LIKE/',
+    			'/^NOT LIKE/',
+    			'/^IN/',
+    			'/^NOT IN/',
+    			'/^INTERVAL/',
+    			'/^IF/',
+    			'/^ELT/',
+    			'/^COALESCE/',
+    			'/^CONCAT/',
+    			'/^SUBSTR/',
+    			'/^TRIM/',
+    			'/^DATE/',
+    			'/^DATE_FORMAT/',
+    			'/^CURRENT_DATE/',
+    			'/^NOW/',
+    			'/^TIME/',
+    			'/^TO_DAYS/',
+    			'/^FROM_DAYS/',
+    			'/^YEAR/',
+    			'/^MONTH/',
+    			'/^DAY/',
+    			'/^DATE_ADD/',
+    			'/^DATE_SUB/',
+    			'/^ROUND/',
+    			'/^FLOOR/',
+    			'/^[0-9]+|0x[0-9a-fA-F]+/',
+    			'/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/',
+    			'/^([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/',
+    			'/^\\./',
+	    	);
+	    	$match = false;
+	    	foreach ($rules as $index => $rule) {
+	    		if (preg_match($rule, substr($this->data, $this->count), $yymatches)) {
+	            	if ($match) {
+	            	    if (strlen($yymatches[0]) > strlen($match[0][0])) {
+	            	    	$match = array($yymatches, $index); // matches, token
+	            	    }
+	            	} else {
+	            		$match = array($yymatches, $index);
+	            	}
+	            }
+	    	}
+	    	if (!$match) {
+	            throw new Exception('Unexpected input at line' . $this->line .
+	                ': ' . $this->data[$this->count]);
+	    	}
+	    	$this->token = $match[1];
+	    	$this->value = $match[0][0];
+	    	$yysubmatches = $match[0];
+	    	array_shift($yysubmatches);
+	    	if (!$yysubmatches) {
+	    		$yysubmatches = array();
+	    	}
+	        $r = $this->{'yy_r1_' . $this->token}($yysubmatches);
+	        if ($r === null) {
+	            $this->count += strlen($this->value);
+	            $this->line += substr_count($this->value, "\n");
+	            // accept this token
+	            return true;
+	        } elseif ($r === true) {
+	            // we have changed state
+	            // process this token in the new state
+	            return $this->yylex();
+	        } elseif ($r === false) {
+	            $this->count += strlen($this->value);
+	            $this->line += substr_count($this->value, "\n");
+	            if ($this->count >= strlen($this->data)) {
+	                return false; // end of input
+	            }
+	            // skip this token
+	            continue;
+	        } else {
+	            $yy_yymore_patterns = array_slice($rules, $this->token, true);
+	            // yymore is needed
+	            do {
+	                if (!isset($yy_yymore_patterns[$this->token])) {
+	                    throw new Exception('cannot do yymore for the last token');
+	                }
+			    	$match = false;
+	                foreach ($yy_yymore_patterns[$this->token] as $index => $rule) {
+	                	if (preg_match('/' . $rule . '/',
+	                      	  substr($this->data, $this->count), $yymatches)) {
+	                    	$yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
+			            	if ($match) {
+			            	    if (strlen($yymatches[0]) > strlen($match[0][0])) {
+			            	    	$match = array($yymatches, $index); // matches, token
+			            	    }
+			            	} else {
+			            		$match = array($yymatches, $index);
+			            	}
+			            }
+			    	}
+			    	if (!$match) {
+			            throw new Exception('Unexpected input at line' . $this->line .
+			                ': ' . $this->data[$this->count]);
+			    	}
+			    	$this->token = $match[1];
+			    	$this->value = $match[0][0];
+			    	$yysubmatches = $match[0];
+			    	array_shift($yysubmatches);
+			    	if (!$yysubmatches) {
+			    		$yysubmatches = array();
+			    	}
+	                $this->line = substr_count($this->value, "\n");
+	                $r = $this->{'yy_r1_' . $this->token}();
+	            } while ($r !== null || !$r);
+		        if ($r === true) {
+		            // we have changed state
+		            // process this token in the new state
+		            return $this->yylex();
+		        } else {
+	                // accept
+	                $this->count += strlen($this->value);
+	                $this->line += substr_count($this->value, "\n");
+	                return true;
+		        }
+	        }
+        } while (true);
+
+    } // end function
+
+    function yy_r1_0($yy_subpatterns)
+    {
+
+	return false;
+    }
+    function yy_r1_1($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::SELECT;
+    }
+    function yy_r1_2($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::AS_ALIAS;
+    }
+    function yy_r1_3($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::WHERE;
+    }
+    function yy_r1_4($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::JOIN;
+    }
+    function yy_r1_5($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::ON;
+    }
+    function yy_r1_6($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::MATH_DIV;
+    }
+    function yy_r1_7($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::MATH_MULT;
+    }
+    function yy_r1_8($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::MATH_PLUS;
+    }
+    function yy_r1_9($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::MATH_MINUS;
+    }
+    function yy_r1_10($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::LOG_AND;
+    }
+    function yy_r1_11($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::LOG_OR;
+    }
+    function yy_r1_12($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::COMA;
+    }
+    function yy_r1_13($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::PAR_OPEN;
+    }
+    function yy_r1_14($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::PAR_CLOSE;
+    }
+    function yy_r1_15($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::EQ;
+    }
+    function yy_r1_16($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NOT_EQ;
+    }
+    function yy_r1_17($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::GT;
+    }
+    function yy_r1_18($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::LT;
+    }
+    function yy_r1_19($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::GE;
+    }
+    function yy_r1_20($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::LE;
+    }
+    function yy_r1_21($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::LIKE;
+    }
+    function yy_r1_22($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NOT_LIKE;
+    }
+    function yy_r1_23($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::IN;
+    }
+    function yy_r1_24($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NOT_IN;
+    }
+    function yy_r1_25($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::INTERVAL;
+    }
+    function yy_r1_26($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_IF;
+    }
+    function yy_r1_27($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_ELT;
+    }
+    function yy_r1_28($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_COALESCE;
+    }
+    function yy_r1_29($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_CONCAT;
+    }
+    function yy_r1_30($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_SUBSTR;
+    }
+    function yy_r1_31($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_TRIM;
+    }
+    function yy_r1_32($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_DATE;
+    }
+    function yy_r1_33($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_DATE_FORMAT;
+    }
+    function yy_r1_34($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_CURRENT_DATE;
+    }
+    function yy_r1_35($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_NOW;
+    }
+    function yy_r1_36($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_TIME;
+    }
+    function yy_r1_37($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_TO_DAYS;
+    }
+    function yy_r1_38($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_FROM_DAYS;
+    }
+    function yy_r1_39($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_YEAR;
+    }
+    function yy_r1_40($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_MONTH;
+    }
+    function yy_r1_41($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_DAY;
+    }
+    function yy_r1_42($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_DATE_ADD;
+    }
+    function yy_r1_43($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_DATE_SUB;
+    }
+    function yy_r1_44($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_ROUND;
+    }
+    function yy_r1_45($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::F_FLOOR;
+    }
+    function yy_r1_46($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NUMVAL;
+    }
+    function yy_r1_47($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::STRVAL;
+    }
+    function yy_r1_48($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::NAME;
+    }
+    function yy_r1_49($yy_subpatterns)
+    {
+
+	$this->token = OQLParser::DOT;
+    }
+
+
+}
+
+define('UNEXPECTED_INPUT_AT_LINE', 'Unexpected input at line');
+
+class OQLLexerException extends OQLException
+{
+	public function __construct($sInput, $iLine, $iCol, $sUnexpected)
+	{
+		parent::__construct("Syntax error", $sInput, $iLine, $iCol, $sUnexpected);
+	}
+}
+
+class OQLLexer extends OQLLexerRaw 
+{
+	public function getTokenPos()
+	{
+		return max(0, $this->count - strlen($this->value));
+	}
+
+   function yylex()
+   {
+      try
+      {
+      	return parent::yylex();
+		}
+		catch (Exception $e)
+		{
+			$sMessage = $e->getMessage();
+			if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
+			{
+				$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
+				if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
+				{
+					$iLine = $aMatches[1];
+					$sUnexpected = $aMatches[2];
+					throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
+				}
+			}
+			// Default: forward the exception
+			throw $e;
+		}
+	}
+}
+?>

+ 305 - 0
core/oql/oql-lexer.plex

@@ -0,0 +1,305 @@
+<?php
+
+// Notes (from the source file: oql-lexer.plex) - Romain
+//
+// The strval rule is a little bit cryptic.
+// This is due to both a bug in the lexer generator and the complexity of our need
+// The rule means: either a quoted string with ", or a quoted string with '
+//                 literal " (resp. ') must be escaped by a \
+//                 \ must be escaped by an additional \
+// 
+// Here are the issues and limitation found in the lexer generator:
+// * Matching simple quotes is an issue, because regexp are not correctly escaped (and the ESC code is escaped itself)
+//    Workaround: insert '.chr(39).' which will be a real ' in the end
+// * Matching an alternate regexp is an issue because you must specify  "|^...."
+//   and the regexp parser will not accept that syntax
+//    Workaround: insert '.chr(94).' which will be a real ^
+//
+// Let's analyze an overview of the regexp, we have
+// 1) The strval rule in the lexer definition
+//     /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
+// 2) Becomes the php expression in the lexer
+//    (note the escaped double quotes, hopefully having no effect, but showing where the issue is!)
+//     $myRegexp = '/^\"([^\\\\\"]|\\\\\"|\\\\\\\\)*\"|'.chr(94).chr(39).'([^\\\\'.chr(39).']|\\\\'.chr(39).'|\\\\\\\\)*'.chr(39).'/';
+//
+// To be fixed in LexerGenerator/Parser.y, in doLongestMatch (doFirstMatch is ok)
+//
+//
+// Now, let's explain how the regexp has been designed.
+// Here is a simplified version, dealing with simple quotes, and based on the assumption that the lexer generator has been fixed!
+// The strval rule in the lexer definition
+//     /'([^\\']*(\\')*(\\\\)*)*'/
+// This means anything containing \\ or \' or any other char but a standalone ' or \
+// This means ' or \ could not be found without a preceding \
+//
+class OQLLexerRaw
+{
+    protected $data;  // input string
+    public $token;  // token id
+    public $value;  // token string representation
+    protected $line;  // current line
+    protected $count; // current column
+
+    function __construct($data)
+    {
+        $this->data  = $data;
+        $this->count = 0;
+        $this->line  = 1;
+    }
+
+/*!lex2php
+%input   $this->data
+%counter $this->count
+%token   $this->token
+%value   $this->value
+%line    $this->line
+%matchlongest 1
+whitespace = /[ \t\n]+/
+select	  = "SELECT"
+as_alias   = "AS"
+where	     = "WHERE"
+join	     = "JOIN"
+on	        = "ON"
+coma       = ","
+par_open   = "("
+par_close  = ")"
+math_div   = "/"
+math_mult  = "*"
+math_plus  = "+"
+math_minus = "-"
+log_and    = "AND"
+log_or     = "OR"
+eq         = "="
+not_eq     = "!="
+gt         = ">"
+lt         = "<"
+ge         = ">="
+le         = "<="
+like       = "LIKE"
+not_like   = "NOT LIKE"
+in         = "IN"
+not_in     = "NOT IN"
+interval   = "INTERVAL"
+f_if       = "IF"
+f_elt      = "ELT"
+f_coalesce = "COALESCE"
+f_concat   = "CONCAT"
+f_substr   = "SUBSTR"
+f_trim     = "TRIM"
+f_date     = "DATE"
+f_date_format = "DATE_FORMAT"
+f_current_date = "CURRENT_DATE"
+f_now      = "NOW"
+f_time     = "TIME"
+f_to_days   = "TO_DAYS"
+f_from_days = "FROM_DAYS"
+f_year     = "YEAR"
+f_month    = "MONTH"
+f_day      = "DAY"
+f_date_add = "DATE_ADD"
+f_date_sub = "DATE_SUB"
+f_round    = "ROUND"
+f_floor    = "FLOOR"
+numval     = /[0-9]+|0x[0-9a-fA-F]+/
+strval     = /"([^\\"]|\\"|\\\\)*"|'.chr(94).chr(39).'([^\\'.chr(39).']|\\'.chr(39).'|\\\\)*'.chr(39).'/
+name       = /([_a-zA-Z][_a-zA-Z0-9]*|`[^`]+`)/
+dot       = "."
+*/
+
+/*!lex2php
+whitespace {
+	return false;
+}
+select {
+	$this->token = OQLParser::SELECT;
+}
+as_alias {
+	$this->token = OQLParser::AS_ALIAS;
+}
+where {
+	$this->token = OQLParser::WHERE;
+}
+join {
+	$this->token = OQLParser::JOIN;
+}
+on {
+	$this->token = OQLParser::ON;
+}
+math_div {
+	$this->token = OQLParser::MATH_DIV;
+}
+math_mult {
+	$this->token = OQLParser::MATH_MULT;
+}
+math_plus {
+	$this->token = OQLParser::MATH_PLUS;
+}
+math_minus {
+	$this->token = OQLParser::MATH_MINUS;
+}
+log_and {
+	$this->token = OQLParser::LOG_AND;
+}
+log_or {
+	$this->token = OQLParser::LOG_OR;
+}
+coma {
+	$this->token = OQLParser::COMA;
+}
+par_open {
+	$this->token = OQLParser::PAR_OPEN;
+}
+par_close {
+	$this->token = OQLParser::PAR_CLOSE;
+}
+eq {
+	$this->token = OQLParser::EQ;
+}
+not_eq {
+	$this->token = OQLParser::NOT_EQ;
+}
+gt {
+	$this->token = OQLParser::GT;
+}
+lt {
+	$this->token = OQLParser::LT;
+}
+ge {
+	$this->token = OQLParser::GE;
+}
+le {
+	$this->token = OQLParser::LE;
+}
+like {
+	$this->token = OQLParser::LIKE;
+}
+not_like {
+	$this->token = OQLParser::NOT_LIKE;
+}
+in {
+	$this->token = OQLParser::IN;
+}
+not_in {
+	$this->token = OQLParser::NOT_IN;
+}
+interval {
+	$this->token = OQLParser::INTERVAL;
+}
+f_if {
+	$this->token = OQLParser::F_IF;
+}
+f_elt {
+	$this->token = OQLParser::F_ELT;
+}
+f_coalesce {
+	$this->token = OQLParser::F_COALESCE;
+}
+f_concat {
+	$this->token = OQLParser::F_CONCAT;
+}
+f_substr {
+	$this->token = OQLParser::F_SUBSTR;
+}
+f_trim {
+	$this->token = OQLParser::F_TRIM;
+}
+f_date {
+	$this->token = OQLParser::F_DATE;
+}
+f_date_format {
+	$this->token = OQLParser::F_DATE_FORMAT;
+}
+f_current_date {
+	$this->token = OQLParser::F_CURRENT_DATE;
+}
+f_now {
+	$this->token = OQLParser::F_NOW;
+}
+f_time {
+	$this->token = OQLParser::F_TIME;
+}
+f_to_days {
+	$this->token = OQLParser::F_TO_DAYS;
+}
+f_from_days {
+	$this->token = OQLParser::F_FROM_DAYS;
+}
+f_year {
+	$this->token = OQLParser::F_YEAR;
+}
+f_month {
+	$this->token = OQLParser::F_MONTH;
+}
+f_day {
+	$this->token = OQLParser::F_DAY;
+}
+f_date_add {
+	$this->token = OQLParser::F_DATE_ADD;
+}
+f_date_sub {
+	$this->token = OQLParser::F_DATE_SUB;
+}
+f_round {
+	$this->token = OQLParser::F_ROUND;
+}
+f_floor {
+	$this->token = OQLParser::F_FLOOR;
+}
+numval {
+	$this->token = OQLParser::NUMVAL;
+}
+strval {
+	$this->token = OQLParser::STRVAL;
+}
+name {
+	$this->token = OQLParser::NAME;
+}
+dot {
+	$this->token = OQLParser::DOT;
+}
+*/
+
+}
+
+define('UNEXPECTED_INPUT_AT_LINE', 'Unexpected input at line');
+
+class OQLLexerException extends OQLException
+{
+	public function __construct($sInput, $iLine, $iCol, $sUnexpected)
+	{
+		parent::__construct("Syntax error", $sInput, $iLine, $iCol, $sUnexpected);
+	}
+}
+
+class OQLLexer extends OQLLexerRaw 
+{
+	public function getTokenPos()
+	{
+		return max(0, $this->count - strlen($this->value));
+	}
+
+   function yylex()
+   {
+      try
+      {
+      	return parent::yylex();
+		}
+		catch (Exception $e)
+		{
+			$sMessage = $e->getMessage();
+			if (substr($sMessage, 0, strlen(UNEXPECTED_INPUT_AT_LINE)) == UNEXPECTED_INPUT_AT_LINE)
+			{
+				$sLineAndChar = substr($sMessage, strlen(UNEXPECTED_INPUT_AT_LINE));
+				if (preg_match('#^([0-9]+): (.+)$#', $sLineAndChar, $aMatches))
+				{
+					$iLine = $aMatches[1];
+					$sUnexpected = $aMatches[2];
+					throw new OQLLexerException($this->data, $iLine, $this->count, $sUnexpected);
+				}
+			}
+			// Default: forward the exception
+			throw $e;
+		}
+	}
+}
+?>

+ 1669 - 0
core/oql/oql-parser.php

@@ -0,0 +1,1669 @@
+<?php
+/* Driver template for the PHP_OQLParser_rGenerator parser generator. (PHP port of LEMON)
+*/
+
+/**
+ * This can be used to store both the string representation of
+ * a token, and any useful meta-data associated with the token.
+ *
+ * meta-data should be stored as an array
+ */
+class OQLParser_yyToken implements ArrayAccess
+{
+    public $string = '';
+    public $metadata = array();
+
+    function __construct($s, $m = array())
+    {
+        if ($s instanceof OQLParser_yyToken) {
+            $this->string = $s->string;
+            $this->metadata = $s->metadata;
+        } else {
+            $this->string = (string) $s;
+            if ($m instanceof OQLParser_yyToken) {
+                $this->metadata = $m->metadata;
+            } elseif (is_array($m)) {
+                $this->metadata = $m;
+            }
+        }
+    }
+
+    function __toString()
+    {
+        return $this->_string;
+    }
+
+    function offsetExists($offset)
+    {
+        return isset($this->metadata[$offset]);
+    }
+
+    function offsetGet($offset)
+    {
+        return $this->metadata[$offset];
+    }
+
+    function offsetSet($offset, $value)
+    {
+        if ($offset === null) {
+            if (isset($value[0])) {
+                $x = ($value instanceof OQLParser_yyToken) ?
+                    $value->metadata : $value;
+                $this->metadata = array_merge($this->metadata, $x);
+                return;
+            }
+            $offset = count($this->metadata);
+        }
+        if ($value === null) {
+            return;
+        }
+        if ($value instanceof OQLParser_yyToken) {
+            if ($value->metadata) {
+                $this->metadata[$offset] = $value->metadata;
+            }
+        } elseif ($value) {
+            $this->metadata[$offset] = $value;
+        }
+    }
+
+    function offsetUnset($offset)
+    {
+        unset($this->metadata[$offset]);
+    }
+}
+
+/** The following structure represents a single element of the
+ * parser's stack.  Information stored includes:
+ *
+ *   +  The state number for the parser at this level of the stack.
+ *
+ *   +  The value of the token stored at this level of the stack.
+ *      (In other words, the "major" token.)
+ *
+ *   +  The semantic value stored at this level of the stack.  This is
+ *      the information used by the action routines in the grammar.
+ *      It is sometimes called the "minor" token.
+ */
+class OQLParser_yyStackEntry
+{
+    public $stateno;       /* The state-number */
+    public $major;         /* The major token value.  This is the code
+                     ** number for the token at this stack level */
+    public $minor; /* The user-supplied minor token value.  This
+                     ** is the value of the token  */
+};
+
+// code external to the class is included here
+
+// declare_class is output here
+#line 24 "oql-parser.y"
+class OQLParserRaw#line 102 "oql-parser.php"
+{
+/* First off, code is included which follows the "include_class" declaration
+** in the input file. */
+
+/* Next is all token values, as class constants
+*/
+/* 
+** These constants (all generated automatically by the parser generator)
+** specify the various kinds of tokens (terminals) that the parser
+** understands. 
+**
+** Each symbol here is a terminal symbol in the grammar.
+*/
+    const SELECT                         =  1;
+    const AS_ALIAS                       =  2;
+    const WHERE                          =  3;
+    const JOIN                           =  4;
+    const ON                             =  5;
+    const EQ                             =  6;
+    const PAR_OPEN                       =  7;
+    const PAR_CLOSE                      =  8;
+    const COMA                           =  9;
+    const INTERVAL                       = 10;
+    const F_DAY                          = 11;
+    const F_MONTH                        = 12;
+    const F_YEAR                         = 13;
+    const DOT                            = 14;
+    const NAME                           = 15;
+    const NUMVAL                         = 16;
+    const STRVAL                         = 17;
+    const NOT_EQ                         = 18;
+    const LOG_AND                        = 19;
+    const LOG_OR                         = 20;
+    const MATH_DIV                       = 21;
+    const MATH_MULT                      = 22;
+    const MATH_PLUS                      = 23;
+    const MATH_MINUS                     = 24;
+    const GT                             = 25;
+    const LT                             = 26;
+    const GE                             = 27;
+    const LE                             = 28;
+    const LIKE                           = 29;
+    const NOT_LIKE                       = 30;
+    const IN                             = 31;
+    const NOT_IN                         = 32;
+    const F_IF                           = 33;
+    const F_ELT                          = 34;
+    const F_COALESCE                     = 35;
+    const F_CONCAT                       = 36;
+    const F_SUBSTR                       = 37;
+    const F_TRIM                         = 38;
+    const F_DATE                         = 39;
+    const F_DATE_FORMAT                  = 40;
+    const F_CURRENT_DATE                 = 41;
+    const F_NOW                          = 42;
+    const F_TIME                         = 43;
+    const F_TO_DAYS                      = 44;
+    const F_FROM_DAYS                    = 45;
+    const F_DATE_ADD                     = 46;
+    const F_DATE_SUB                     = 47;
+    const F_ROUND                        = 48;
+    const F_FLOOR                        = 49;
+    const YY_NO_ACTION = 205;
+    const YY_ACCEPT_ACTION = 204;
+    const YY_ERROR_ACTION = 203;
+
+/* Next are that tables used to determine what action to take based on the
+** current state and lookahead token.  These tables are used to implement
+** functions that take a state number and lookahead value and return an
+** action integer.  
+**
+** Suppose the action integer is N.  Then the action is determined as
+** follows
+**
+**   0 <= N < self::YYNSTATE                              Shift N.  That is,
+**                                                        push the lookahead
+**                                                        token onto the stack
+**                                                        and goto state N.
+**
+**   self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE   Reduce by rule N-YYNSTATE.
+**
+**   N == self::YYNSTATE+self::YYNRULE                    A syntax error has occurred.
+**
+**   N == self::YYNSTATE+self::YYNRULE+1                  The parser accepts its
+**                                                        input. (and concludes parsing)
+**
+**   N == self::YYNSTATE+self::YYNRULE+2                  No such action.  Denotes unused
+**                                                        slots in the yy_action[] table.
+**
+** The action table is constructed as a single large static array $yy_action.
+** Given state S and lookahead X, the action is computed as
+**
+**      self::$yy_action[self::$yy_shift_ofst[S] + X ]
+**
+** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value
+** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if
+** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that
+** the action is not in the table and that self::$yy_default[S] should be used instead.  
+**
+** The formula above is for computing the action when the lookahead is
+** a terminal symbol.  If the lookahead is a non-terminal (as occurs after
+** a reduce action) then the static $yy_reduce_ofst array is used in place of
+** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of
+** self::YY_SHIFT_USE_DFLT.
+**
+** The following are the tables generated in this section:
+**
+**  self::$yy_action        A single table containing all actions.
+**  self::$yy_lookahead     A table containing the lookahead for each entry in
+**                          yy_action.  Used to detect hash collisions.
+**  self::$yy_shift_ofst    For each state, the offset into self::$yy_action for
+**                          shifting terminals.
+**  self::$yy_reduce_ofst   For each state, the offset into self::$yy_action for
+**                          shifting non-terminals after a reduce.
+**  self::$yy_default       Default action for each state.
+*/
+    const YY_SZ_ACTTAB = 419;
+static public $yy_action = array(
+ /*     0 */     5,   57,    8,    4,   95,   96,   97,    6,   93,   76,
+ /*    10 */    77,   89,    2,   53,   86,   50,   54,   25,   52,   55,
+ /*    20 */    51,   46,   47,   49,   56,   70,   94,  110,  109,  108,
+ /*    30 */   107,  111,  112,  115,  114,  113,  106,   71,   98,   99,
+ /*    40 */   100,  104,  103,   26,   66,   38,   42,    9,   81,    5,
+ /*    50 */    62,   44,   82,   95,   96,   97,    3,   93,   76,   77,
+ /*    60 */    39,  102,   92,   75,   74,   73,   72,   75,   74,   73,
+ /*    70 */    72,   10,   66,   41,   91,   94,  110,  109,  108,  107,
+ /*    80 */   111,  112,  115,  114,  113,  106,   71,   98,   99,  100,
+ /*    90 */   104,  103,    5,   63,   90,   22,   95,   96,   97,   61,
+ /*   100 */    93,   76,   77,   65,   64,   60,   83,   11,   80,   79,
+ /*   110 */    91,   33,   91,   22,   21,   18,   16,   12,   94,  110,
+ /*   120 */   109,  108,  107,  111,  112,  115,  114,  113,  106,   71,
+ /*   130 */    98,   99,  100,  104,  103,  204,  105,   87,   42,   23,
+ /*   140 */    43,   24,   66,   88,   30,   28,   84,   45,   36,    6,
+ /*   150 */    22,   20,   58,   15,   32,   37,    1,   76,   77,  101,
+ /*   160 */   116,   75,   74,   73,   72,   41,   13,   66,    7,  160,
+ /*   170 */    67,   93,   24,   42,   35,   78,  173,  173,   88,   34,
+ /*   180 */    28,   84,   45,   40,   42,  173,   20,  173,   15,   69,
+ /*   190 */    37,  173,  173,  173,   59,  173,   75,   74,   73,   72,
+ /*   200 */    41,   42,  173,  173,  173,  173,   88,   34,   28,   84,
+ /*   210 */    45,   41,  173,  173,   20,  173,   15,  173,   37,  173,
+ /*   220 */   173,  173,   68,  173,   75,   74,   73,   72,   41,  173,
+ /*   230 */   173,  173,   85,   42,  173,  173,  173,  173,   88,   30,
+ /*   240 */    28,   84,   45,  173,  173,  173,   20,  173,   15,  173,
+ /*   250 */    37,  173,  173,  173,  173,  173,   75,   74,   73,   72,
+ /*   260 */    41,   42,  173,  173,  173,  173,   88,   17,   28,   84,
+ /*   270 */    45,  173,  173,  173,   20,  173,   15,   42,   37,  173,
+ /*   280 */   173,   48,   44,  173,   75,   74,   73,   72,   41,  173,
+ /*   290 */   173,  173,  173,   42,  173,  173,  173,  173,   88,   27,
+ /*   300 */    28,   84,   45,  173,   41,  173,   20,  173,   15,  173,
+ /*   310 */    37,  173,  173,  173,  173,  173,   75,   74,   73,   72,
+ /*   320 */    41,   42,  173,  173,  173,  173,   88,  173,   28,   84,
+ /*   330 */    45,  173,  173,  173,   20,  173,   15,  173,   31,  173,
+ /*   340 */   173,  173,  173,  173,   75,   74,   73,   72,   41,  173,
+ /*   350 */   173,  173,  173,   42,  173,  173,  173,  173,   88,  173,
+ /*   360 */    28,   84,   45,  173,  173,  173,   20,  173,   14,  173,
+ /*   370 */   173,  173,  173,  173,  173,  173,   75,   74,   73,   72,
+ /*   380 */    41,   42,  173,  173,  173,  173,   88,  173,   28,   84,
+ /*   390 */    45,   42,  173,  173,   19,  173,   88,  173,   29,   84,
+ /*   400 */    45,  173,  173,  173,   75,   74,   73,   72,   41,  173,
+ /*   410 */   173,  173,  173,  173,   75,   74,   73,   72,   41,
+    );
+    static public $yy_lookahead = array(
+ /*     0 */     7,    6,   70,   10,   11,   12,   13,   73,   15,   16,
+ /*    10 */    17,    8,    9,   18,   56,   83,   84,   54,   23,   24,
+ /*    20 */    25,   26,   27,   28,   29,   30,   33,   34,   35,   36,
+ /*    30 */    37,   38,   39,   40,   41,   42,   43,   44,   45,   46,
+ /*    40 */    47,   48,   49,    1,   81,   54,   54,   68,   62,    7,
+ /*    50 */    58,   59,   62,   11,   12,   13,    3,   15,   16,   17,
+ /*    60 */    74,   82,    8,   77,   78,   79,   80,   77,   78,   79,
+ /*    70 */    80,    7,   81,   81,   20,   33,   34,   35,   36,   37,
+ /*    80 */    38,   39,   40,   41,   42,   43,   44,   45,   46,   47,
+ /*    90 */    48,   49,    7,   55,   66,   57,   11,   12,   13,   56,
+ /*   100 */    15,   16,   17,   11,   12,   13,    8,    9,   31,   32,
+ /*   110 */    20,   55,   20,   57,    2,   54,    6,    5,   33,   34,
+ /*   120 */    35,   36,   37,   38,   39,   40,   41,   42,   43,   44,
+ /*   130 */    45,   46,   47,   48,   49,   51,   52,   53,   54,    2,
+ /*   140 */    54,    4,   81,   59,   60,   61,   62,   63,   55,   73,
+ /*   150 */    57,   67,   76,   69,   14,   71,    7,   16,   17,   21,
+ /*   160 */    22,   77,   78,   79,   80,   81,    5,   81,   72,   14,
+ /*   170 */    19,   15,    4,   54,   65,   81,   85,   85,   59,   60,
+ /*   180 */    61,   62,   63,   64,   54,   85,   67,   85,   69,   59,
+ /*   190 */    71,   85,   85,   85,   75,   85,   77,   78,   79,   80,
+ /*   200 */    81,   54,   85,   85,   85,   85,   59,   60,   61,   62,
+ /*   210 */    63,   81,   85,   85,   67,   85,   69,   85,   71,   85,
+ /*   220 */    85,   85,   75,   85,   77,   78,   79,   80,   81,   85,
+ /*   230 */    85,   85,   53,   54,   85,   85,   85,   85,   59,   60,
+ /*   240 */    61,   62,   63,   85,   85,   85,   67,   85,   69,   85,
+ /*   250 */    71,   85,   85,   85,   85,   85,   77,   78,   79,   80,
+ /*   260 */    81,   54,   85,   85,   85,   85,   59,   60,   61,   62,
+ /*   270 */    63,   85,   85,   85,   67,   85,   69,   54,   71,   85,
+ /*   280 */    85,   58,   59,   85,   77,   78,   79,   80,   81,   85,
+ /*   290 */    85,   85,   85,   54,   85,   85,   85,   85,   59,   60,
+ /*   300 */    61,   62,   63,   85,   81,   85,   67,   85,   69,   85,
+ /*   310 */    71,   85,   85,   85,   85,   85,   77,   78,   79,   80,
+ /*   320 */    81,   54,   85,   85,   85,   85,   59,   85,   61,   62,
+ /*   330 */    63,   85,   85,   85,   67,   85,   69,   85,   71,   85,
+ /*   340 */    85,   85,   85,   85,   77,   78,   79,   80,   81,   85,
+ /*   350 */    85,   85,   85,   54,   85,   85,   85,   85,   59,   85,
+ /*   360 */    61,   62,   63,   85,   85,   85,   67,   85,   69,   85,
+ /*   370 */    85,   85,   85,   85,   85,   85,   77,   78,   79,   80,
+ /*   380 */    81,   54,   85,   85,   85,   85,   59,   85,   61,   62,
+ /*   390 */    63,   54,   85,   85,   67,   85,   59,   85,   61,   62,
+ /*   400 */    63,   85,   85,   85,   77,   78,   79,   80,   81,   85,
+ /*   410 */    85,   85,   85,   85,   77,   78,   79,   80,   81,
+);
+    const YY_SHIFT_USE_DFLT = -8;
+    const YY_SHIFT_MAX = 45;
+    static public $yy_shift_ofst = array(
+ /*     0 */    42,   -7,   -7,   85,   85,   85,   85,   85,   85,   85,
+ /*    10 */   141,  141,  156,  156,   -5,   -5,  156,   92,  137,  138,
+ /*    20 */   138,  156,  168,  156,  156,  168,  156,   54,   77,   77,
+ /*    30 */    90,  151,  156,   53,   90,   64,   53,  151,  112,   98,
+ /*    40 */     3,  155,  140,  161,  110,  149,
+);
+    const YY_REDUCE_USE_DFLT = -69;
+    const YY_REDUCE_MAX = 37;
+    static public $yy_reduce_ofst = array(
+ /*     0 */    84,  119,  147,  179,  207,  239,  267,  299,  327,  337,
+ /*    10 */   -14,  -10,  223,   -8,  -68,  -68,  130,   76,   93,  -21,
+ /*    20 */   -21,   86,   38,  -37,   -9,   56,   61,  -66,  109,  109,
+ /*    30 */   -66,   96,   94,   43,  -66,   28,  -42,   96,
+);
+    static public $yyExpectedTokens = array(
+        /* 0 */ array(1, 7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 1 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 2 */ array(7, 10, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 3 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 4 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 5 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 6 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 7 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 8 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 9 */ array(7, 11, 12, 13, 15, 16, 17, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ),
+        /* 10 */ array(16, 17, ),
+        /* 11 */ array(16, 17, ),
+        /* 12 */ array(15, ),
+        /* 13 */ array(15, ),
+        /* 14 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ),
+        /* 15 */ array(6, 18, 23, 24, 25, 26, 27, 28, 29, 30, ),
+        /* 16 */ array(15, ),
+        /* 17 */ array(11, 12, 13, 20, ),
+        /* 18 */ array(2, 4, ),
+        /* 19 */ array(21, 22, ),
+        /* 20 */ array(21, 22, ),
+        /* 21 */ array(15, ),
+        /* 22 */ array(4, ),
+        /* 23 */ array(15, ),
+        /* 24 */ array(15, ),
+        /* 25 */ array(4, ),
+        /* 26 */ array(15, ),
+        /* 27 */ array(8, 20, ),
+        /* 28 */ array(31, 32, ),
+        /* 29 */ array(31, 32, ),
+        /* 30 */ array(20, ),
+        /* 31 */ array(19, ),
+        /* 32 */ array(15, ),
+        /* 33 */ array(3, ),
+        /* 34 */ array(20, ),
+        /* 35 */ array(7, ),
+        /* 36 */ array(3, ),
+        /* 37 */ array(19, ),
+        /* 38 */ array(2, 5, ),
+        /* 39 */ array(8, 9, ),
+        /* 40 */ array(8, 9, ),
+        /* 41 */ array(14, ),
+        /* 42 */ array(14, ),
+        /* 43 */ array(5, ),
+        /* 44 */ array(6, ),
+        /* 45 */ array(7, ),
+        /* 46 */ array(),
+        /* 47 */ array(),
+        /* 48 */ array(),
+        /* 49 */ array(),
+        /* 50 */ array(),
+        /* 51 */ array(),
+        /* 52 */ array(),
+        /* 53 */ array(),
+        /* 54 */ array(),
+        /* 55 */ array(),
+        /* 56 */ array(),
+        /* 57 */ array(),
+        /* 58 */ array(),
+        /* 59 */ array(),
+        /* 60 */ array(),
+        /* 61 */ array(),
+        /* 62 */ array(),
+        /* 63 */ array(),
+        /* 64 */ array(),
+        /* 65 */ array(),
+        /* 66 */ array(),
+        /* 67 */ array(),
+        /* 68 */ array(),
+        /* 69 */ array(),
+        /* 70 */ array(),
+        /* 71 */ array(),
+        /* 72 */ array(),
+        /* 73 */ array(),
+        /* 74 */ array(),
+        /* 75 */ array(),
+        /* 76 */ array(),
+        /* 77 */ array(),
+        /* 78 */ array(),
+        /* 79 */ array(),
+        /* 80 */ array(),
+        /* 81 */ array(),
+        /* 82 */ array(),
+        /* 83 */ array(),
+        /* 84 */ array(),
+        /* 85 */ array(),
+        /* 86 */ array(),
+        /* 87 */ array(),
+        /* 88 */ array(),
+        /* 89 */ array(),
+        /* 90 */ array(),
+        /* 91 */ array(),
+        /* 92 */ array(),
+        /* 93 */ array(),
+        /* 94 */ array(),
+        /* 95 */ array(),
+        /* 96 */ array(),
+        /* 97 */ array(),
+        /* 98 */ array(),
+        /* 99 */ array(),
+        /* 100 */ array(),
+        /* 101 */ array(),
+        /* 102 */ array(),
+        /* 103 */ array(),
+        /* 104 */ array(),
+        /* 105 */ array(),
+        /* 106 */ array(),
+        /* 107 */ array(),
+        /* 108 */ array(),
+        /* 109 */ array(),
+        /* 110 */ array(),
+        /* 111 */ array(),
+        /* 112 */ array(),
+        /* 113 */ array(),
+        /* 114 */ array(),
+        /* 115 */ array(),
+        /* 116 */ array(),
+);
+    static public $yy_default = array(
+ /*     0 */   203,  146,  203,  203,  203,  203,  203,  203,  203,  203,
+ /*    10 */   203,  203,  203,  203,  140,  139,  203,  203,  125,  138,
+ /*    20 */   137,  203,  124,  203,  203,  125,  203,  203,  135,  136,
+ /*    30 */   129,  142,  203,  122,  149,  203,  122,  141,  203,  203,
+ /*    40 */   203,  158,  203,  203,  203,  203,  176,  177,  127,  178,
+ /*    50 */   165,  175,  173,  168,  166,  174,  179,  167,  150,  147,
+ /*    60 */   153,  120,  126,  123,  152,  151,  160,  169,  148,  128,
+ /*    70 */   180,  194,  157,  156,  155,  154,  162,  163,  159,  182,
+ /*    80 */   181,  144,  145,  143,  130,  121,  119,  118,  131,  132,
+ /*    90 */   134,  170,  133,  161,  183,  198,  197,  196,  195,  199,
+ /*   100 */   200,  171,  164,  202,  201,  117,  193,  187,  186,  185,
+ /*   110 */   184,  188,  189,  192,  191,  190,  172,
+);
+/* The next thing included is series of defines which control
+** various aspects of the generated parser.
+**    self::YYNOCODE      is a number which corresponds
+**                        to no legal terminal or nonterminal number.  This
+**                        number is used to fill in empty slots of the hash 
+**                        table.
+**    self::YYFALLBACK    If defined, this indicates that one or more tokens
+**                        have fall-back values which should be used if the
+**                        original value of the token will not parse.
+**    self::YYSTACKDEPTH  is the maximum depth of the parser's stack.
+**    self::YYNSTATE      the combined number of states.
+**    self::YYNRULE       the number of rules in the grammar
+**    self::YYERRORSYMBOL is the code number of the error symbol.  If not
+**                        defined, then do no error processing.
+*/
+    const YYNOCODE = 86;
+    const YYSTACKDEPTH = 100;
+    const YYNSTATE = 117;
+    const YYNRULE = 86;
+    const YYERRORSYMBOL = 50;
+    const YYERRSYMDT = 'yy0';
+    const YYFALLBACK = 0;
+    /** The next table maps tokens into fallback tokens.  If a construct
+     * like the following:
+     * 
+     *      %fallback ID X Y Z.
+     *
+     * appears in the grammer, then ID becomes a fallback token for X, Y,
+     * and Z.  Whenever one of the tokens X, Y, or Z is input to the parser
+     * but it does not parse, the type of the token is changed to ID and
+     * the parse is retried before an error is thrown.
+     */
+    static public $yyFallback = array(
+    );
+    /**
+     * Turn parser tracing on by giving a stream to which to write the trace
+     * and a prompt to preface each trace message.  Tracing is turned off
+     * by making either argument NULL 
+     *
+     * Inputs:
+     * 
+     * - A stream resource to which trace output should be written.
+     *   If NULL, then tracing is turned off.
+     * - A prefix string written at the beginning of every
+     *   line of trace output.  If NULL, then tracing is
+     *   turned off.
+     *
+     * Outputs:
+     * 
+     * - None.
+     * @param resource
+     * @param string
+     */
+    static function Trace($TraceFILE, $zTracePrompt)
+    {
+        if (!$TraceFILE) {
+            $zTracePrompt = 0;
+        } elseif (!$zTracePrompt) {
+            $TraceFILE = 0;
+        }
+        self::$yyTraceFILE = $TraceFILE;
+        self::$yyTracePrompt = $zTracePrompt;
+    }
+
+    /**
+     * Output debug information to output (php://output stream)
+     */
+    static function PrintTrace()
+    {
+        self::$yyTraceFILE = fopen('php://output', 'w');
+        self::$yyTracePrompt = '';
+    }
+
+    /**
+     * @var resource|0
+     */
+    static public $yyTraceFILE;
+    /**
+     * String to prepend to debug output
+     * @var string|0
+     */
+    static public $yyTracePrompt;
+    /**
+     * @var int
+     */
+    public $yyidx;                    /* Index of top element in stack */
+    /**
+     * @var int
+     */
+    public $yyerrcnt;                 /* Shifts left before out of the error */
+    /**
+     * @var array
+     */
+    public $yystack = array();  /* The parser's stack */
+
+    /**
+     * For tracing shifts, the names of all terminals and nonterminals
+     * are required.  The following table supplies these names
+     * @var array
+     */
+    static public $yyTokenName = array( 
+  '$',             'SELECT',        'AS_ALIAS',      'WHERE',       
+  'JOIN',          'ON',            'EQ',            'PAR_OPEN',    
+  'PAR_CLOSE',     'COMA',          'INTERVAL',      'F_DAY',       
+  'F_MONTH',       'F_YEAR',        'DOT',           'NAME',        
+  'NUMVAL',        'STRVAL',        'NOT_EQ',        'LOG_AND',     
+  'LOG_OR',        'MATH_DIV',      'MATH_MULT',     'MATH_PLUS',   
+  'MATH_MINUS',    'GT',            'LT',            'GE',          
+  'LE',            'LIKE',          'NOT_LIKE',      'IN',          
+  'NOT_IN',        'F_IF',          'F_ELT',         'F_COALESCE',  
+  'F_CONCAT',      'F_SUBSTR',      'F_TRIM',        'F_DATE',      
+  'F_DATE_FORMAT',  'F_CURRENT_DATE',  'F_NOW',         'F_TIME',      
+  'F_TO_DAYS',     'F_FROM_DAYS',   'F_DATE_ADD',    'F_DATE_SUB',  
+  'F_ROUND',       'F_FLOOR',       'error',         'result',      
+  'query',         'condition',     'class_name',    'join_statement',
+  'where_statement',  'join_item',     'join_condition',  'field_id',    
+  'expression_prio4',  'expression_basic',  'scalar',        'func_name',   
+  'arg_list',      'list_operator',  'list',          'expression_prio1',
+  'operator1',     'expression_prio2',  'operator2',     'expression_prio3',
+  'operator3',     'operator4',     'scalar_list',   'argument',    
+  'interval_unit',  'num_scalar',    'str_scalar',    'num_value',   
+  'str_value',     'name',          'num_operator1',  'num_operator2',
+  'str_operator',
+    );
+
+    /**
+     * For tracing reduce actions, the names of all rules are required.
+     * @var array
+     */
+    static public $yyRuleName = array(
+ /*   0 */ "result ::= query",
+ /*   1 */ "result ::= condition",
+ /*   2 */ "query ::= SELECT class_name join_statement where_statement",
+ /*   3 */ "query ::= SELECT class_name AS_ALIAS class_name join_statement where_statement",
+ /*   4 */ "where_statement ::= WHERE condition",
+ /*   5 */ "where_statement ::=",
+ /*   6 */ "join_statement ::= join_item join_statement",
+ /*   7 */ "join_statement ::= join_item",
+ /*   8 */ "join_statement ::=",
+ /*   9 */ "join_item ::= JOIN class_name AS_ALIAS class_name ON join_condition",
+ /*  10 */ "join_item ::= JOIN class_name ON join_condition",
+ /*  11 */ "join_condition ::= field_id EQ field_id",
+ /*  12 */ "condition ::= expression_prio4",
+ /*  13 */ "expression_basic ::= scalar",
+ /*  14 */ "expression_basic ::= field_id",
+ /*  15 */ "expression_basic ::= func_name PAR_OPEN arg_list PAR_CLOSE",
+ /*  16 */ "expression_basic ::= PAR_OPEN expression_prio4 PAR_CLOSE",
+ /*  17 */ "expression_basic ::= expression_basic list_operator list",
+ /*  18 */ "expression_prio1 ::= expression_basic",
+ /*  19 */ "expression_prio1 ::= expression_prio1 operator1 expression_basic",
+ /*  20 */ "expression_prio2 ::= expression_prio1",
+ /*  21 */ "expression_prio2 ::= expression_prio2 operator2 expression_prio1",
+ /*  22 */ "expression_prio3 ::= expression_prio2",
+ /*  23 */ "expression_prio3 ::= expression_prio3 operator3 expression_prio2",
+ /*  24 */ "expression_prio4 ::= expression_prio3",
+ /*  25 */ "expression_prio4 ::= expression_prio4 operator4 expression_prio3",
+ /*  26 */ "list ::= PAR_OPEN scalar_list PAR_CLOSE",
+ /*  27 */ "scalar_list ::= scalar",
+ /*  28 */ "scalar_list ::= scalar_list COMA scalar",
+ /*  29 */ "arg_list ::=",
+ /*  30 */ "arg_list ::= argument",
+ /*  31 */ "arg_list ::= arg_list COMA argument",
+ /*  32 */ "argument ::= expression_prio4",
+ /*  33 */ "argument ::= INTERVAL expression_prio4 interval_unit",
+ /*  34 */ "interval_unit ::= F_DAY",
+ /*  35 */ "interval_unit ::= F_MONTH",
+ /*  36 */ "interval_unit ::= F_YEAR",
+ /*  37 */ "scalar ::= num_scalar",
+ /*  38 */ "scalar ::= str_scalar",
+ /*  39 */ "num_scalar ::= num_value",
+ /*  40 */ "str_scalar ::= str_value",
+ /*  41 */ "field_id ::= name",
+ /*  42 */ "field_id ::= class_name DOT name",
+ /*  43 */ "class_name ::= name",
+ /*  44 */ "name ::= NAME",
+ /*  45 */ "num_value ::= NUMVAL",
+ /*  46 */ "str_value ::= STRVAL",
+ /*  47 */ "operator1 ::= num_operator1",
+ /*  48 */ "operator2 ::= num_operator2",
+ /*  49 */ "operator2 ::= str_operator",
+ /*  50 */ "operator2 ::= EQ",
+ /*  51 */ "operator2 ::= NOT_EQ",
+ /*  52 */ "operator3 ::= LOG_AND",
+ /*  53 */ "operator4 ::= LOG_OR",
+ /*  54 */ "num_operator1 ::= MATH_DIV",
+ /*  55 */ "num_operator1 ::= MATH_MULT",
+ /*  56 */ "num_operator2 ::= MATH_PLUS",
+ /*  57 */ "num_operator2 ::= MATH_MINUS",
+ /*  58 */ "num_operator2 ::= GT",
+ /*  59 */ "num_operator2 ::= LT",
+ /*  60 */ "num_operator2 ::= GE",
+ /*  61 */ "num_operator2 ::= LE",
+ /*  62 */ "str_operator ::= LIKE",
+ /*  63 */ "str_operator ::= NOT_LIKE",
+ /*  64 */ "list_operator ::= IN",
+ /*  65 */ "list_operator ::= NOT_IN",
+ /*  66 */ "func_name ::= F_IF",
+ /*  67 */ "func_name ::= F_ELT",
+ /*  68 */ "func_name ::= F_COALESCE",
+ /*  69 */ "func_name ::= F_CONCAT",
+ /*  70 */ "func_name ::= F_SUBSTR",
+ /*  71 */ "func_name ::= F_TRIM",
+ /*  72 */ "func_name ::= F_DATE",
+ /*  73 */ "func_name ::= F_DATE_FORMAT",
+ /*  74 */ "func_name ::= F_CURRENT_DATE",
+ /*  75 */ "func_name ::= F_NOW",
+ /*  76 */ "func_name ::= F_TIME",
+ /*  77 */ "func_name ::= F_TO_DAYS",
+ /*  78 */ "func_name ::= F_FROM_DAYS",
+ /*  79 */ "func_name ::= F_YEAR",
+ /*  80 */ "func_name ::= F_MONTH",
+ /*  81 */ "func_name ::= F_DAY",
+ /*  82 */ "func_name ::= F_DATE_ADD",
+ /*  83 */ "func_name ::= F_DATE_SUB",
+ /*  84 */ "func_name ::= F_ROUND",
+ /*  85 */ "func_name ::= F_FLOOR",
+    );
+
+    /**
+     * This function returns the symbolic name associated with a token
+     * value.
+     * @param int
+     * @return string
+     */
+    function tokenName($tokenType)
+    {
+        if ($tokenType === 0) {
+            return 'End of Input';
+        }
+        if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) {
+            return self::$yyTokenName[$tokenType];
+        } else {
+            return "Unknown";
+        }
+    }
+
+    /**
+     * The following function deletes the value associated with a
+     * symbol.  The symbol can be either a terminal or nonterminal.
+     * @param int the symbol code
+     * @param mixed the symbol's value
+     */
+    static function yy_destructor($yymajor, $yypminor)
+    {
+        switch ($yymajor) {
+        /* Here is inserted the actions which take place when a
+        ** terminal or non-terminal is destroyed.  This can happen
+        ** when the symbol is popped from the stack during a
+        ** reduce or during error processing or when a parser is 
+        ** being destroyed before it is finished parsing.
+        **
+        ** Note: during a reduce, the only symbols destroyed are those
+        ** which appear on the RHS of the rule, but which are not used
+        ** inside the C code.
+        */
+            default:  break;   /* If no destructor action specified: do nothing */
+        }
+    }
+
+    /**
+     * Pop the parser's stack once.
+     *
+     * If there is a destructor routine associated with the token which
+     * is popped from the stack, then call it.
+     *
+     * Return the major token number for the symbol popped.
+     * @param OQLParser_yyParser
+     * @return int
+     */
+    function yy_pop_parser_stack()
+    {
+        if (!count($this->yystack)) {
+            return;
+        }
+        $yytos = array_pop($this->yystack);
+        if (self::$yyTraceFILE && $this->yyidx >= 0) {
+            fwrite(self::$yyTraceFILE,
+                self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] .
+                    "\n");
+        }
+        $yymajor = $yytos->major;
+        self::yy_destructor($yymajor, $yytos->minor);
+        $this->yyidx--;
+        return $yymajor;
+    }
+
+    /**
+     * Deallocate and destroy a parser.  Destructors are all called for
+     * all stack elements before shutting the parser down.
+     */
+    function __destruct()
+    {
+        while ($this->yyidx >= 0) {
+            $this->yy_pop_parser_stack();
+        }
+        if (is_resource(self::$yyTraceFILE)) {
+            fclose(self::$yyTraceFILE);
+        }
+    }
+
+    /**
+     * Based on the current state and parser stack, get a list of all
+     * possible lookahead tokens
+     * @param int
+     * @return array
+     */
+    function yy_get_expected_tokens($token)
+    {
+        $state = $this->yystack[$this->yyidx]->stateno;
+        $expected = self::$yyExpectedTokens[$state];
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return $expected;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return array_unique($expected);
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate])) {
+                        $expected += self::$yyExpectedTokens[$nextstate];
+                            if (in_array($token,
+                                  self::$yyExpectedTokens[$nextstate], true)) {
+                            $this->yyidx = $yyidx;
+                            $this->yystack = $stack;
+                            return array_unique($expected);
+                        }
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new OQLParser_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return array_unique($expected);
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return $expected;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+        return array_unique($expected);
+    }
+
+    /**
+     * Based on the parser state and current parser stack, determine whether
+     * the lookahead token is possible.
+     * 
+     * The parser will convert the token value to an error token if not.  This
+     * catches some unusual edge cases where the parser would fail.
+     * @param int
+     * @return bool
+     */
+    function yy_is_expected_token($token)
+    {
+        if ($token === 0) {
+            return true; // 0 is not part of this
+        }
+        $state = $this->yystack[$this->yyidx]->stateno;
+        if (in_array($token, self::$yyExpectedTokens[$state], true)) {
+            return true;
+        }
+        $stack = $this->yystack;
+        $yyidx = $this->yyidx;
+        do {
+            $yyact = $this->yy_find_shift_action($token);
+            if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
+                // reduce action
+                $done = 0;
+                do {
+                    if ($done++ == 100) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // too much recursion prevents proper detection
+                        // so give up
+                        return true;
+                    }
+                    $yyruleno = $yyact - self::YYNSTATE;
+                    $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
+                    $nextstate = $this->yy_find_reduce_action(
+                        $this->yystack[$this->yyidx]->stateno,
+                        self::$yyRuleInfo[$yyruleno]['lhs']);
+                    if (isset(self::$yyExpectedTokens[$nextstate]) &&
+                          in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        return true;
+                    }
+                    if ($nextstate < self::YYNSTATE) {
+                        // we need to shift a non-terminal
+                        $this->yyidx++;
+                        $x = new OQLParser_yyStackEntry;
+                        $x->stateno = $nextstate;
+                        $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
+                        $this->yystack[$this->yyidx] = $x;
+                        continue 2;
+                    } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        if (!$token) {
+                            // end of input: this is valid
+                            return true;
+                        }
+                        // the last token was just ignored, we can't accept
+                        // by ignoring input, this is in essence ignoring a
+                        // syntax error!
+                        return false;
+                    } elseif ($nextstate === self::YY_NO_ACTION) {
+                        $this->yyidx = $yyidx;
+                        $this->yystack = $stack;
+                        // input accepted, but not shifted (I guess)
+                        return true;
+                    } else {
+                        $yyact = $nextstate;
+                    }
+                } while (true);
+            }
+            break;
+        } while (true);
+        $this->yyidx = $yyidx;
+        $this->yystack = $stack;
+        return true;
+    }
+
+    /**
+     * Find the appropriate action for a parser given the terminal
+     * look-ahead token iLookAhead.
+     *
+     * If the look-ahead token is YYNOCODE, then check to see if the action is
+     * independent of the look-ahead.  If it is, return the action, otherwise
+     * return YY_NO_ACTION.
+     * @param int The look-ahead token
+     */
+    function yy_find_shift_action($iLookAhead)
+    {
+        $stateno = $this->yystack[$this->yyidx]->stateno;
+     
+        /* if ($this->yyidx < 0) return self::YY_NO_ACTION;  */
+        if (!isset(self::$yy_shift_ofst[$stateno])) {
+            // no shift actions
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_shift_ofst[$stateno];
+        if ($i === self::YY_SHIFT_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
+                   && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
+                if (self::$yyTraceFILE) {
+                    fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
+                        self::$yyTokenName[$iLookAhead] . " => " .
+                        self::$yyTokenName[$iFallback] . "\n");
+                }
+                return $this->yy_find_shift_action($iFallback);
+            }
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    /**
+     * Find the appropriate action for a parser given the non-terminal
+     * look-ahead token $iLookAhead.
+     *
+     * If the look-ahead token is self::YYNOCODE, then check to see if the action is
+     * independent of the look-ahead.  If it is, return the action, otherwise
+     * return self::YY_NO_ACTION.
+     * @param int Current state number
+     * @param int The look-ahead token
+     */
+    function yy_find_reduce_action($stateno, $iLookAhead)
+    {
+        /* $stateno = $this->yystack[$this->yyidx]->stateno; */
+
+        if (!isset(self::$yy_reduce_ofst[$stateno])) {
+            return self::$yy_default[$stateno];
+        }
+        $i = self::$yy_reduce_ofst[$stateno];
+        if ($i == self::YY_REDUCE_USE_DFLT) {
+            return self::$yy_default[$stateno];
+        }
+        if ($iLookAhead == self::YYNOCODE) {
+            return self::YY_NO_ACTION;
+        }
+        $i += $iLookAhead;
+        if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
+              self::$yy_lookahead[$i] != $iLookAhead) {
+            return self::$yy_default[$stateno];
+        } else {
+            return self::$yy_action[$i];
+        }
+    }
+
+    /**
+     * Perform a shift action.
+     * @param int The new state to shift in
+     * @param int The major token to shift in
+     * @param mixed the minor token to shift in
+     */
+    function yy_shift($yyNewState, $yyMajor, $yypMinor)
+    {
+        $this->yyidx++;
+        if ($this->yyidx >= self::YYSTACKDEPTH) {
+            $this->yyidx--;
+            if (self::$yyTraceFILE) {
+                fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
+            }
+            while ($this->yyidx >= 0) {
+                $this->yy_pop_parser_stack();
+            }
+            /* Here code is inserted which will execute if the parser
+            ** stack ever overflows */
+            return;
+        }
+        $yytos = new OQLParser_yyStackEntry;
+        $yytos->stateno = $yyNewState;
+        $yytos->major = $yyMajor;
+        $yytos->minor = $yypMinor;
+        array_push($this->yystack, $yytos);
+        if (self::$yyTraceFILE && $this->yyidx > 0) {
+            fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
+                $yyNewState);
+            fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
+            for($i = 1; $i <= $this->yyidx; $i++) {
+                fprintf(self::$yyTraceFILE, " %s",
+                    self::$yyTokenName[$this->yystack[$i]->major]);
+            }
+            fwrite(self::$yyTraceFILE,"\n");
+        }
+    }
+
+    /**
+     * The following table contains information about every rule that
+     * is used during the reduce.
+     *
+     * <pre>
+     * array(
+     *  array(
+     *   int $lhs;         Symbol on the left-hand side of the rule
+     *   int $nrhs;     Number of right-hand side symbols in the rule
+     *  ),...
+     * );
+     * </pre>
+     */
+    static public $yyRuleInfo = array(
+  array( 'lhs' => 51, 'rhs' => 1 ),
+  array( 'lhs' => 51, 'rhs' => 1 ),
+  array( 'lhs' => 52, 'rhs' => 4 ),
+  array( 'lhs' => 52, 'rhs' => 6 ),
+  array( 'lhs' => 56, 'rhs' => 2 ),
+  array( 'lhs' => 56, 'rhs' => 0 ),
+  array( 'lhs' => 55, 'rhs' => 2 ),
+  array( 'lhs' => 55, 'rhs' => 1 ),
+  array( 'lhs' => 55, 'rhs' => 0 ),
+  array( 'lhs' => 57, 'rhs' => 6 ),
+  array( 'lhs' => 57, 'rhs' => 4 ),
+  array( 'lhs' => 58, 'rhs' => 3 ),
+  array( 'lhs' => 53, 'rhs' => 1 ),
+  array( 'lhs' => 61, 'rhs' => 1 ),
+  array( 'lhs' => 61, 'rhs' => 1 ),
+  array( 'lhs' => 61, 'rhs' => 4 ),
+  array( 'lhs' => 61, 'rhs' => 3 ),
+  array( 'lhs' => 61, 'rhs' => 3 ),
+  array( 'lhs' => 67, 'rhs' => 1 ),
+  array( 'lhs' => 67, 'rhs' => 3 ),
+  array( 'lhs' => 69, 'rhs' => 1 ),
+  array( 'lhs' => 69, 'rhs' => 3 ),
+  array( 'lhs' => 71, 'rhs' => 1 ),
+  array( 'lhs' => 71, 'rhs' => 3 ),
+  array( 'lhs' => 60, 'rhs' => 1 ),
+  array( 'lhs' => 60, 'rhs' => 3 ),
+  array( 'lhs' => 66, 'rhs' => 3 ),
+  array( 'lhs' => 74, 'rhs' => 1 ),
+  array( 'lhs' => 74, 'rhs' => 3 ),
+  array( 'lhs' => 64, 'rhs' => 0 ),
+  array( 'lhs' => 64, 'rhs' => 1 ),
+  array( 'lhs' => 64, 'rhs' => 3 ),
+  array( 'lhs' => 75, 'rhs' => 1 ),
+  array( 'lhs' => 75, 'rhs' => 3 ),
+  array( 'lhs' => 76, 'rhs' => 1 ),
+  array( 'lhs' => 76, 'rhs' => 1 ),
+  array( 'lhs' => 76, 'rhs' => 1 ),
+  array( 'lhs' => 62, 'rhs' => 1 ),
+  array( 'lhs' => 62, 'rhs' => 1 ),
+  array( 'lhs' => 77, 'rhs' => 1 ),
+  array( 'lhs' => 78, 'rhs' => 1 ),
+  array( 'lhs' => 59, 'rhs' => 1 ),
+  array( 'lhs' => 59, 'rhs' => 3 ),
+  array( 'lhs' => 54, 'rhs' => 1 ),
+  array( 'lhs' => 81, 'rhs' => 1 ),
+  array( 'lhs' => 79, 'rhs' => 1 ),
+  array( 'lhs' => 80, 'rhs' => 1 ),
+  array( 'lhs' => 68, 'rhs' => 1 ),
+  array( 'lhs' => 70, 'rhs' => 1 ),
+  array( 'lhs' => 70, 'rhs' => 1 ),
+  array( 'lhs' => 70, 'rhs' => 1 ),
+  array( 'lhs' => 70, 'rhs' => 1 ),
+  array( 'lhs' => 72, 'rhs' => 1 ),
+  array( 'lhs' => 73, 'rhs' => 1 ),
+  array( 'lhs' => 82, 'rhs' => 1 ),
+  array( 'lhs' => 82, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 83, 'rhs' => 1 ),
+  array( 'lhs' => 84, 'rhs' => 1 ),
+  array( 'lhs' => 84, 'rhs' => 1 ),
+  array( 'lhs' => 65, 'rhs' => 1 ),
+  array( 'lhs' => 65, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+  array( 'lhs' => 63, 'rhs' => 1 ),
+    );
+
+    /**
+     * The following table contains a mapping of reduce action to method name
+     * that handles the reduction.
+     * 
+     * If a rule is not set, it has no handler.
+     */
+    static public $yyReduceMap = array(
+        0 => 0,
+        1 => 0,
+        2 => 2,
+        3 => 3,
+        4 => 4,
+        5 => 5,
+        8 => 5,
+        6 => 6,
+        7 => 7,
+        9 => 9,
+        10 => 10,
+        11 => 11,
+        12 => 12,
+        13 => 12,
+        14 => 12,
+        18 => 12,
+        20 => 12,
+        22 => 12,
+        24 => 12,
+        32 => 12,
+        34 => 12,
+        35 => 12,
+        36 => 12,
+        37 => 12,
+        38 => 12,
+        15 => 15,
+        16 => 16,
+        17 => 17,
+        19 => 17,
+        21 => 17,
+        23 => 17,
+        25 => 17,
+        26 => 26,
+        27 => 27,
+        30 => 27,
+        28 => 28,
+        31 => 28,
+        29 => 29,
+        33 => 33,
+        39 => 39,
+        40 => 39,
+        41 => 41,
+        42 => 42,
+        43 => 43,
+        66 => 43,
+        67 => 43,
+        68 => 43,
+        69 => 43,
+        70 => 43,
+        71 => 43,
+        72 => 43,
+        73 => 43,
+        74 => 43,
+        75 => 43,
+        76 => 43,
+        77 => 43,
+        78 => 43,
+        79 => 43,
+        80 => 43,
+        81 => 43,
+        82 => 43,
+        83 => 43,
+        84 => 43,
+        85 => 43,
+        44 => 44,
+        45 => 45,
+        47 => 45,
+        48 => 45,
+        49 => 45,
+        50 => 45,
+        51 => 45,
+        52 => 45,
+        53 => 45,
+        54 => 45,
+        55 => 45,
+        56 => 45,
+        57 => 45,
+        58 => 45,
+        59 => 45,
+        60 => 45,
+        61 => 45,
+        62 => 45,
+        63 => 45,
+        64 => 45,
+        65 => 45,
+        46 => 46,
+    );
+    /* Beginning here are the reduction cases.  A typical example
+    ** follows:
+    **  #line <lineno> <grammarfile>
+    **   function yy_r0($yymsp){ ... }           // User supplied code
+    **  #line <lineno> <thisfile>
+    */
+#line 29 "oql-parser.y"
+    function yy_r0(){ $this->my_result = $this->yystack[$this->yyidx + 0]->minor;     }
+#line 1230 "oql-parser.php"
+#line 32 "oql-parser.y"
+    function yy_r2(){
+	$this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
+    }
+#line 1235 "oql-parser.php"
+#line 35 "oql-parser.y"
+    function yy_r3(){
+	$this->_retvalue = new OqlQuery($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
+    }
+#line 1240 "oql-parser.php"
+#line 39 "oql-parser.y"
+    function yy_r4(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;    }
+#line 1243 "oql-parser.php"
+#line 40 "oql-parser.y"
+    function yy_r5(){ $this->_retvalue = null;    }
+#line 1246 "oql-parser.php"
+#line 42 "oql-parser.y"
+    function yy_r6(){
+	// insert the join statement on top of the existing list
+	array_unshift($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -1]->minor);
+	// and return the updated array
+	$this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
+    }
+#line 1254 "oql-parser.php"
+#line 48 "oql-parser.y"
+    function yy_r7(){
+	$this->_retvalue = Array($this->yystack[$this->yyidx + 0]->minor);
+    }
+#line 1259 "oql-parser.php"
+#line 54 "oql-parser.y"
+    function yy_r9(){
+	// create an array with one single item
+	$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
+    }
+#line 1265 "oql-parser.php"
+#line 59 "oql-parser.y"
+    function yy_r10(){
+	// create an array with one single item
+	$this->_retvalue = new OqlJoinSpec($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
+    }
+#line 1271 "oql-parser.php"
+#line 64 "oql-parser.y"
+    function yy_r11(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, '=', $this->yystack[$this->yyidx + 0]->minor);     }
+#line 1274 "oql-parser.php"
+#line 66 "oql-parser.y"
+    function yy_r12(){ $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;     }
+#line 1277 "oql-parser.php"
+#line 70 "oql-parser.y"
+    function yy_r15(){ $this->_retvalue = new FunctionOqlExpression($this->yystack[$this->yyidx + -3]->minor, $this->yystack[$this->yyidx + -1]->minor);     }
+#line 1280 "oql-parser.php"
+#line 71 "oql-parser.y"
+    function yy_r16(){ $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;     }
+#line 1283 "oql-parser.php"
+#line 72 "oql-parser.y"
+    function yy_r17(){ $this->_retvalue = new BinaryOqlExpression($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);     }
+#line 1286 "oql-parser.php"
+#line 87 "oql-parser.y"
+    function yy_r26(){
+	$this->_retvalue = new ListOqlExpression($this->yystack[$this->yyidx + -1]->minor);
+    }
+#line 1291 "oql-parser.php"
+#line 90 "oql-parser.y"
+    function yy_r27(){
+	$this->_retvalue = array($this->yystack[$this->yyidx + 0]->minor);
+    }
+#line 1296 "oql-parser.php"
+#line 93 "oql-parser.y"
+    function yy_r28(){
+	array_push($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor);
+	$this->_retvalue = $this->yystack[$this->yyidx + -2]->minor;
+    }
+#line 1302 "oql-parser.php"
+#line 98 "oql-parser.y"
+    function yy_r29(){
+	$this->_retvalue = array();
+    }
+#line 1307 "oql-parser.php"
+#line 109 "oql-parser.y"
+    function yy_r33(){ $this->_retvalue = new IntervalOqlExpression($this->yystack[$this->yyidx + -1]->minor, $this->yystack[$this->yyidx + 0]->minor);     }
+#line 1310 "oql-parser.php"
+#line 118 "oql-parser.y"
+    function yy_r39(){ $this->_retvalue = new ScalarOqlExpression($this->yystack[$this->yyidx + 0]->minor);     }
+#line 1313 "oql-parser.php"
+#line 121 "oql-parser.y"
+    function yy_r41(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor);     }
+#line 1316 "oql-parser.php"
+#line 122 "oql-parser.y"
+    function yy_r42(){ $this->_retvalue = new FieldOqlExpression($this->yystack[$this->yyidx + 0]->minor, $this->yystack[$this->yyidx + -2]->minor);     }
+#line 1319 "oql-parser.php"
+#line 123 "oql-parser.y"
+    function yy_r43(){ $this->_retvalue=$this->yystack[$this->yyidx + 0]->minor;     }
+#line 1322 "oql-parser.php"
+#line 125 "oql-parser.y"
+    function yy_r44(){
+	if ($this->yystack[$this->yyidx + 0]->minor[0] == '`')
+	{
+		$name = substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2);
+	}
+	else
+	{
+		$name = $this->yystack[$this->yyidx + 0]->minor;
+	}
+	$this->_retvalue = new OqlName($name, $this->m_iColPrev);
+    }
+#line 1335 "oql-parser.php"
+#line 137 "oql-parser.y"
+    function yy_r45(){$this->_retvalue=$this->yystack[$this->yyidx + 0]->minor;    }
+#line 1338 "oql-parser.php"
+#line 138 "oql-parser.y"
+    function yy_r46(){$this->_retvalue=stripslashes(substr($this->yystack[$this->yyidx + 0]->minor, 1, strlen($this->yystack[$this->yyidx + 0]->minor) - 2));    }
+#line 1341 "oql-parser.php"
+
+    /**
+     * placeholder for the left hand side in a reduce operation.
+     * 
+     * For a parser with a rule like this:
+     * <pre>
+     * rule(A) ::= B. { A = 1; }
+     * </pre>
+     * 
+     * The parser will translate to something like:
+     * 
+     * <code>
+     * function yy_r0(){$this->_retvalue = 1;}
+     * </code>
+     */
+    private $_retvalue;
+
+    /**
+     * Perform a reduce action and the shift that must immediately
+     * follow the reduce.
+     * 
+     * For a rule such as:
+     * 
+     * <pre>
+     * A ::= B blah C. { dosomething(); }
+     * </pre>
+     * 
+     * This function will first call the action, if any, ("dosomething();" in our
+     * example), and then it will pop three states from the stack,
+     * one for each entry on the right-hand side of the expression
+     * (B, blah, and C in our example rule), and then push the result of the action
+     * back on to the stack with the resulting state reduced to (as described in the .out
+     * file)
+     * @param int Number of the rule by which to reduce
+     */
+    function yy_reduce($yyruleno)
+    {
+        //int $yygoto;                     /* The next state */
+        //int $yyact;                      /* The next action */
+        //mixed $yygotominor;        /* The LHS of the rule reduced */
+        //OQLParser_yyStackEntry $yymsp;            /* The top of the parser's stack */
+        //int $yysize;                     /* Amount to pop the stack */
+        $yymsp = $this->yystack[$this->yyidx];
+        if (self::$yyTraceFILE && $yyruleno >= 0 
+              && $yyruleno < count(self::$yyRuleName)) {
+            fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
+                self::$yyTracePrompt, $yyruleno,
+                self::$yyRuleName[$yyruleno]);
+        }
+
+        $this->_retvalue = $yy_lefthand_side = null;
+        if (array_key_exists($yyruleno, self::$yyReduceMap)) {
+            // call the action
+            $this->_retvalue = null;
+            $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
+            $yy_lefthand_side = $this->_retvalue;
+        }
+        $yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
+        $yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
+        $this->yyidx -= $yysize;
+        for($i = $yysize; $i; $i--) {
+            // pop all of the right-hand side parameters
+            array_pop($this->yystack);
+        }
+        $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
+        if ($yyact < self::YYNSTATE) {
+            /* If we are not debugging and the reduce action popped at least
+            ** one element off the stack, then we can push the new element back
+            ** onto the stack here, and skip the stack overflow test in yy_shift().
+            ** That gives a significant speed improvement. */
+            if (!self::$yyTraceFILE && $yysize) {
+                $this->yyidx++;
+                $x = new OQLParser_yyStackEntry;
+                $x->stateno = $yyact;
+                $x->major = $yygoto;
+                $x->minor = $yy_lefthand_side;
+                $this->yystack[$this->yyidx] = $x;
+            } else {
+                $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
+            }
+        } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
+            $this->yy_accept();
+        }
+    }
+
+    /**
+     * The following code executes when the parse fails
+     * 
+     * Code from %parse_fail is inserted here
+     */
+    function yy_parse_failed()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $this->yy_pop_parser_stack();
+        }
+        /* Here code is inserted which will be executed whenever the
+        ** parser fails */
+    }
+
+    /**
+     * The following code executes when a syntax error first occurs.
+     * 
+     * %syntax_error code is inserted here
+     * @param int The major type of the error token
+     * @param mixed The minor type of the error token
+     */
+    function yy_syntax_error($yymajor, $TOKEN)
+    {
+#line 25 "oql-parser.y"
+ 
+throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
+#line 1457 "oql-parser.php"
+    }
+
+    /**
+     * The following is executed when the parser accepts
+     * 
+     * %parse_accept code is inserted here
+     */
+    function yy_accept()
+    {
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
+        }
+        while ($this->yyidx >= 0) {
+            $stack = $this->yy_pop_parser_stack();
+        }
+        /* Here code is inserted which will be executed whenever the
+        ** parser accepts */
+    }
+
+    /**
+     * The main parser program.
+     * 
+     * The first argument is the major token number.  The second is
+     * the token value string as scanned from the input.
+     *
+     * @param int the token number
+     * @param mixed the token value
+     * @param mixed any extra arguments that should be passed to handlers
+     */
+    function doParse($yymajor, $yytokenvalue)
+    {
+//        $yyact;            /* The parser action. */
+//        $yyendofinput;     /* True if we are at the end of input */
+        $yyerrorhit = 0;   /* True if yymajor has invoked an error */
+        
+        /* (re)initialize the parser, if necessary */
+        if ($this->yyidx === null || $this->yyidx < 0) {
+            /* if ($yymajor == 0) return; // not sure why this was here... */
+            $this->yyidx = 0;
+            $this->yyerrcnt = -1;
+            $x = new OQLParser_yyStackEntry;
+            $x->stateno = 0;
+            $x->major = 0;
+            $this->yystack = array();
+            array_push($this->yystack, $x);
+        }
+        $yyendofinput = ($yymajor==0);
+        
+        if (self::$yyTraceFILE) {
+            fprintf(self::$yyTraceFILE, "%sInput %s\n",
+                self::$yyTracePrompt, self::$yyTokenName[$yymajor]);
+        }
+        
+        do {
+            $yyact = $this->yy_find_shift_action($yymajor);
+            if ($yymajor < self::YYERRORSYMBOL &&
+                  !$this->yy_is_expected_token($yymajor)) {
+                // force a syntax error
+                $yyact = self::YY_ERROR_ACTION;
+            }
+            if ($yyact < self::YYNSTATE) {
+                $this->yy_shift($yyact, $yymajor, $yytokenvalue);
+                $this->yyerrcnt--;
+                if ($yyendofinput && $this->yyidx >= 0) {
+                    $yymajor = 0;
+                } else {
+                    $yymajor = self::YYNOCODE;
+                }
+            } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
+                $this->yy_reduce($yyact - self::YYNSTATE);
+            } elseif ($yyact == self::YY_ERROR_ACTION) {
+                if (self::$yyTraceFILE) {
+                    fprintf(self::$yyTraceFILE, "%sSyntax Error!\n",
+                        self::$yyTracePrompt);
+                }
+                if (self::YYERRORSYMBOL) {
+                    /* A syntax error has occurred.
+                    ** The response to an error depends upon whether or not the
+                    ** grammar defines an error token "ERROR".  
+                    **
+                    ** This is what we do if the grammar does define ERROR:
+                    **
+                    **  * Call the %syntax_error function.
+                    **
+                    **  * Begin popping the stack until we enter a state where
+                    **    it is legal to shift the error symbol, then shift
+                    **    the error symbol.
+                    **
+                    **  * Set the error count to three.
+                    **
+                    **  * Begin accepting and shifting new tokens.  No new error
+                    **    processing will occur until three tokens have been
+                    **    shifted successfully.
+                    **
+                    */
+                    if ($this->yyerrcnt < 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $yymx = $this->yystack[$this->yyidx]->major;
+                    if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){
+                        if (self::$yyTraceFILE) {
+                            fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n",
+                                self::$yyTracePrompt, self::$yyTokenName[$yymajor]);
+                        }
+                        $this->yy_destructor($yymajor, $yytokenvalue);
+                        $yymajor = self::YYNOCODE;
+                    } else {
+                        while ($this->yyidx >= 0 &&
+                                 $yymx != self::YYERRORSYMBOL &&
+        ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
+                              ){
+                            $this->yy_pop_parser_stack();
+                        }
+                        if ($this->yyidx < 0 || $yymajor==0) {
+                            $this->yy_destructor($yymajor, $yytokenvalue);
+                            $this->yy_parse_failed();
+                            $yymajor = self::YYNOCODE;
+                        } elseif ($yymx != self::YYERRORSYMBOL) {
+                            $u2 = 0;
+                            $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
+                        }
+                    }
+                    $this->yyerrcnt = 3;
+                    $yyerrorhit = 1;
+                } else {
+                    /* YYERRORSYMBOL is not defined */
+                    /* This is what we do if the grammar does not define ERROR:
+                    **
+                    **  * Report an error message, and throw away the input token.
+                    **
+                    **  * If the input token is $, then fail the parse.
+                    **
+                    ** As before, subsequent error messages are suppressed until
+                    ** three input tokens have been successfully shifted.
+                    */
+                    if ($this->yyerrcnt <= 0) {
+                        $this->yy_syntax_error($yymajor, $yytokenvalue);
+                    }
+                    $this->yyerrcnt = 3;
+                    $this->yy_destructor($yymajor, $yytokenvalue);
+                    if ($yyendofinput) {
+                        $this->yy_parse_failed();
+                    }
+                    $yymajor = self::YYNOCODE;
+                }
+            } else {
+                $this->yy_accept();
+                $yymajor = self::YYNOCODE;
+            }            
+        } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
+    }
+}#line 186 "oql-parser.y"
+
+
+class OQLParserException extends OQLException
+{
+	public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
+	{
+		$sIssue = "Unexpected token $sTokenName";
+	
+		parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
+	}
+}
+
+class OQLParser extends OQLParserRaw
+{
+	// dirty, but working for us (no other mean to get the final result :-(
+   protected $my_result;
+
+	public function GetResult()
+	{
+		return $this->my_result;
+	}
+
+   // More info on the source query and the current position while parsing it
+   // Data used when an exception is raised
+	protected $m_iLine; // still not used
+	protected $m_iCol;
+	protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token
+	protected $m_sSourceQuery;
+
+	public function __construct($sQuery)
+	{
+		$this->m_iLine = 0;
+		$this->m_iCol = 0;
+		$this->m_iColPrev = 0;
+		$this->m_sSourceQuery = $sQuery;
+		// no constructor - parent::__construct();
+	}
+	
+	public function doParse($token, $value, $iCurrPosition = 0)
+	{
+		$this->m_iColPrev = $this->m_iCol;
+		$this->m_iCol = $iCurrPosition;
+
+		return parent::DoParse($token, $value);
+	}
+
+	public function doFinish()
+	{
+		$this->doParse(0, 0);
+		return $this->my_result;
+	}
+	
+	public function __destruct()
+	{
+		// Bug in the original destructor, causing an infinite loop !
+		// This is a real issue when a fatal error occurs on the first token (the error could not be seen)
+		if (is_null($this->yyidx))
+		{
+			$this->yyidx = -1;
+		}
+		parent::__destruct();
+	}
+}
+
+#line 1676 "oql-parser.php"

+ 250 - 0
core/oql/oql-parser.y

@@ -0,0 +1,250 @@
+
+/*
+
+This is a LALR(1) grammar
+(seek for Lemon grammar to get some documentation from the Net)
+That doc was helpful: http://www.hwaci.com/sw/lemon/lemon.html
+
+To handle operators precedence we could have used the %left directive
+(we took another option, because that one was discovered right after...
+which option is the best for us?)
+Example:
+%left LOG_AND.
+%left LOG_OR.
+%nonassoc EQ NE GT GE LT LE.
+%left PLUS MINUS.
+%left TIMES DIVIDE MOD.
+%right EXP NOT.
+
+TODO : solve the 2 remaining shift-reduce conflicts (JOIN)
+
+*/
+
+%name OQLParser_
+%declare_class {class OQLParserRaw}
+%syntax_error { 
+throw new OQLParserException($this->m_sSourceQuery, $this->m_iLine, $this->m_iCol, $this->tokenName($yymajor), $TOKEN);
+}
+
+result ::= query(X). { $this->my_result = X; }
+result ::= condition(X). { $this->my_result = X; }
+
+query(A) ::= SELECT class_name(X) join_statement(J) where_statement(W). {
+	A = new OqlQuery(X, X, W, J);
+}
+query(A) ::= SELECT class_name(X) AS_ALIAS class_name(Y) join_statement(J) where_statement(W). {
+	A = new OqlQuery(X, Y, W, J);
+}
+
+where_statement(A) ::= WHERE condition(C). { A = C;}
+where_statement(A) ::= . { A = null;}
+
+join_statement(A) ::= join_item(J) join_statement(S). {
+	// insert the join statement on top of the existing list
+	array_unshift(S, J);
+	// and return the updated array
+	A = S;
+}
+join_statement(A) ::= join_item(J). {
+	A = Array(J);
+}
+join_statement(A) ::= . { A = null;}
+
+join_item(A) ::= JOIN class_name(X) AS_ALIAS class_name(Y) ON join_condition(C).
+{
+	// create an array with one single item
+	A = new OqlJoinSpec(X, Y, C);
+}
+join_item(A) ::= JOIN class_name(X) ON join_condition(C).
+{
+	// create an array with one single item
+	A = new OqlJoinSpec(X, X, C);
+}
+
+join_condition(A) ::= field_id(X) EQ field_id(Y). { A = new BinaryOqlExpression(X, '=', Y); }
+
+condition(A) ::= expression_prio4(X). { A = X; }
+
+expression_basic(A) ::= scalar(X). { A = X; } 
+expression_basic(A) ::= field_id(X). { A = X; }
+expression_basic(A) ::= func_name(X) PAR_OPEN arg_list(Y) PAR_CLOSE. { A = new FunctionOqlExpression(X, Y); }
+expression_basic(A) ::= PAR_OPEN expression_prio4(X) PAR_CLOSE. { A = X; }
+expression_basic(A) ::= expression_basic(X) list_operator(Y) list(Z). { A = new BinaryOqlExpression(X, Y, Z); }
+
+expression_prio1(A) ::= expression_basic(X). { A = X; }
+expression_prio1(A) ::= expression_prio1(X) operator1(Y) expression_basic(Z). { A = new BinaryOqlExpression(X, Y, Z); }
+
+expression_prio2(A) ::= expression_prio1(X). { A = X; }
+expression_prio2(A) ::= expression_prio2(X) operator2(Y) expression_prio1(Z). { A = new BinaryOqlExpression(X, Y, Z); }
+
+expression_prio3(A) ::= expression_prio2(X). { A = X; }
+expression_prio3(A) ::= expression_prio3(X) operator3(Y) expression_prio2(Z). { A = new BinaryOqlExpression(X, Y, Z); }
+
+expression_prio4(A) ::= expression_prio3(X). { A = X; }
+expression_prio4(A) ::= expression_prio4(X) operator4(Y) expression_prio3(Z). { A = new BinaryOqlExpression(X, Y, Z); }
+
+
+list(A) ::= PAR_OPEN scalar_list(X) PAR_CLOSE. {
+	A = new ListOqlExpression(X);
+}
+scalar_list(A) ::= scalar(X). {
+	A = array(X);
+}
+scalar_list(A) ::= scalar_list(L) COMA scalar(X). {
+	array_push(L, X);
+	A = L;
+}
+
+arg_list(A) ::= . {
+	A = array();
+}
+arg_list(A) ::= argument(X). {
+	A = array(X);
+}
+arg_list(A) ::= arg_list(L) COMA argument(X). {
+	array_push(L, X);
+	A = L;
+}
+argument(A) ::= expression_prio4(X). { A = X; }
+argument(A) ::= INTERVAL expression_prio4(X) interval_unit(Y). { A = new IntervalOqlExpression(X, Y); }
+
+interval_unit(A) ::= F_DAY(X). { A = X; }
+interval_unit(A) ::= F_MONTH(X). { A = X; }
+interval_unit(A) ::= F_YEAR(X). { A = X; }
+
+scalar(A) ::= num_scalar(X). { A = X; }
+scalar(A) ::= str_scalar(X). { A = X; }
+
+num_scalar(A) ::= num_value(X). { A = new ScalarOqlExpression(X); }
+str_scalar(A) ::= str_value(X). { A = new ScalarOqlExpression(X); }
+
+field_id(A) ::= name(X). { A = new FieldOqlExpression(X); }
+field_id(A) ::= class_name(X) DOT name(Y). { A = new FieldOqlExpression(Y, X); }
+class_name(A) ::= name(X). { A=X; }
+
+name(A) ::= NAME(X). {
+	if (X[0] == '`')
+	{
+		$name = substr(X, 1, strlen(X) - 2);
+	}
+	else
+	{
+		$name = X;
+	}
+	A = new OqlName($name, $this->m_iColPrev);
+}
+
+num_value(A) ::= NUMVAL(X). {A=X;}
+str_value(A) ::= STRVAL(X). {A=stripslashes(substr(X, 1, strlen(X) - 2));}
+
+
+operator1(A) ::= num_operator1(X). {A=X;}
+operator2(A) ::= num_operator2(X). {A=X;}
+operator2(A) ::= str_operator(X). {A=X;}
+operator2(A) ::= EQ(X). {A=X;}
+operator2(A) ::= NOT_EQ(X). {A=X;}
+operator3(A) ::= LOG_AND(X). {A=X;}
+operator4(A) ::= LOG_OR(X). {A=X;}
+
+num_operator1(A) ::= MATH_DIV(X). {A=X;}
+num_operator1(A) ::= MATH_MULT(X). {A=X;}
+num_operator2(A) ::= MATH_PLUS(X). {A=X;}
+num_operator2(A) ::= MATH_MINUS(X). {A=X;}
+num_operator2(A) ::= GT(X). {A=X;}
+num_operator2(A) ::= LT(X). {A=X;}
+num_operator2(A) ::= GE(X). {A=X;}
+num_operator2(A) ::= LE(X). {A=X;}
+
+str_operator(A) ::= LIKE(X). {A=X;}
+str_operator(A) ::= NOT_LIKE(X). {A=X;}
+
+list_operator(A) ::= IN(X). {A=X;}
+list_operator(A) ::= NOT_IN(X). {A=X;}
+
+func_name(A) ::= F_IF(X). { A=X; }
+func_name(A) ::= F_ELT(X). { A=X; }
+func_name(A) ::= F_COALESCE(X). { A=X; }
+func_name(A) ::= F_CONCAT(X). { A=X; }
+func_name(A) ::= F_SUBSTR(X). { A=X; }
+func_name(A) ::= F_TRIM(X). { A=X; }
+func_name(A) ::= F_DATE(X). { A=X; }
+func_name(A) ::= F_DATE_FORMAT(X). { A=X; }
+func_name(A) ::= F_CURRENT_DATE(X). { A=X; }
+func_name(A) ::= F_NOW(X). { A=X; }
+func_name(A) ::= F_TIME(X). { A=X; }
+func_name(A) ::= F_TO_DAYS(X). { A=X; }
+func_name(A) ::= F_FROM_DAYS(X). { A=X; }
+func_name(A) ::= F_YEAR(X). { A=X; }
+func_name(A) ::= F_MONTH(X). { A=X; }
+func_name(A) ::= F_DAY(X). { A=X; }
+func_name(A) ::= F_DATE_ADD(X). { A=X; }
+func_name(A) ::= F_DATE_SUB(X). { A=X; }
+func_name(A) ::= F_ROUND(X). { A=X; }
+func_name(A) ::= F_FLOOR(X). { A=X; }
+
+
+%code {
+
+class OQLParserException extends OQLException
+{
+	public function __construct($sInput, $iLine, $iCol, $sTokenName, $sTokenValue)
+	{
+		$sIssue = "Unexpected token $sTokenName";
+	
+		parent::__construct($sIssue, $sInput, $iLine, $iCol, $sTokenValue);
+	}
+}
+
+class OQLParser extends OQLParserRaw
+{
+	// dirty, but working for us (no other mean to get the final result :-(
+   protected $my_result;
+
+	public function GetResult()
+	{
+		return $this->my_result;
+	}
+
+   // More info on the source query and the current position while parsing it
+   // Data used when an exception is raised
+	protected $m_iLine; // still not used
+	protected $m_iCol;
+	protected $m_iColPrev; // this is the interesting one, because the parser will reduce on the next token
+	protected $m_sSourceQuery;
+
+	public function __construct($sQuery)
+	{
+		$this->m_iLine = 0;
+		$this->m_iCol = 0;
+		$this->m_iColPrev = 0;
+		$this->m_sSourceQuery = $sQuery;
+		// no constructor - parent::__construct();
+	}
+	
+	public function doParse($token, $value, $iCurrPosition = 0)
+	{
+		$this->m_iColPrev = $this->m_iCol;
+		$this->m_iCol = $iCurrPosition;
+
+		return parent::DoParse($token, $value);
+	}
+
+	public function doFinish()
+	{
+		$this->doParse(0, 0);
+		return $this->my_result;
+	}
+	
+	public function __destruct()
+	{
+		// Bug in the original destructor, causing an infinite loop !
+		// This is a real issue when a fatal error occurs on the first token (the error could not be seen)
+		if (is_null($this->yyidx))
+		{
+			$this->yyidx = -1;
+		}
+		parent::__destruct();
+	}
+}
+
+}

+ 78 - 0
core/oql/oqlexception.class.inc.php

@@ -0,0 +1,78 @@
+<?php
+
+class OQLException extends CoreException
+{
+	public function __construct($sIssue, $sInput, $iLine, $iCol, $sUnexpected, $aExpecting = null)
+	{
+		$this->m_MyIssue = $sIssue;
+		$this->m_sInput = $sInput;
+		$this->m_iLine = $iLine;
+		$this->m_iCol = $iCol;
+		$this->m_sUnexpected = $sUnexpected;
+		$this->m_aExpecting = $aExpecting;
+
+		if (is_null($this->m_aExpecting) || (count($this->m_aExpecting) == 0))
+		{
+			$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput'";
+		}
+		else
+		{
+			$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
+			$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
+			$sMessage = "$sIssue - found '{$this->m_sUnexpected}' at $iCol in '$sInput', expecting $sExpectations, I would suggest to use '$sSuggest'";
+		}
+		
+		// make sure everything is assigned properly
+		parent::__construct($sMessage, 0);
+	}
+
+	public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
+	{
+		$sRet = htmlentities($this->m_MyIssue.", found '".$this->m_sUnexpected."' in: ");
+		$sRet .= htmlentities(substr($this->m_sInput, 0, $this->m_iCol));
+		$sRet .= $sHighlightHtmlBegin.htmlentities(substr($this->m_sInput, $this->m_iCol, strlen($this->m_sUnexpected))).$sHighlightHtmlEnd;
+		$sRet .= htmlentities(substr($this->m_sInput, $this->m_iCol + strlen($this->m_sUnexpected)));
+
+		if (!is_null($this->m_aExpecting) && (count($this->m_aExpecting) > 0))
+		{
+			$sExpectations = '{'.implode(', ', $this->m_aExpecting).'}';
+			$sRet .= ", expecting ".htmlentities($sExpectations); 
+			$sSuggest = self::FindClosestString($this->m_sUnexpected, $this->m_aExpecting);
+			if (strlen($sSuggest) > 0)
+			{
+				$sRet .= ", I would suggest to use '$sHighlightHtmlBegin".htmlentities($sSuggest)."$sHighlightHtmlEnd'";
+			}
+		}
+
+		return $sRet;
+	}
+
+	static protected function FindClosestString($sInput, $aDictionary)
+	{
+		// no shortest distance found, yet
+		$fShortest = -1;
+		$sRet = '';
+		
+		// loop through words to find the closest
+		foreach ($aDictionary as $sSuggestion)
+		{
+			// calculate the distance between the input string and the suggested one
+			$fDist = levenshtein($sInput, $sSuggestion);
+			if ($fDist == 0)
+			{
+				// Exact match
+				return $sSuggestion;
+			}
+			
+			if ($fShortest < 0 || ($fDist < 4 && $fDist <= $fShortest))
+			{
+				// set the closest match, and shortest distance
+				$sRet = $sSuggestion;
+				$fShortest = $fDist;
+			}
+		}
+		return $sRet;
+	}
+}
+
+?>

+ 59 - 0
core/oql/oqlinterpreter.class.inc.php

@@ -0,0 +1,59 @@
+<?
+
+class OqlNormalizeException extends OQLException
+{
+	public function __construct($sIssue, $sInput, OqlName $oName, $aExpecting = null)
+	{
+		parent::__construct($sIssue, $sInput, 0, $oName->GetPos(), $oName->GetValue(), $aExpecting);
+	}
+}
+
+class OqlInterpreterException extends OQLException
+{
+}
+
+
+class OqlInterpreter
+{
+	public $m_sQuery;
+
+	public function __construct($sQuery)
+	{
+		$this->m_sQuery = $sQuery;
+	}
+
+	protected function Parse()
+	{
+		$oLexer = new OQLLexer($this->m_sQuery);
+		$oParser = new OQLParser($this->m_sQuery);
+
+		while($oLexer->yylex())
+		{
+			$oParser->doParse($oLexer->token, $oLexer->value, $oLexer->getTokenPos());
+		}
+		$res = $oParser->doFinish();
+		return $res;
+	}
+
+	public function ParseQuery()
+	{
+		$oRes = $this->Parse();
+		if (!$oRes instanceof OqlQuery)
+		{
+			throw new OqlException('Expecting an OQL query', $this->m_sQuery, 0, 0, get_class($oRes), array('OqlQuery'));
+		}
+		return $oRes;
+	}
+
+	public function ParseExpression()
+	{
+		$oRes = $this->Parse();
+		if (!$oRes instanceof Expression)
+		{
+			throw new OqlException('Expecting an OQL expression', $this->m_sQuery, 0, 0, get_class($oRes), array('Expression'));
+		}
+		return $oRes;
+	}
+}
+
+?>

+ 168 - 0
core/oql/oqlquery.class.inc.php

@@ -0,0 +1,168 @@
+<?
+
+// Position a string within an OQL query
+// This is a must if we want to be able to pinpoint an error at any stage of the query interpretation
+// In particular, the normalization phase requires this
+class OqlName
+{
+	protected $m_sValue;
+	protected $m_iPos;
+
+	public function __construct($sValue, $iPos)
+	{
+		$this->m_iPos = $iPos;
+		$this->m_sValue = $sValue;
+	}
+
+	public function GetValue()
+	{
+		return $this->m_sValue;
+	}
+
+	public function GetPos()
+	{
+		return $this->m_iPos;
+	}
+	
+	public function __toString()
+	{
+		return $this->m_sValue;
+	} 
+}
+
+class OqlJoinSpec
+{
+	protected $m_oClass;
+	protected $m_oClassAlias;
+	protected $m_oLeftField;
+	protected $m_oRightField;
+
+	protected $m_oNextJoinspec;
+
+	public function __construct($oClass, $oClassAlias, BinaryExpression $oExpression)
+	{
+		$this->m_oClass = $oClass;
+		$this->m_oClassAlias = $oClassAlias;
+		$this->m_oLeftField = $oExpression->GetLeftExpr();
+		$this->m_oRightField = $oExpression->GetRightExpr();
+	}
+
+	public function GetClass()
+	{
+		return $this->m_oClass->GetValue();
+	}
+	public function GetClassAlias()
+	{
+		return $this->m_oClassAlias->GetValue();
+	}
+
+	public function GetClassDetails()
+	{
+		return $this->m_oClass;
+	}
+	public function GetClassAliasDetails()
+	{
+		return $this->m_oClassAlias;
+	}
+
+	public function GetLeftField()
+	{
+		return $this->m_oLeftField;
+	}
+	public function GetRightField()
+	{
+		return $this->m_oRightField;
+	}
+}
+
+class BinaryOqlExpression extends BinaryExpression
+{
+}
+
+class ScalarOqlExpression extends ScalarExpression
+{
+}
+
+class FieldOqlExpression extends FieldExpression
+{
+	protected $m_oParent;
+	protected $m_oName;
+
+	public function __construct($oName, $oParent = null)
+	{
+		if (is_null($oParent))
+		{
+			$oParent = new OqlName('', 0);
+		}
+		$this->m_oParent = $oParent;
+		$this->m_oName = $oName;
+
+		parent::__construct($oName->GetValue(), $oParent->GetValue());
+	}
+
+	public function GetParentDetails()
+	{
+		return $this->m_oParent;
+	}
+
+	public function GetNameDetails()
+	{
+		return $this->m_oName;
+	}
+}
+
+class ListOqlExpression extends ListExpression
+{
+}
+
+class FunctionOqlExpression extends FunctionExpression
+{
+}
+
+class IntervalOqlExpression extends IntervalExpression
+{
+}
+class OqlQuery
+{
+	protected $m_oClass;
+	protected $m_oClassAlias;
+	protected $m_aJoins; // array of OqlJoinSpec
+	protected $m_oCondition; // condition tree (expressions)
+
+	public function __construct($oClass, $oClassAlias = '', $oCondition = null, $aJoins = null)
+	{
+		$this->m_oClass = $oClass;
+		$this->m_oClassAlias = $oClassAlias;
+		$this->m_aJoins = $aJoins;
+		$this->m_oCondition = $oCondition;
+	}
+
+	public function GetClass()
+	{
+		return $this->m_oClass->GetValue();
+	}
+	public function GetClassAlias()
+	{
+		return $this->m_oClassAlias->GetValue();
+	}
+
+	public function GetClassDetails()
+	{
+		return $this->m_oClass;
+	}
+	public function GetClassAliasDetails()
+	{
+		return $this->m_oClassAlias;
+	}
+
+	public function GetJoins()
+	{
+		return $this->m_aJoins;
+	}
+	public function GetCondition()
+	{
+		return $this->m_oCondition;
+	}
+}
+
+?>

+ 413 - 0
core/sqlquery.class.inc.php

@@ -0,0 +1,413 @@
+<?php
+
+/**
+ * SQLQuery
+ * build an mySQL compatible SQL query
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+require_once('cmdbsource.class.inc.php');
+
+
+class SQLExpression extends BinaryExpression
+{
+}
+class ScalarSQLExpression extends ScalarExpression
+{
+}
+class TrueSQLExpression extends TrueExpression
+{
+}
+class FieldSQLExpression extends FieldExpression
+{
+}
+
+
+
+class SQLQuery
+{
+	private $m_sTable = '';
+	private $m_sTableAlias = '';
+	private $m_aFields = array();
+	private $m_oConditionExpr = null;
+	private $m_aFullTextNeedles = array();
+	private $m_bToDelete = true; // The current table must be listed for deletion ?
+	private $m_aValues = array(); // Values to set in case of an update query
+	private $m_aJoinSelects = array();
+
+	public function __construct($sTable, $sTableAlias, $aFields, $oConditionExpr, $aFullTextNeedles, $bToDelete = true, $aValues = array())
+	{
+		if (!CMDBSource::IsTable($sTable))
+		{
+			throw new CoreException("Unknown table '$sTable'");
+		}
+		// $aFields must be an array of "alias"=>"expr"
+		// $oConditionExpr must be a condition tree
+		// $aValues is an array of "alias"=>value
+
+		$this->m_sTable = $sTable;
+		$this->m_sTableAlias = $sTableAlias;
+		$this->m_aFields = $aFields;
+		$this->m_oConditionExpr = $oConditionExpr;
+		if (is_null($oConditionExpr))
+		{
+			$this->m_oConditionExpr = new TrueExpression;
+		}
+		else if (!$oConditionExpr instanceof Expression)
+		{
+			throw new CoreException('Invalid type for condition, expecting an Expression', array('class' => get_class($oConditionExpr)));
+		}
+		$this->m_aFullTextNeedles = $aFullTextNeedles;
+		$this->m_bToDelete = $bToDelete;
+		$this->m_aValues = $aValues;
+	}
+
+	public function DisplayHtml()
+	{
+		if (count($this->m_aFields) == 0) $sFields = "";
+		else
+		{
+			$aFieldDesc = array();
+			foreach ($this->m_aFields as $sAlias => $oExpression)
+			{
+				$aFieldDesc[] = $oExpression->Render()." as <em>$sAlias</em>";
+			}
+			$sFields = " =&gt; ".implode(', ', $aFieldDesc);
+		}
+		echo "<b>$this->m_sTable</b>$sFields<br/>\n";
+		// #@# todo - display html of an expression tree
+		//$this->m_oConditionExpr->DisplayHtml()
+		if (count($this->m_aFullTextNeedles) > 0)
+		{
+			echo "Full text criteria...<br/>\n";
+			echo "<ul class=\"treeview\">\n";
+			foreach ($this->m_aFullTextNeedles as $sFTNeedle)
+			{
+				echo "<li>$sFTNeedle</li>\n";
+			}
+			echo "</ul>";
+		}
+		if (count($this->m_aJoinSelects) > 0)
+		{
+			echo "Joined to...<br/>\n";
+			echo "<ul class=\"treeview\">\n";
+			foreach ($this->m_aJoinSelects as $aJoinInfo)
+			{
+				$sJoinType = $aJoinInfo["jointype"];
+				$oSQLQuery = $aJoinInfo["select"];
+				$sLeftField = $aJoinInfo["leftfield"];
+				$sRightField = $aJoinInfo["rightfield"];
+				$sRightTableAlias = $aJoinInfo["righttablealias"];
+
+				echo "<li>Join '$sJoinType', $sLeftField, $sRightTableAlias.$sRightField".$oSQLQuery->DisplayHtml()."</li>\n";
+			}
+			echo "</ul>";
+		}
+		$aFrom = array();
+		$aFields = array();
+		$oCondition = null;
+		$aDelTables = array();
+		$aSetValues = array();
+		$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues);
+		echo "From ...<br/>\n";
+		echo "<pre style=\"font-size: smaller;\">\n";
+		print_r($aFrom);
+		echo "</pre>";
+	}
+
+	public function SetCondition($oConditionExpr)
+	{
+		$this->m_oConditionExpr = $oConditionExpr;
+	}
+
+	public function AddCondition($oConditionExpr)
+	{
+		$this->m_oConditionExpr->LogAnd($oConditionExpr);
+	}
+
+	private function AddJoin($sJoinType, $oSQLQuery, $sLeftField, $sRightField, $sRightTableAlias = '')
+	{
+		assert((get_class($oSQLQuery) == __CLASS__) || is_subclass_of($oSQLQuery, __CLASS__));
+		if (!CMDBSource::IsField($this->m_sTable, $sLeftField))
+		{
+			throw new CoreException("Unknown field '$sLeftField' in table '".$this->m_sTable);
+		}
+		if (empty($sRightTableAlias))
+		{
+			$sRightTableAlias = $oSQLQuery->m_sTableAlias;
+		}
+// #@# Could not be verified here because the namespace is unknown - do we need to check it there?
+//
+//		if (!CMDBSource::IsField($sRightTable, $sRightField))
+//		{
+//			throw new CoreException("Unknown field '$sRightField' in table '".$sRightTable."'");
+//		}
+
+		$this->m_aJoinSelects[] = array(
+			"jointype" => $sJoinType,
+			"select" => $oSQLQuery,
+			"leftfield" => $sLeftField,
+			"rightfield" => $sRightField,
+			"righttablealias" => $sRightTableAlias
+		);
+	}
+	public function AddInnerJoin($oSQLQuery, $sLeftField, $sRightField, $sRigthtTable = '')
+	{
+		$this->AddJoin("inner", $oSQLQuery, $sLeftField, $sRightField, $sRigthtTable);
+	}
+	public function AddLeftJoin($oSQLQuery, $sLeftField, $sRightField)
+	{
+		return $this->AddJoin("left", $oSQLQuery, $sLeftField, $sRightField);
+	}
+	
+	// Interface, build the SQL query
+	public function RenderDelete()
+	{
+		// The goal will be to complete the list as we build the Joins
+		$aFrom = array();
+		$aFields = array();
+		$oCondition = null;
+		$aDelTables = array();
+		$aSetValues = array();
+		$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues);
+
+		// Target: DELETE myAlias1, myAlias2 FROM t1 as myAlias1, t2 as myAlias2, t3 as topreserve WHERE ...
+
+		$sDelete = self::ClauseDelete($aDelTables);
+		$sFrom   = self::ClauseFrom($aFrom);
+		// #@# safety net to redo ?
+		/*
+		if ($this->m_oConditionExpr->IsAny())
+		-- if (count($aConditions) == 0) --
+		{
+			throw new CoreException("Building a request wich will delete every object of a given table -looks suspicious- please use truncate instead...");
+		}
+		*/
+		$sWhere  = self::ClauseWhere($oCondition);
+		return "DELETE $sDelete FROM $sFrom WHERE $sWhere";
+	}
+
+	// Interface, build the SQL query
+	public function RenderUpdate()
+	{
+		// The goal will be to complete the list as we build the Joins
+		$aFrom = array();
+		$aFields = array();
+		$oCondition = null;
+		$aDelTables = array();
+		$aSetValues = array();
+		$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues);
+
+		$sFrom   = self::ClauseFrom($aFrom);
+		$sValues = self::ClauseValues($aSetValues);
+		$sWhere  = self::ClauseWhere($oCondition);
+		return "UPDATE $sFrom SET $sValues WHERE $sWhere";
+	}
+
+	// Interface, build the SQL query
+	public function RenderSelect($aOrderBy = array())
+	{
+		// The goal will be to complete the lists as we build the Joins
+		$aFrom = array();
+		$aFields = array();
+		$oCondition = null;
+		$aDelTables = array();
+		$aSetValues = array();
+		$this->privRender($aFrom, $aFields, $oCondition, $aDelTables, $aSetValues);
+
+		$sSelect = self::ClauseSelect($aFields);
+		$sFrom   = self::ClauseFrom($aFrom);
+		$sWhere  = self::ClauseWhere($oCondition);
+		$sOrderBy = self::ClauseOrderBy($aOrderBy);
+		if (!empty($sOrderBy))
+		{
+			$sOrderBy = "ORDER BY $sOrderBy";
+		}
+		return "SELECT DISTINCT $sSelect FROM $sFrom WHERE $sWhere $sOrderBy";
+	}
+
+	private static function ClauseSelect($aFields)
+	{
+		$aSelect = array();
+		foreach ($aFields as $sFieldAlias => $sSQLExpr)
+		{
+			$aSelect[] = "$sSQLExpr AS $sFieldAlias";
+		}
+		$sSelect = implode(', ', $aSelect);
+		return $sSelect;
+	}
+
+	private static function ClauseDelete($aDelTableAliases)
+	{
+		$aDelTables = array();
+		foreach ($aDelTableAliases as $sTableAlias)
+		{
+			$aDelTables[] = "$sTableAlias";
+		}
+		$sDelTables = implode(', ', $aDelTables);
+		return $sDelTables;
+	}
+
+	private static function ClauseFrom($aFrom)
+	{
+		$sFrom = "";
+		foreach ($aFrom as $sTableAlias => $aJoinInfo)
+		{
+			switch ($aJoinInfo["jointype"])
+			{
+				case "first":
+					$sFrom .= "`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
+					$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
+					break;
+				case "inner":
+					$sFrom .= " INNER JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
+					$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
+					$sFrom .= ") ON ".$aJoinInfo["joincondition"];
+					break;
+				case "left":
+					$sFrom .= " LEFT JOIN (`".$aJoinInfo["tablename"]."` AS `$sTableAlias`";
+					$sFrom .= " ".self::ClauseFrom($aJoinInfo["subfrom"]);
+					$sFrom .= ") ON ".$aJoinInfo["joincondition"];
+					break;
+				default:
+					throw new CoreException("Unknown jointype: '".$aJoinInfo["jointype"]."'");
+			}
+		}
+		return $sFrom;
+	}
+
+	private static function ClauseValues($aValues)
+	{
+		$aSetValues = array();
+		foreach ($aValues as $sFieldSpec => $value)
+		{
+			$aSetValues[] = "$sFieldSpec = ".CMDBSource::Quote($value);
+		}
+		$sSetValues = implode(', ', $aSetValues);
+		return $sSetValues;
+	}
+
+	private static function ClauseWhere($oConditionExpr)
+	{
+		return $oConditionExpr->Render();
+	}
+
+	private static function ClauseOrderBy($aOrderBy)
+	{
+		$aOrderBySpec = array();
+		foreach($aOrderBy as $sFieldAlias => $bAscending)
+		{
+			$aOrderBySpec[] = '`'.$sFieldAlias.'`'.($bAscending ? " ASC" : " DESC");
+		}
+		$sOrderBy = implode(", ", $aOrderBySpec);
+		return $sOrderBy;
+	}
+
+	// Purpose: prepare the query data, once for all
+	private function privRender(&$aFrom, &$aFields, &$oCondition, &$aDelTables, &$aSetValues)
+	{
+		$sTableAlias = $this->privRenderSingleTable($aFrom, $aFields, $aDelTables, $aSetValues);
+
+		// Add the full text search condition, based on each and every requested field
+		//
+		// To be updated with a real full text search based on the mySQL settings
+		// (then it might move somewhere else !)
+		//
+		$oCondition = $this->m_oConditionExpr;
+		if ((count($aFields) > 0) && (count($this->m_aFullTextNeedles) > 0))
+		{
+			$aFieldExp = array();
+			foreach ($aFields as $sField)
+			{
+				// This is TEMPORARY (that's why it is weird, actually)
+				// Full text match will be done as an expression in the filter condition
+
+				// $sField is already a string `table`.`column`
+				// Let's make an expression out of it (again !)
+				$aFieldExp[] = Expression::FromOQL($sField);
+			}
+			$oFullTextExpr = new CharConcatExpression($aFieldExp);
+			// The cast is necessary because the CONCAT result in a binary string:
+			// if any of the field is a binary string => case sensitive comparison
+			//
+			foreach($this->m_aFullTextNeedles as $sFTNeedle)
+			{
+				$oNewCond = new BinaryExpression($oFullTextExpr, 'LIKE', new ScalarExpression("%$sFTNeedle%"));
+				$oCondition = $oCondition->LogAnd($oNewCond);
+			}
+		}
+
+		return $sTableAlias; 
+	}
+
+	private function privRenderSingleTable(&$aFrom, &$aFields, &$aDelTables, &$aSetValues, $sJoinType = 'first', $sCallerAlias = '', $sLeftField = '', $sRightField = '', $sRightTableAlias = '')
+	{
+		$aActualTableFields = CMDBSource::GetTableFieldsList($this->m_sTable);
+
+		$aTranslationTable[$this->m_sTable]['*'] = $this->m_sTableAlias;
+
+		// Handle the various kinds of join (or first table in the list)
+		//
+		if (empty($sRightTableAlias))
+		{
+			$sRightTableAlias = $this->m_sTableAlias;
+		}
+		$sJoinCond = "`$sCallerAlias`.`$sLeftField` = `$sRightTableAlias`.`$sRightField`";
+		switch ($sJoinType)
+		{
+			case "first":
+				$aFrom[$this->m_sTableAlias] = array("jointype"=>"first", "tablename"=>$this->m_sTable, "joincondition"=>"");
+				break;
+			case "inner":
+			case "left":
+			// table or tablealias ???
+				$aFrom[$this->m_sTableAlias] = array("jointype"=>$sJoinType, "tablename"=>$this->m_sTable, "joincondition"=>"$sJoinCond");
+				break;
+		}
+
+		// Given the alias, modify the fields and conditions
+		// before adding them into the current lists
+		//
+		foreach($this->m_aFields as $sAlias => $oExpression)
+		{
+			$sTable = $oExpression->GetParent();
+			$sColumn = $oExpression->GetName();
+			$aFields["`$sAlias`"] = $oExpression->Render();
+		}
+		if ($this->m_bToDelete)
+		{
+			$aDelTables[] = "`{$this->m_sTableAlias}`";
+		}
+		foreach($this->m_aValues as $sFieldName=>$value)
+		{
+			$aSetValues["`{$this->m_sTableAlias}`.`$sFieldName`"] = $value; // quoted further!
+		}
+
+		// loop on joins, to complete the list of tables/fields/conditions
+		//
+		$aTempFrom = array(); // temporary subset of 'from' specs, to be grouped in the final query
+		foreach ($this->m_aJoinSelects as $aJoinData)
+		{
+			$sJoinType = $aJoinData["jointype"];
+			$oRightSelect = $aJoinData["select"];
+			$sLeftField = $aJoinData["leftfield"];
+			$sRightField = $aJoinData["rightfield"];
+			$sRightTableAlias = $aJoinData["righttablealias"];
+
+			$sJoinTableAlias = $oRightSelect->privRenderSingleTable($aTempFrom, $aFields, $aDelTables, $aSetValues, $sJoinType, $this->m_sTableAlias, $sLeftField, $sRightField, $sRightTableAlias);
+		}
+		$aFrom[$this->m_sTableAlias]['subfrom'] = $aTempFrom;
+
+		return $this->m_sTableAlias;
+	}
+
+}
+
+?>

+ 59 - 0
core/stimulus.class.inc.php

@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * A stimulus is the trigger that makes the lifecycle go ahead (state machine) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class ObjectStimulus
+{
+	private $m_aParams = array();
+
+	public function __construct($aParams)
+	{
+		$this->m_aParams = $aParams;
+		$this->ConsistencyCheck();
+	}
+
+	public function Get($sParamName) {return $this->m_aParams[$sParamName];}
+
+	// Note: I could factorize this code with the parameter management made for the AttributeDef class
+	// to be overloaded
+	static protected function ListExpectedParams()
+	{
+		return array("label", "description");
+	}
+
+	private function ConsistencyCheck()
+	{
+
+		// Check that any mandatory param has been specified
+		//
+		$aExpectedParams = $this->ListExpectedParams();
+		foreach($aExpectedParams as $sParamName)
+		{
+			if (!array_key_exists($sParamName, $this->m_aParams))
+			{
+				$aBacktrace = debug_backtrace();
+				$sTargetClass = $aBacktrace[2]["class"];
+				$sCodeInfo = $aBacktrace[1]["file"]." - ".$aBacktrace[1]["line"];
+				throw new CoreException("missing parameter '$sParamName' in ".get_class($this)." declaration for class $sTargetClass ($sCodeInfo)");
+			}
+		}
+	}
+}
+
+
+
+class StimulusUserAction extends ObjectStimulus
+{
+}
+
+?>

+ 504 - 0
core/test.class.inc.php

@@ -0,0 +1,504 @@
+<?php
+
+require_once('coreexception.class.inc.php');
+require_once('attributedef.class.inc.php');
+require_once('filterdef.class.inc.php');
+require_once('stimulus.class.inc.php');
+require_once('MyHelpers.class.inc.php');
+
+require_once('expression.class.inc.php');
+require_once('cmdbsource.class.inc.php');
+require_once('sqlquery.class.inc.php');
+
+require_once('dbobject.class.php');
+require_once('dbobjectsearch.class.php');
+require_once('dbobjectset.class.php');
+
+require_once('userrights.class.inc.php');
+
+
+
+// Just to differentiate programmatically triggered exceptions and other kind of errors (usefull?)
+class UnitTestException extends Exception
+{}
+
+
+/**
+ * Improved display of the backtrace
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+class ExceptionFromError extends Exception
+{
+	public function getTraceAsHtml()
+	{
+		$aBackTrace = $this->getTrace();
+		return MyHelpers::get_callstack_html(0, $this->getTrace());
+		// return "<pre>\n".$this->getTraceAsString()."</pre>\n";
+	}
+}
+
+
+/**
+ * Test handler API and basic helpers
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+abstract class TestHandler
+{
+	protected $m_aSuccesses;
+	protected $m_aWarnings;
+	protected $m_aErrors;
+	protected $m_sOutput;
+
+	public function __construct()
+	{
+		$this->m_aSuccesses = array();
+		$this->m_aWarnings = array();
+		$this->m_aErrors = array();
+	}
+
+	abstract static public function GetName();
+	abstract static public function GetDescription();
+
+	protected function DoPrepare() {return true;}
+	abstract protected function DoExecute();
+	protected function DoCleanup() {return true;}
+
+	protected function ReportSuccess($sMessage, $sSubtestId = '')
+	{
+		$this->m_aSuccesses[] = $sMessage;
+	}
+
+	protected function ReportWarning($sMessage, $sSubtestId = '')
+	{
+		$this->m_aWarnings[] = $sMessage;
+	}
+
+	protected function ReportError($sMessage, $sSubtestId = '')
+	{
+		$this->m_aErrors[] = $sMessage;
+	}
+
+	public function GetResults()
+	{
+		return $this->m_aSuccesses;
+	}
+
+	public function GetWarnings()
+	{
+		return $this->m_aWarnings;
+	}
+
+	public function GetErrors()
+	{
+		return $this->m_aErrors;
+	}
+
+	public function GetOutput()
+	{
+		return $this->m_sOutput;
+	}
+
+	public function error_handler($errno, $errstr, $errfile, $errline)
+	{
+		// Note: return false to call the default handler (stop the program if an error)
+
+		switch ($errno)
+		{
+		case E_USER_ERROR:
+			$this->ReportError($errstr);
+			//throw new ExceptionFromError("Fatal error in line $errline of file $errfile: $errstr");
+			break;
+		case E_USER_WARNING:
+			$this->ReportWarning($errstr);
+			break;
+		case E_USER_NOTICE:
+			$this->ReportWarning($errstr);
+			break;
+		default:
+			throw new ExceptionFromError("Fatal warning in line $errline of file $errfile: $errstr");
+			$this->ReportWarning("Unknown error type: [$errno] $errstr");
+			echo "Unknown error type: [$errno] $errstr<br />\n";
+			break;
+		}
+		return true; // do not call the default handler
+	}
+
+	public function Execute()
+	{
+		ob_start();
+		set_error_handler(array($this, 'error_handler'));
+		try
+		{
+			$this->DoPrepare();
+			$this->DoExecute();
+		}
+		catch (ExceptionFromError $e)
+		{
+			$this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
+		}
+		catch (CoreException $e)
+		{
+			//$this->ReportError($e->getMessage());
+			//$this->ReportError($e->__tostring());
+			$this->ReportError($e->getMessage().' - '.$e->getTraceAsHtml());
+		}
+		catch (Exception $e)
+		{
+			//$this->ReportError($e->getMessage());
+			//$this->ReportError($e->__tostring());
+			$this->ReportError('class '.get_class($e).' --- '.$e->getMessage().' - '.$e->getTraceAsString());
+		}
+		restore_error_handler();
+		$this->m_sOutput = ob_get_clean();
+		return (count($this->GetErrors()) == 0);
+	}
+}
+
+
+
+
+/**
+ * Test to execute a piece of code (checks if an error occurs)  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestFunction extends TestHandler
+{
+	// simply overload DoExecute (temporary)
+}
+
+
+/**
+ * Test to execute a piece of code (checks if an error occurs)  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestWebServices extends TestHandler
+{
+	// simply overload DoExecute (temporary)
+
+	static protected function DoPostRequestAuth($sRelativeUrl, $aData, $sLogin = 'admin', $sPassword = '', $sOptionnalHeaders = null)
+	{
+		$aDataAndAuth = $aData;
+		$aDataAndAuth['operation'] = 'login';
+		$aDataAndAuth['auth_user'] = $sLogin;
+		$aDataAndAuth['auth_pwd'] = $sPassword;
+
+		$sHost = $GLOBALS['_SERVER']['HTTP_HOST'];
+		$sUrl = "http://$sHost/$sRelativeUrl";
+
+		return self::DoPostRequest($sUrl, $aDataAndAuth, $sOptionnalHeaders);
+	}
+
+	// Source: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
+	// originaly named after do_post_request
+	// Partially adapted to our coding conventions
+	static protected function DoPostRequest($sUrl, $aData, $sOptionnalHeaders = null)
+	{
+		// $sOptionnalHeaders is a string containing additional HTTP headers that you would like to send in your request.
+
+		$sData = http_build_query($aData);
+
+		$aParams = array('http' => array(
+								'method' => 'POST',
+								'content' => $sData,
+								'header'=> "Content-type: application/x-www-form-urlencoded\r\nContent-Length: ".strlen($sData)."\r\n",
+								));
+		if ($sOptionnalHeaders !== null)
+		{
+			$aParams['http']['header'] .= $sOptionnalHeaders;
+		}
+		$ctx = stream_context_create($aParams);
+
+		$fp = @fopen($sUrl, 'rb', false, $ctx);
+		if (!$fp)
+		{
+			throw new Exception("Problem with $sUrl, $php_errormsg");
+		}
+		$response = @stream_get_contents($fp);
+		if ($response === false)
+		{
+			throw new Exception("Problem reading data from $sUrl, $php_errormsg");
+		}
+		return $response;
+	}
+}
+
+/**
+ * Test to check that a function outputs some values depending on its input  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestFunctionInOut extends TestFunction
+{
+	abstract static public function GetCallSpec(); // parameters to call_user_func
+	abstract static public function GetInOut(); // array of input => output
+
+	protected function DoExecute()
+	{
+		$aTests = $this->GetInOut();
+		if (is_array($aTests))
+		{
+			foreach ($aTests as $iTestId => $aTest)
+			{
+				$ret = call_user_func_array($this->GetCallSpec(), $aTest['args']);
+				if ($ret != $aTest['output'])
+				{
+					// Note: to be improved to cope with non string parameters
+					$this->ReportError("Found '$ret' while expecting '".$aTest['output']."'", $iTestId);
+				}
+				else
+				{
+					$this->ReportSuccess("Found the expected output '$ret'", $iTestId);
+				}
+			}
+		}
+		else
+		{
+			$ret = call_user_func($this->GetCallSpec());
+			$this->ReportSuccess('Finished successfully');
+		}
+	}
+}
+
+
+/**
+ * Test to check an URL (Searches for Error/Warning/Etc keywords)  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestUrl extends TestHandler
+{
+	abstract static public function GetUrl();
+	abstract static public function GetErrorKeywords();
+	abstract static public function GetWarningKeywords();
+
+	protected function DoExecute()
+	{
+		return true;
+	}
+}
+
+
+/**
+ * Test to check a user management module  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestUserRights extends TestHandler
+{
+	protected function DoExecute()
+	{
+		return true;
+	}
+}
+
+
+/**
+ * Test to execute a scenario on a given DB
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestScenarioOnDB extends TestHandler
+{
+	abstract static public function GetDBHost();
+	abstract static public function GetDBUser();
+	abstract static public function GetDBPwd();
+	abstract static public function GetDBName();
+
+	protected function DoPrepare()
+	{
+		$sDBHost = $this->GetDBHost();
+		$sDBUser = $this->GetDBUser();
+		$sDBPwd = $this->GetDBPwd();
+		$sDBName = $this->GetDBName();
+
+		CMDBSource::Init($sDBHost, $sDBUser, $sDBPwd);
+		if (CMDBSource::IsDB($sDBName))
+		{
+			CMDBSource::DropDB($sDBName);
+		}
+		CMDBSource::CreateDB($sDBName);
+	}
+
+	protected function DoCleanup()
+	{
+		// CMDBSource::DropDB($this->GetDBName());
+	}
+}
+
+
+/**
+ * Test to use a business model on a given DB  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestBizModel extends TestHandler
+{
+//	abstract static public function GetDBSubName();
+//	abstract static public function GetBusinessModelFile();
+	abstract static public function GetConfigFile();
+
+	protected function DoPrepare()
+	{
+		MetaModel::Startup($this->GetConfigFile(), true); // allow missing DB
+		MetaModel::CheckDefinitions();
+
+		// something here to create records... but that's another story
+	}
+
+	protected function ResetDB()
+	{
+		if (MetaModel::DBExists())
+		{
+			MetaModel::DBDrop();
+		}
+		MetaModel::DBCreate();
+	}
+
+	static protected function show_list($oObjectSet)
+	{
+		$oObjectSet->Rewind();
+		$aData = array();
+		while ($oItem = $oObjectSet->Fetch())
+		{
+			$aValues = array();
+			foreach(MetaModel::GetAttributesList(get_class($oItem)) as $sAttCode)
+			{
+				$aValues[$sAttCode] = $oItem->GetAsHTML($sAttCode);
+			}
+			//echo $oItem->GetKey()." => ".implode(", ", $aValues)."</br>\n";
+			$aData[] = $aValues;
+		}
+		echo MyHelpers::make_table_from_assoc_array($aData);
+	}
+
+	static protected function search_and_show_list(DBObjectSearch $oMyFilter)
+	{
+		$oObjSet = new CMDBObjectSet($oMyFilter);
+		echo $oMyFilter->__DescribeHTML()."' - Found ".$oObjSet->Count()." items.</br>\n";
+		self::show_list($oObjSet);
+	}
+
+	static protected function search_and_show_list_from_sibusql($sSibuSQL)
+	{
+		echo $sSibuSQL."...<br/>\n"; 
+		$oNewFilter = DBObjectSearch::FromSibuSQL($sSibuSQL);
+		self::search_and_show_list($oNewFilter);
+	}
+}
+
+
+/**
+ * Test to execute a scenario common to any business model (tries to build all the possible queries, etc.)
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class TestBizModelGeneric extends TestBizModel
+{
+	static public function GetName()
+	{
+		return 'Full test on a given business model';
+	}
+
+	static public function GetDescription()
+	{
+		return 'Systematic tests: gets each and every existing class and tries every attribute, search filters, etc.';
+	}
+
+	protected function DoPrepare()
+	{
+		parent::DoPrepare();
+
+		if (!MetaModel::DBExists())
+		{
+			MetaModel::DBCreate();
+		}
+		// something here to create records... but that's another story
+	}
+
+	protected function DoExecute()
+	{
+		foreach(MetaModel::GetClasses() as $sClassName)
+		{
+			if (MetaModel::IsAbstract($sClassName)) continue;
+
+			$oNobody = MetaModel::GetObject($sClassName, 123);
+			$oBaby = new $sClassName;
+			$oFilter = new DBObjectSearch($sClassName);
+
+			// Challenge reversibility of SibusQL / filter object
+			//
+			$sExpr1 = $oFilter->ToSibuSQL();
+			$oNewFilter = DBObjectSearch::FromSibuSQL($sExpr1);
+			$sExpr2 = $oNewFilter->ToSibuSQL();
+			if ($sExpr1 != $sExpr2)
+			{
+				$this->ReportError("Found two different SibuSQL expression out of the (same?) filter: <em>$sExpr1</em> != <em>$sExpr2</em>");
+			}
+
+			// Use the filter (perform the query)
+			//
+			$oSet = new CMDBObjectSet($oFilter);
+			$this->ReportSuccess('Found '.$oSet->Count()." objects of class $sClassName");
+		}
+		return true;
+	}
+}
+
+
+?>

+ 211 - 0
core/userrights.class.inc.php

@@ -0,0 +1,211 @@
+<?php
+
+/**
+ * UserRights
+ * User management API 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+class UserRightException extends CoreException
+{
+}
+
+
+define('UR_ALLOWED_NO', 0);
+define('UR_ALLOWED_YES', 1);
+define('UR_ALLOWED_DEPENDS', 2);
+
+define('UR_ACTION_READ', 1); // View an object
+define('UR_ACTION_MODIFY', 2); // Create/modify an object/attribute
+define('UR_ACTION_DELETE', 3); // Delete an object
+
+define('UR_ACTION_BULK_READ', 4); // Export multiple objects
+define('UR_ACTION_BULK_MODIFY', 5); // Create/modify multiple objects
+define('UR_ACTION_BULK_DELETE', 6); // Delete multiple objects
+
+define('UR_ACTION_APPLICATION_DEFINED', 10000); // Application specific actions (CSV import, View schema...)
+
+/**
+ * User management module API  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+abstract class UserRightsAddOnAPI
+{
+	abstract public function Setup(); // initial installation
+	abstract public function Init(); // loads data (possible optimizations)
+	abstract public function CheckCredentials($iUserId, $sPassword); // returns the id of the user or false
+	abstract public function GetFilter($iUserId, $sClass); // returns a filter object
+	abstract public function IsActionAllowed($iUserId, $sClass, $iActionCode, dbObjectSet $aInstances);
+	abstract public function IsStimulusAllowed($iUserId, $sClass, $sStimulusCode, dbObjectSet $aInstances);
+	abstract public function IsActionAllowedOnAttribute($iUserId, $sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances);
+}
+
+
+
+/**
+ * User management core API  
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class UserRights
+{
+	protected static $m_oAddOn;
+	protected static $m_sUser;
+	protected static $m_sRealUser;
+	protected static $m_iUserId;
+	protected static $m_iRealUserId;
+
+	public static function SelectModule($sModuleName)
+	{
+		if (!class_exists($sModuleName))
+		{
+			throw new CoreException("Could not select this module, '$sModuleName' in not a valid class name");
+			return;
+		}
+		if (!is_subclass_of($sModuleName, 'UserRightsAddOnAPI'))
+		{
+			throw new CoreException("Could not select this module, the class '$sModuleName' is not derived from UserRightsAddOnAPI");
+			return;
+		}
+		self::$m_oAddOn = new $sModuleName;
+		self::$m_oAddOn->Init();
+		self::$m_sUser = '';
+		self::$m_sRealUser = '';
+		self::$m_iUserId = 0;
+		self::$m_iRealUserId = 0;
+	}
+
+	// Installation: create the very first user
+	public static function CreateAdministrator($sAdminUser, $sAdminPwd)
+	{
+		return self::$m_oAddOn->CreateAdministrator($sAdminUser, $sAdminPwd);
+	}
+	
+	// Installation (e.g: give default values for users)
+	public static function Setup()
+	{
+		// to be discussed...
+		return self::$m_oAddOn->Setup();
+	}
+
+	protected static function IsLoggedIn()
+	{
+		return (!empty(self::$m_sUser));
+	}
+
+	public static function Login($sName, $sPassword)
+	{
+		self::$m_iUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword);
+		if ( self::$m_iUserId !== false )
+		{
+			self::$m_sUser = $sName;
+			self::$m_iRealUserId = self::$m_iUserId;
+			self::$m_sRealUser = $sName;
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	public static function Impersonate($sName, $sPassword)
+	{
+		if (!self::CheckLogin()) return false;
+
+		self::$m_iRealUserId = self::$m_oAddOn->CheckCredentials($sName, $sPassword);
+		if ( self::$m_iRealUserId !== false)
+		{
+			self::$m_sUser = $sName;
+			return true;
+		}
+		else
+		{
+			return false;
+		}
+	}
+
+	public static function GetUser()
+	{
+		return self::$m_sUser;
+	}
+
+	public static function GetUserId()
+	{
+		return self::$m_iUserId;
+	}
+
+	public static function GetRealUser()
+	{
+		return self::$m_sRealUser;
+	}
+
+	public static function GetRealUserId()
+	{
+		return self::$m_iRealUserId;
+	}
+
+	protected static function CheckLogin()
+	{
+		if (!self::IsLoggedIn())
+		{
+			//throw new UserRightException('No user logged in', array());	
+			return false;
+		}
+		return true;
+	}
+
+
+	public static function GetFilter($sClass)
+	{
+		if (!MetaModel::HasCategory($sClass, 'bizModel')) return new DBObjectSearch($sClass);
+		if (!self::CheckLogin()) return false;
+
+		return self::$m_oAddOn->GetFilter(self::$m_iUserId, $sClass);
+	}
+
+	public static function IsActionAllowed($sClass, $iActionCode, dbObjectSet $aInstances)
+	{
+		if (!MetaModel::HasCategory($sClass, 'bizModel')) return true;
+		if (!self::CheckLogin()) return false;
+
+		return self::$m_oAddOn->IsActionAllowed(self::$m_iUserId, $sClass, $iActionCode, $aInstances);
+	}
+
+	public static function IsStimulusAllowed($sClass, $sStimulusCode, dbObjectSet $aInstances)
+	{
+		if (!MetaModel::HasCategory($sClass, 'bizModel')) return true;
+		if (!self::CheckLogin()) return false;
+
+		return self::$m_oAddOn->IsStimulusAllowed(self::$m_iUserId, $sClass, $sStimulusCode, $aInstances);
+	}
+
+	public static function IsActionAllowedOnAttribute($sClass, $sAttCode, $iActionCode, dbObjectSet $aInstances)
+	{
+		if (!MetaModel::HasCategory($sClass, 'bizModel')) return true;
+		if (!self::CheckLogin()) return false;
+
+		return self::$m_oAddOn->IsActionAllowedOnAttribute(self::$m_iUserId, $sClass, $sAttCode, $iActionCode, $aInstances);
+	}
+}
+
+
+?>

+ 238 - 0
core/valuesetdef.class.inc.php

@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * ValueSetDefinition
+ * value sets API and implementations
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @author      Denis Flaven <denisflave@free.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     1.1.1.1 $
+ */
+
+require_once('MyHelpers.class.inc.php');
+
+abstract class ValueSetDefinition
+{
+	protected $m_bIsLoaded = false;
+	protected $m_aValues = array();
+	protected $m_aArgsObj = array();
+	protected $m_aArgsApp = array();
+
+
+	// Displayable description that could be computed out of the std usage context
+	public function GetValuesDescription()
+	{
+		$aValues = $this->GetValues(array(), '');
+		$aDisplayedValues = array();
+		foreach($aValues as $key => $value)
+		{
+			$aDisplayedValues[] = "$key => $value";
+		}
+		$sAllowedValues = implode(', ', $aDisplayedValues);
+		return $sAllowedValues;
+	}
+
+
+	public function GetValues($aArgs, $sBeginsWith)
+	{
+		if (!$this->m_bIsLoaded)
+		{
+			$this->LoadValues($aArgs);
+			$this->m_bIsLoaded = true;
+		}
+		if (strlen($sBeginsWith) == 0)
+		{
+			$aRet = $this->m_aValues;
+		}
+		else
+		{
+			$iCheckedLen = strlen($sBeginsWith);
+			$sBeginsWith = strtolower($sBeginsWith);
+			$aRet = array();
+			foreach ($this->m_aValues as $sKey=>$sValue)
+			{
+				if (strtolower(substr($sValue, 0, $iCheckedLen)) == $sBeginsWith)
+				{
+					$aRet[$sKey] = $sValue;
+				}
+			}
+		}
+		return $aRet;
+	}
+
+	public function ListArgsFromContextApp()
+	{
+		return $this->m_aArgsObj;
+	}
+	public function ListArgsFromContextObj()
+	{
+		return $this->m_aArgsApp;
+	}
+
+	abstract protected function LoadValues($aArgs);
+}
+
+
+/**
+ * Set of existing values for an attribute, given a search filter 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class ValueSetObjects extends ValueSetDefinition
+{
+	protected $m_sFilterExpr; // in SibuSQL
+	protected $m_sValueAttCode;
+	protected $m_aOrderBy;
+
+	public function __construct($sFilterExp, $sValueAttCode = '', $aOrderBy = array())
+	{
+		$this->m_sFilterExpr = $sFilterExp;
+		$this->m_sValueAttCode = $sValueAttCode;
+		$this->m_aOrderBy = $aOrderBy;
+	}
+
+	protected function LoadValues($aArgs)
+	{
+		$this->m_aValues = array();
+		
+		$oFilter = DBObjectSearch::FromSibuSQL($this->m_sFilterExpr, $aArgs);
+		if (!$oFilter) return false;
+
+        if (empty($this->m_sValueAttCode))
+        {
+            $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass());
+        }
+		$oObjects = new DBObjectSet($oFilter, $this->m_aOrderBy);
+		while ($oObject = $oObjects->Fetch())
+		{
+			$this->m_aValues[$oObject->GetKey()] = $oObject->GetAsHTML($this->m_sValueAttCode);
+		}
+		return true;
+	}
+	
+	public function GetValuesDescription()
+	{
+		return 'Filter: '.$this->m_sFilterExpr;
+	}
+}
+
+
+/**
+ * Set of existing values for an attribute, given a search filter and a relation id 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class ValueSetRelatedObjects extends ValueSetObjects
+{
+	public function __construct($sFilterExp, $sRelCode, $sClass, $sValueAttCode = '', $aOrderBy = array())
+	{
+		$sFullFilterExp = "$sClass: RELATED ($sRelCode, 1) TO ($sFilterExp)";
+		parent::__construct($sFullFilterExp, $sValueAttCode, $aOrderBy);
+	}
+}
+
+
+/**
+ * Set oof existing values for an attribute, given a set of objects (AttributeLinkedSet) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class ValueSetRelatedObjectsFromLinkedSet extends ValueSetDefinition
+{
+	protected $m_sLinkedSetAttCode;
+	protected $m_sRelCode;
+	protected $m_sValueAttCode;
+	protected $m_aOrderBy;
+
+	public function __construct($sLinkedSetAttCode, $sRelCode, $sValueAttCode = '', $aOrderBy = array())
+	{
+		$this->m_sLinkedSetAttCode = $sLinkedSetAttCode;
+		$this->m_sRelCode = $sRelCode;
+		$this->m_sValueAttCode = $sValueAttCode;
+		$this->m_aOrderBy = $aOrderBy;
+	}
+
+	protected function LoadValues($aArgs)
+	{
+		$this->m_aValues = array();
+
+        if (empty($this->m_sValueAttCode))
+        {
+            $this->m_sValueAttCode = MetaModel::GetNameAttributeCode($oFilter->GetClass());
+        }
+
+        $oCurrentObject = @$aArgs['*this*'];
+        if (!is_object($oCurrentObject)) return false;
+
+		$oObjects = $oCurrentObject->Get($this->m_sLinkedSetAttCode);
+		while ($oObject = $oObjects->Fetch())
+		{
+			$this->m_aValues[$oObject->GetKey()] = $oObject->Get($this->m_sValueAttCode);
+		}
+		return true;
+	}
+	
+	public function GetValuesDescription()
+	{
+		return 'Objects related ('.$this->m_sRelCode.') to objects linked through '.$this->m_sLinkedSetAttCode;
+	}
+}
+
+
+/**
+ * Fixed set values (could be hardcoded in the business model) 
+ *
+ * @package     iTopORM
+ * @author      Romain Quetiez <romainquetiez@yahoo.fr>
+ * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link        www.itop.com
+ * @since       1.0
+ * @version     $itopversion$
+ */
+class ValueSetEnum extends ValueSetDefinition
+{
+	public function __construct($Values)
+	{
+		if (is_array($Values))
+		{
+			$aValues = $Values;
+		}
+		else
+		{
+			$aValues = array();
+			foreach (explode(",", $Values) as $sVal)
+			{
+				$sVal = trim($sVal);
+				$sKey = $sVal; 
+				$aValues[$sKey] = $sVal;
+			}
+		}
+		$this->m_aValues = $aValues;
+	}
+
+	protected function LoadValues($aArgs)
+	{
+		return true;
+	}
+}
+
+?>

+ 222 - 0
css/blue_green.css

@@ -0,0 +1,222 @@
+/* CSS Document */
+body {
+    font-family: Verdana, Arial, Helevtica;
+    font-size: smaller;
+    background-color: #68a;
+    color:#000000;
+	margin: 0;			/* Remove body margin/padding */
+	padding: 0;
+	overflow: hidden;	/* Remove scroll bars on browser window */
+}
+
+table {
+	border: 1px solid #000000;
+}
+
+.raw_output {
+    font-family: Courier-New, Courier, Arial, Helevtica;
+    font-size: smaller;
+    background-color: #eeeeee;
+    color: #000000;
+    border: 1px dashed #000000;
+    padding: 0.25em;
+    margin-top: 1em;
+}
+
+th {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+    color: #000000;
+    background-color:#ace27d;	
+}
+
+td {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+	background-color: #b7cfe8;
+}
+
+tr.clicked td {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+	background-color: #ffcfe8;
+}
+
+td.label {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+    color: #000000;
+    background-color:#ace27d;
+    padding: 0.2em;	
+}
+
+td a, td a:visited {
+	text-decoration:none;
+	color:#000000;
+}
+td a:hover {
+	text-decoration:underline;
+	color:#FFFFFF;
+}
+
+a.small_action {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+    color: #000000;
+	text-decoration:none;
+}
+
+.display_block {
+	noborder: 1px dashed #CCC;
+	background: #79b;
+	padding:0.25em;
+}
+div#TopPane .display_block {
+	background: #f0eee0;
+	padding:0.25em;
+	text-align:center;
+}
+div#TopPane label {
+	color:#000;
+	background: #f0eee0;
+}
+
+div#TopPane td {
+	color:#000;
+	background: #f0eee0;
+}
+
+.loading {
+	noborder: 1px dashed #CCC;
+	background: #b9c1c8;
+	padding:0.25em;
+}
+
+label {
+	font-family:Georgia, "Times New Roman", Times, serif;
+	color:#FFFFFF;
+	text-align:right;
+}
+
+input.textSearch {
+	border:1px solid #333;
+	noheight:1.2em;
+	font-size:0.8em;
+	font-family:Verdana, Arial, Helvetica, sans-serif;
+	color:#000000;
+}
+
+/* By Rom */
+.csvimport_createobj {
+    color: #AA0000;
+    background-color:#EEEEEE;
+}
+.csvimport_error {
+    font-weight: bold;
+    color: #FF0000;
+    background-color:#EEEEEE;
+}
+.csvimport_warning {
+    color: #CC8888;
+    background-color:#EEEEEE;
+}
+.csvimport_ok {
+    color: #00000;
+    background-color:#BBFFBB;
+}
+.csvimport_reconkey {
+    font-style: italic;
+    color: #888888;
+    background-color:#FFFFF;
+}
+.csvimport_extreconkey {
+    color: #888888;
+    background-color:#FFFFFF;
+}
+
+.treeview, .treeview ul { 
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}	
+
+.treeview li { 
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+	font-size:0.9em;
+}
+
+ul.dir li {
+	padding: 2px 0 0 16px;
+}
+
+.treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; }
+.treeview .collapsable { background-image: url(/images/tv-collapsable.gif); }
+.treeview .expandable { background-image: url(/images/tv-expandable.gif); }
+.treeview .last { background-image: url(/images/tv-item-last.gif); }
+.treeview .lastCollapsable { background-image: url(/images/tv-collapsable-last.gif); }
+.treeview .lastExpandable { background-image: url(/images/tv-expandable-last.gif); }
+
+#Header { padding: 0; background:#ccc url(/images/bandeau2.gif) repeat-x center;}
+div.iTopLogo {
+	background:url(/images/iTop.gif) no-repeat center;
+	width:100px;
+	height:56px;
+}
+div.iTopLogo span {
+	display:none;
+}
+
+#MySplitter {
+	/* Height is set to match window size in $().ready() below */
+	border:0px;
+	margin:4px;
+	padding:0px;
+	min-width: 100px;	/* Splitter can't be too thin ... */
+	min-height: 100px;	/* ... or too flat */
+}
+#LeftPane {
+	background: #f0eee0;
+	padding: 4px;
+	overflow: auto;		/* Scroll bars appear as needed */
+	color:#666;
+}
+#TopPane {				/* Top nested in right pane */
+	background: #f0eee0;
+	padding: 4px;
+	height: 150px;		/* Initial height */
+	min-height: 75px;	/* Minimum height */
+	overflow: auto;	
+	color:#666;
+}
+#RightPane {			/* Bottom nested in right pane */
+	background: #79b;
+	height:150px;		/* Initial height */
+	min-height:130px;
+	no.padding:15px;
+	no.margin:10px;
+	overflow:auto;
+	color:#fff;
+}
+
+#BottomPane {			/* Bottom nested in right pane */
+	background: #79b;
+	padding: 4px;
+	overflow: auto;
+	color:#fff;
+}
+#MySplitter .vsplitbar {
+	width: 7px;
+	height: 50px;
+	background: #68a url(/images/vgrabber2.gif) no-repeat  center;
+}
+#MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover {
+	background: #68a url(/images/vgrabber2_active.gif) no-repeat  center;
+}
+#MySplitter .hsplitbar {
+	height: 8px;
+	background: #68a url(/images/hgrabber2.gif) no-repeat center;
+}
+#MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover {
+	background: #68a url(/images/hgrabber2_active.gif) no-repeat center;
+}

+ 117 - 0
css/date.picker.css

@@ -0,0 +1,117 @@
+
+
+table.jCalendar {
+	border: 1px solid #000;
+	background: #aaa;
+    border-collapse: separate;
+    border-spacing: 2px;
+}
+table.jCalendar th {
+	background: #333;
+	color: #fff;
+	font-weight: bold;
+	padding: 3px 5px;
+}
+table.jCalendar td {
+	background: #ccc;
+	color: #000;
+	padding: 3px 5px;
+	text-align: center;
+}
+table.jCalendar td.other-month {
+	background: #ddd;
+	color: #aaa;
+}
+table.jCalendar td.today {
+	background: #666;
+	color: #fff;
+}
+table.jCalendar td.selected {
+	background: #f66;
+	color: #fff;
+}
+table.jCalendar td.selected:hover {
+	background: #f33;
+	color: #fff;
+}
+table.jCalendar td:hover, table.jCalendar td.dp-hover {
+	background: #fff;
+	color: #000;
+}
+table.jCalendar td.disabled, table.jCalendar td.disabled:hover {
+	background: #bbb;
+	color: #888;
+}
+
+/* For the popup */
+
+/* NOTE - you will probably want to style a.dp-choose-date - see how I did it in demo.css */
+
+div.dp-popup {
+	position: relative;
+	background: #ccc;
+	font-size: 10px;
+	font-family: arial, sans-serif;
+	padding: 2px;
+	width: 171px;
+	line-height: 1.2em;
+}
+div#dp-popup {
+	position: absolute;
+	z-index: 199;
+}
+div.dp-popup h2 {
+	font-size: 12px;
+	text-align: center;
+	margin: 2px 0;
+	padding: 0;
+}
+a#dp-close {
+	font-size: 11px;
+	padding: 4px 0;
+	text-align: center;
+	display: block;
+}
+a#dp-close:hover {
+	text-decoration: underline;
+}
+div.dp-popup a {
+	color: #000;
+	text-decoration: none;
+	padding: 3px 2px 0;
+}
+div.dp-popup div.dp-nav-prev {
+	position: absolute;
+	top: 2px;
+	left: 4px;
+	width: 100px;
+}
+div.dp-popup div.dp-nav-prev a {
+	float: left;
+}
+/* Opera needs the rules to be this specific otherwise it doesn't change the cursor back to pointer after you have disabled and re-enabled a link */
+div.dp-popup div.dp-nav-prev a, div.dp-popup div.dp-nav-next a {
+	cursor: pointer;
+}
+div.dp-popup div.dp-nav-prev a.disabled, div.dp-popup div.dp-nav-next a.disabled {
+	cursor: default;
+}
+div.dp-popup div.dp-nav-next {
+	position: absolute;
+	top: 2px;
+	right: 4px;
+	width: 100px;
+}
+div.dp-popup div.dp-nav-next a {
+	float: right;
+}
+div.dp-popup a.disabled {
+	cursor: default;
+	color: #aaa;
+}
+div.dp-popup td {
+	cursor: pointer;
+}
+div.dp-popup td.disabled {
+	cursor: default;
+}

+ 165 - 0
css/default.css

@@ -0,0 +1,165 @@
+/* CSS Document */
+body {
+    font-family: Verdana, Arial, Helevtica;
+    font-size: smaller;
+    background-color: #ffffff;
+    color:#000000;
+	margin: 0;			/* Remove body margin/padding */
+	padding: 0;
+	overflow: hidden;	/* Remove scroll bars on browser window */
+}
+
+table {
+	border: 1px solid #000000;
+}
+
+.raw_output {
+    font-family: Courier-New, Courier, Arial, Helevtica;
+    font-size: smaller;
+    background-color: #eeeeee;
+    color: #000000;
+    border: 1px dashed #000000;
+    padding: 0.25em;
+    margin-top: 1em;
+}
+
+th {
+    font-family: Verdana, Arial, Helevtica;
+    font-size: smaller;
+    color: #000000;
+    background-color:#E1DEB5;	
+}
+
+td {
+    font-family: Verdana, Arial, Helevtica;
+    font-size: smaller;
+}
+
+td.label {
+    font-family: Verdana, Arial, Helevtica;
+    font-size: smaller;
+    color: #000000;
+    background-color:#E1DEB5;
+    padding: 0.2em;	
+}
+
+a.small_action {
+    font-family: Verdana, Arial, Helvetica;
+    font-size: smaller;
+    color: #000000;
+	text-decoration:none;
+}
+
+.display_block {
+	border: 1px dashed #CCC;
+	background: #CFC;
+	padding:0.25em;
+}
+
+.loading {
+	border: 1px dashed #CCC;
+	background: #FCC;
+	padding:0.25em;
+}
+
+/* By Rom */
+.csvimport_createobj {
+    color: #AA0000;
+    background-color:#EEEEEE;
+}
+.csvimport_error {
+    font-weight: bold;
+    color: #FF0000;
+    background-color:#EEEEEE;
+}
+.csvimport_warning {
+    color: #CC8888;
+    background-color:#EEEEEE;
+}
+.csvimport_ok {
+    color: #00000;
+    background-color:#BBFFBB;
+}
+.csvimport_reconkey {
+    font-style: italic;
+    color: #888888;
+    background-color:#FFFFF;
+}
+.csvimport_extreconkey {
+    color: #888888;
+    background-color:#FFFFFF;
+}
+
+.treeview, .treeview ul { 
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}	
+
+.treeview li { 
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+}
+
+ul.dir li { padding: 2px 0 0 16px; }
+
+.treeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; }
+.treeview .collapsable { background-image: url(../images/tv-collapsable.gif); }
+.treeview .expandable { background-image: url(../images/tv-expandable.gif); }
+.treeview .last { background-image: url(../images/tv-item-last.gif); }
+.treeview .lastCollapsable { background-image: url(../images/tv-collapsable-last.gif); }
+.treeview .lastExpandable { background-image: url(../images/tv-expandable-last.gif); }
+
+#MySplitter {
+	/* Height is set to match window size in $().ready() below */
+	border:0px;
+	margin:4px;
+	padding:0px;
+	min-width: 100px;	/* Splitter can't be too thin ... */
+	min-height: 100px;	/* ... or too flat */
+}
+#LeftPane {
+	background: #f0eee0;
+	padding: 4px;
+	overflow: auto;		/* Scroll bars appear as needed */
+	color:#666;
+}
+#TopPane {				/* Top nested in right pane */
+	background: #f0eee0;
+	padding: 4px;
+	height: 150px;		/* Initial height */
+	min-height: 75px;	/* Minimum height */
+	overflow: auto;	
+	color:#666;
+}
+#RightPane {			/* Bottom nested in right pane */
+	background: #79b;
+	height:150px;		/* Initial height */
+	min-height:130px;
+	no.padding:15px;
+	no.margin:10px;
+	overflow:auto;
+	color:#fff;
+}
+
+#BottomPane {			/* Bottom nested in right pane */
+	background: #79b;
+	padding: 4px;
+	overflow: auto;
+	color:#fff;
+}
+#MySplitter .vsplitbar {
+	width: 7px;
+	height: 50px;
+	background: #68a url(../images/vgrabber2.gif) no-repeat  center;
+}
+#MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover {
+	background: #68a url(../images/vgrabber2_active.gif) no-repeat  center;
+}
+#MySplitter .hsplitbar {
+	height: 8px;
+	background: #68a url(../images/hgrabber2.gif) no-repeat center;
+}
+#MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover {
+	background: #68a url(../images/hgrabber2_active.gif) no-repeat center;
+}

+ 40 - 0
css/jqModal.css

@@ -0,0 +1,40 @@
+/* jqModal base Styling courtesy of;
+	Brice Burgess <bhb@iceburg.net> */
+
+/* The Window's CSS z-index value is respected (takes priority). If none is supplied,
+	the Window's z-index value will be set to 3000 by default (in jqModal.js). You
+	can change this value by either;
+	  a) supplying one via CSS
+	  b) passing the "zIndex" parameter. E.g.  (window).jqm({zIndex: 500}); */
+	
+.jqmWindow {
+    display: none;
+    
+    position: fixed;
+    top: 10%;
+    left: 20%;
+    
+    margin-left: -100px;
+    width: 800px;
+    
+    background-color: #FFF;
+    color: #333;
+    border: 1px solid black;
+    padding: 12px;
+}
+
+.jqmOverlay { background-color: #333; }
+
+/* Background iframe styling for IE6. Prevents ActiveX bleed-through (<select> form elements, etc.) */
+* removed.iframe.jqm {position:absolute;top:0;left:0;z-index:-1;
+	width: expression(this.parentNode.offsetWidth+'px');
+	height: expression(this.parentNode.offsetHeight+'px');
+}
+
+/* Fixed posistioning emulation for IE6
+     Star selector used to hide definition from browsers other than IE6
+     For valid CSS, use a conditional include instead */
+* removed.html .jqmWindow {
+     position: absolute;
+     top: expression((document.documentElement.scrollTop || document.body.scrollTop) + Math.round(17 * (document.documentElement.offsetHeight || document.body.clientHeight) / 100) + 'px');
+}

+ 46 - 0
css/jquery.autocomplete.css

@@ -0,0 +1,46 @@
+.ac_results {
+	padding: 0px;
+	border: 1px solid WindowFrame;
+	background-color: Window;
+	overflow: hidden;
+}
+
+.ac_results ul {
+	width: 100%;
+	list-style-position: outside;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.ac_results iframe {
+	display:none;/*sorry for IE5*/
+	display/**/:block;/*sorry for IE5*/
+	position:absolute;
+	top:0;
+	left:0;
+	z-index:-1;
+	filter:mask();
+	width:3000px;
+	height:3000px;
+}
+
+.ac_results li {
+	margin: 0px;
+	padding: 2px 5px;
+	cursor: pointer;
+	display: block;
+	width: 100%;
+	font: menu;
+	font-size: 12px;
+	overflow: hidden;
+}
+
+.ac_loading {
+	background : Window url('../images/indicator.gif') right center no-repeat;
+}
+
+.ac_over {
+	background-color: Highlight;
+	color: HighlightText;
+}

+ 15 - 0
css/jquery.tabs-ie.css

@@ -0,0 +1,15 @@
+/*
+Tabs - additional IE specific bug fixes
+
+Recommended usage (Conditional Comments):
+<!--[if lte IE 7]>
+<link rel="stylesheet" href="tabs_ie.css" type="text/css" media="projection, screen" />
+<![endif]-->
+
+*/
+.tabs-nav { /* auto clear */
+    display: inline-block;
+}
+.tabs-nav .tabs-disabled a {
+    filter: alpha(opacity=40);
+}

+ 76 - 0
css/jquery.tabs.css

@@ -0,0 +1,76 @@
+/* Caution! Ensure accessibility in print and other media types... */
+@media projection, screen { /* Use class for showing/hiding tab content, so that visibility can be better controlled in different media types... */
+    .tabs-hide {
+        display: none;
+    }
+}
+
+/* Hide useless elements in print layouts... */
+@media print {
+    .tabs-nav {
+        display: none;
+    }
+}
+
+/* Skin */
+.tabs-nav {
+    list-style: none;
+    margin: 0;
+    padding: 0 0 0 4px;
+}
+.tabs-nav:after { /* clearing without presentational markup, IE gets extra treatment */
+    display: block;
+    clear: both;
+    content: " ";
+}
+.tabs-nav li {
+    float: left;
+    margin: 0 0 0 1px;
+}
+.tabs-nav a {
+    display: block;
+    position: relative;
+    top: 1px;
+    z-index: 2;
+    padding: 6px 10px 0;
+    width: 64px;
+    height: 18px;
+    color: #27537a;
+    font-size: 12px;
+    font-weight: bold;
+    line-height: 1.2;
+    text-align: center;
+    text-decoration: none;
+    background: url(../images/tab.png) no-repeat;
+}
+.tabs-nav .tabs-selected a {
+    padding-top: 7px;
+    color: #000;
+}
+.tabs-nav .tabs-selected a, .tabs-nav a:hover, .tabs-nav a:focus, .tabs-nav a:active {
+    background-position: 0 -50px;
+    outline: 0; /* @ Firefox, switch off dotted border */
+}
+.tabs-nav .tabs-disabled a:hover, .tabs-nav .tabs-disabled a:focus, .tabs-nav .tabs-disabled a:active {
+    background-position: 0 0;
+}
+.tabs-nav .tabs-selected a:link, .tabs-nav .tabs-selected a:visited,
+.tabs-nav .tabs-disabled a:link, .tabs-nav .tabs-disabled a:visited { /* @ Opera, use pseudo classes otherwise it confuses cursor... */
+    cursor: text;
+}
+.tabs-nav a:hover, .tabs-nav a:focus, .tabs-nav a:active { /* @ Opera, we need to be explicit again here now... */
+    cursor: pointer;
+}
+.tabs-nav .tabs-disabled {
+    opacity: .4;
+}
+.tabs-container {
+    border-top: 1px solid #97a5b0;
+    padding: 1em 8px;
+    background: #fff; /* declare background color for container to avoid distorted fonts in IE while fading */
+}
+/* Uncomment this if you want a little spinner to be shown next to the tab title while an Ajax tab gets loaded
+.tabs-loading span {
+    padding: 0 0 0 20px;
+    background: url(loading.gif) no-repeat 0 50%;
+}*/

+ 47 - 0
css/jquery.treeview.css

@@ -0,0 +1,47 @@
+.treeview ul { background-color: white; }
+
+.treeview, .treeview ul { 
+	padding: 0;
+	margin: 0;
+	list-style: none;
+}
+
+.treeview div.hitarea {
+	height: 15px;
+	width: 15px;
+	margin-left: -15px;
+	float: left;
+	cursor: pointer;
+}
+/* fix for IE6 */
+* html div.hitarea {
+	background: #fff;
+	filter: alpha(opacity=0);
+	display: inline;
+	float:none;
+}
+
+.treeview li { 
+	margin: 0;
+	padding: 3px 0pt 3px 16px;
+}
+
+.treeview a.selected {
+	background-color: #eee;
+}
+
+#treecontrol { margin: 1em 0; }
+
+.treeview .hover { color: red; cursor: pointer; }
+
+.treeview li { background: url(/images/tv-item.gif) 0 0 no-repeat; }
+.treeview .collapsable { background-image: url(/images/tv-collapsable.gif); }
+.treeview .expandable { background-image: url(/images/tv-expandable.gif); }
+.treeview .last { background-image: url(/images/tv-item-last.gif); }
+.treeview .lastCollapsable { background-image: url(/images/tv-collapsable-last.gif); }
+.treeview .lastExpandable { background-image: url(/images/tv-expandable-last.gif); }
+
+.filetree li { padding: 3px 0 1px 16px; }
+.filetree span.folder, .filetree span.file { padding-left: 16px; display: block; height: 15px; }
+.filetree span.folder { background: url(/images/folder.gif) 0 0 no-repeat; }
+.filetree span.file { background: url(/images/file.gif) 0 0 no-repeat; }

+ 633 - 0
css/light-grey.css

@@ -0,0 +1,633 @@
+/* CSS Document */
+body {
+    font-family: Tahoma, Verdana, Arial, Helevtica;
+    font-size: 8pt;
+    background-color: #fff;
+    color:#000000;
+	margin: 0;			/* Remove body margin/padding */
+	padding: 0;
+	overflow: hidden;	/* Remove scroll bars on browser window */
+}
+
+.raw_output {
+    font-family: Courier-New, Courier, Arial, Helevtica;
+    font-size: 8pt;
+    background-color: #eeeeee;
+    color: #000000;
+    border: 1px dashed #000000;
+    padding: 0.25em;
+    margin-top: 1em;
+}
+
+h1 {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: 10pt;
+    color: #000;
+    font-weight:10px;
+}
+.hilite {
+    color: #d81515;
+}
+table.listResults {
+    padding: 0px;
+	border-top: 3px solid #f6f6f1;
+	border-left: 3px solid #f6f6f1;
+	border-bottom: 3px solid #e6e6e1;  
+	border-right: 3px solid #e6e6e1;
+	width: 100%;
+}
+
+table.listResults td {
+	padding: 2px;
+}
+
+table.listContainer {
+    border: 0;
+	padding: 0;
+	margin:0;
+	width: 97%;
+}
+
+tr.containerHeader, tr.containerHeader td {
+	background: transparent;
+} 
+
+tr.even td {
+	background-color: #f9f9f1;
+} 
+
+tr td.hover, tr.even td.hover, .hover a, .hover a:visited, .hover a:hover {
+	background-color: #E8FFD3;
+}
+
+th {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: 8pt;
+    color: #d81515;
+    height:20px;
+    background: #f6f6f1 url(../images/grey-header.gif) bottom repeat-x;	
+}
+th.header {
+	cursor: pointer;
+}
+
+th.headerSortUp, th.headerSortDown {
+	
+	text-decoration: underline;
+	cursor: pointer;
+}
+
+td {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: 8pt;
+    color:#696969;
+	background-color: #ffffff;
+	padding: 0px;
+}
+
+tr.clicked td {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: smaller;
+	background-color: #ffcfe8;
+}
+
+td.label {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: 8pt;
+    color: #000000;
+    background-color:#f6f6f6;
+    padding: 0.25em;
+    font-weight:bold;	
+}
+
+td a, td a:visited {
+	text-decoration:none;
+	color:#000000;
+	padding-left:14px;
+	background: url(../images/red-arrow.gif) no-repeat left;
+}
+td a:hover {
+	text-decoration:underline;
+	color:#d81515;
+	padding-left:14px;
+	background: url(../images/red-arrow.gif) no-repeat left;
+}
+
+td a.no-arrow, td a.no-arrow:visited {
+	text-decoration:none;
+	color:#000000;
+	padding-left:0px;
+	background: inherit;
+}
+td a.no-arrow:hover {
+	text-decoration:underline;
+	color:#d81515;
+	padding-left:0px;
+	background: inherit;
+}
+
+a.small_action {
+    font-family: Tahoma, Verdana, Arial, Helvetica;
+    font-size: 8pt;
+    color: #000000;
+	text-decoration:none;
+}
+
+.display_block {
+	noborder: 1px dashed #CCC;
+	padding:0.25em;
+}
+div#TopPane .display_block {
+	background: #fff;
+	padding:0.25em;
+	text-align:center;
+}
+div#TopPane label {
+	color:#000;
+	background: #fff;
+}
+
+div#TopPane td {
+	color:#000;
+	background: #fff;
+}
+
+.actions_details {
+	float:right;
+	margin-top:10px;
+	margin-right:10px;
+	padding-left: 5px;
+	padding-top: 2px;
+	padding-bottom: 2px;
+	background: url(../images/actions_left.png) no-repeat left;
+}
+
+.actions_details span{
+	background: url(../images/actions_right.png) no-repeat right;
+	color: #fff;
+	font-weight: bold;
+	padding-top: 2px;
+	padding-bottom: 2px;
+	padding-right: 12px;
+}
+.actions_details a {
+	text-decoration:none;
+}
+.loading {
+	noborder: 1px dashed #CCC;
+	background: #b9c1c8;
+	padding:0.25em;
+}
+
+input.textSearch {
+	border:1px solid #000;
+	noheight:1.2em;
+	font-size:8pt;
+	font-family:Tahoma,Verdana, Arial, Helvetica, sans-serif;
+	color:#000000;
+}
+
+/* By Rom */
+.csvimport_createobj {
+    color: #AA0000;
+    background-color:#EEEEEE;
+}
+.csvimport_error {
+    font-weight: bold;
+    color: #FF0000;
+    background-color:#EEEEEE;
+}
+.csvimport_warning {
+    color: #CC8888;
+    background-color:#EEEEEE;
+}
+.csvimport_ok {
+    color: #00000;
+    background-color:#BBFFBB;
+}
+.csvimport_reconkey {
+    font-style: italic;
+    color: #888888;
+    background-color:#FFFFF;
+}
+.csvimport_extreconkey {
+    color: #888888;
+    background-color:#FFFFFF;
+}
+
+ul.dir {
+    list-style:none;
+}	
+
+ul.dir ul {
+	padding: 8px 0px 8px 16px;
+	margin:0;
+    list-style:none;
+    border: 0;
+}	
+
+ul.dir li a,  ul.dir li a:visited, ul.dir li a:hover{
+    noborder-top: 1px solid #8b8b8b;
+    padding: 4px 0px 0px 16px;   	
+	font-size:8pt;
+    background: url(../images/green-square.gif) no-repeat bottom left;
+    color:#83b217;
+    font-weight:bold;
+	text-decoration:none;
+}
+
+ul.dir li li a, ul.dir li li a:visited, ul.dir li li a:hover { 
+	color:#8b8b8b;
+	text-decoration:none;
+	margin: 0;
+	padding: 0px 0pt 0px 16px;
+	font-size:8pt;
+    background: url(../images/mini-arrow-green.gif) no-repeat left;
+    font-weight:normal;
+    border: 0;
+}
+
+a.CollapsibleLabel, td a.CollapsibleLabel { 
+	margin: 0;
+	padding: 0px 0pt 0px 16px;
+	font-size:8pt;
+	text-decoration:none;
+	color:#8b8b8b;
+    background: url(../images/mini-arrow-green.gif) no-repeat left;
+}
+
+a.CollapsibleLabel.open,  td a.CollapsibleLabel.open { 
+	margin: 0;
+	padding: 0px 0pt 0px 16px;
+	font-size:8pt;
+	text-decoration:none;
+	color:red;
+    background: url(../images/mini-arrow-green-open.gif) no-repeat left;
+}
+
+.page_header {
+	background-color:#f6f6f1;
+	padding:5px;
+}
+
+.notreeview li { background: url(../images/tv-item.gif) 0 0 no-repeat; }
+.notreeview .collapsable { background-image: url(../images/tv-collapsable.gif); }
+.notreeview .expandable { background-image: url(../images/tv-expandable.gif); }
+.notreeview .last { background-image: url(../images/tv-item-last.gif); }
+.notreeview .lastCollapsable { background-image: url(../images/tv-collapsable-last.gif); }
+.notreeview .lastExpandable { background-image: url(../images/tv-expandable-last.gif); }
+
+#Header { padding: 0; background:#ccc url(../images/bandeau3.gif) repeat-x center;}
+div.iTopLogo {
+	background:url(../images/iTop2.gif) no-repeat center;
+	width:150px;
+	height:56px;
+}
+div.iTopLogo span {
+	display:none;
+}
+
+#MySplitter {
+	/* Height is set to match window size in $().ready() below */
+	border:0px;
+	margin:4px;
+	padding:0px;
+	min-width: 100px;	/* Splitter can't be too thin ... */
+	min-height: 100px;	/* ... or too flat */
+}
+#LeftPane {
+	background: #f6f6f1 url(../images/left-border.gif) repeat-y top left;
+	padding: 16px;
+	overflow: auto;		/* Scroll bars appear as needed */
+	color:#000;
+}
+#TopPane {				/* Top nested in right pane */
+	background: #fff;
+	padding: 4px;
+	height: 150px;		/* Initial height */
+	min-height: 75px;	/* Minimum height */
+	overflow: auto;	
+	color:#000;
+}
+#RightPane {			/* Bottom nested in right pane */
+	background: #fff;
+	height:150px;		/* Initial height */
+	min-height:130px;
+	no.padding:15px;
+	no.margin:10px;
+	overflow:auto;
+	color:#000000;
+	padding-left:10px;
+}
+
+#BottomPane {			/* Bottom nested in right pane */
+	background: #fff;
+	padding: 4px;
+	overflow: auto;
+	color:#000;
+}
+#MySplitter .vsplitbar {
+	width: 9px;
+	height: 50px;
+	background: url(../images/vsplitter-grey.gif) repeat top;
+}
+#MySplitter .vsplitbar.active, #MySplitter .vsplitbar:hover {
+	background-color: #fff;
+}
+#MySplitter .hsplitbar {
+	height: 8px;
+	background-color: #fff;
+}
+#MySplitter .hsplitbar.active, #MySplitter .hsplitbar:hover {
+	background-color: #fff;
+}
+#OrganizationSelection {
+    padding:5px 0px 16px 40px;
+}
+
+/* jdMenu popup menus */
+ul.jd_menu, 
+ul.jd_menu_vertical {
+	margin: 0px;
+	padding: 0px;
+	list-style-type: none;
+}
+ul.jd_menu ul,
+ul.jd_menu_vertical ul {
+	display: none;
+}
+ul.jd_menu li {
+	float: left;
+}
+
+/* -- Sub-Menus -- */
+ul.jd_menu ul,
+ul.jd_menu_vertical ul {
+	position: absolute;
+	display: none;
+	list-style-type: none;
+	margin: 0px;
+	padding: 0px;
+	z-index: 10000;
+}
+ul.jd_menu ul li,
+ul.jd_menu_vertical ul li {
+	float: none;
+	margin: 0px;
+}
+
+/* jdMenu popup menus styling */
+div.jd_menu_itop {
+	height:19px;
+	float:right;
+	display:inline;
+	width:70px; /* Nasty work-around for IE... en attendant mieux */
+	padding-left: 5px;
+	background: url(../images/actions_left.png) no-repeat left;
+}
+
+ul.jd_menu_itop {
+	height: 19px;
+	padding-right:16px;
+	background: url(../images/actions_right.png) no-repeat right;
+	font-weight: bold;
+}
+
+ul.jd_menu_vertical {
+	width: 200px;
+	height: auto;
+	clear: both;
+	background: url(gradient-vertical.png) repeat-x;
+	background-color: #A5AFB8;
+}
+
+
+ul.jd_menu_itop a, 
+ul.jd_menu_itop a:active,
+ul.jd_menu_itop a:link,
+ul.jd_menu_itop a:visited {
+	text-decoration: none;
+	color: #FFF;
+}
+ul.jd_menu_itop ul li a,
+ul.jd_menu_itop ul li a:active,
+ul.jd_menu_itop ul li a:link,
+ul.jd_menu_itop ul li a:visited {
+	color: #000;
+}
+ul.jd_menu_itop li {
+	font-family: Tahoma, sans-serif;
+	font-size: 11px;
+	padding: 2px 6px 4px 6px;
+	cursor: pointer;
+	white-space: nowrap;
+	color: #FFF;
+}
+ul.jd_menu_itop li.jd_menu_active_menubar,
+ul.jd_menu_itop li.jd_menu_hover_menubar {
+	color: #FFF;
+}
+
+ul.jd_menu_vertical li.jd_menu_active_menubar,
+ul.jd_menu_vertical li.jd_menu_hover_menubar {
+	padding-left: 6px;
+	padding-top: 1px;
+	border-top: 1px solid #70777D;
+	border-left: 0px;
+	border-right: 0px;
+}
+
+ul.jd_menu_itop ul {
+	background: #d81515;
+	border: 1px solid #70777D;
+}
+ul.jd_menu_itop ul li {
+	padding: 3px 10px 3px 4px;
+	background: #FFF;
+	border: none;
+	color: #000;
+}
+ul.jd_menu_itop ul li.jd_menu_active,
+ul.jd_menu_itop ul li.jd_menu_hover {
+	background: #d81515;
+	padding-top: 2px;
+	border-top: 1px solid #ABB5BC;
+	padding-bottom: 2px;
+	border-bottom: 1px solid #929AA1;
+	color: #FFF;
+}
+
+ul.jd_menu_itop li ul li a {
+	padding: 0;
+	background: #fff;
+	padding-left:0;
+}
+
+ul.jd_menu_itop ul li.jd_menu_active a.jd_menu_active,
+ul.jd_menu_itop ul li.jd_menu_hover a.jd_menu_hover {
+	color: #FFF;
+	background: #d81515;
+	padding-left:0;
+}
+
+.wizHeader {
+	background: #83b217;
+	padding: 15px;
+}
+.wizSeparator {
+	float:left;
+	background: #83b217;
+	height: 29px;
+	margin-top: 5px;
+}
+div.wizActiveStep {
+	height: 29px;
+	background: url(/images/wizActiveStepLeft.gif) no-repeat left;
+	color: #d81515;
+	padding-left: 8px;
+	margin-top: 5px;
+	vertical-align: middle;
+	float: left;
+}
+div.wizActiveStep span {
+	height: 29px;
+	background: url(/images/wizActiveStepRight.gif) no-repeat top right;
+	padding-right: 8px;
+	padding-top: 8px;
+	float:left;
+}
+
+div.wizStep {
+	height: 29px;
+	background: url(/images/wizStepLeft.gif) no-repeat left;
+	padding-left: 8px;
+	vertical-align: middle;
+	margin-top: 5px;
+	float:left;
+}
+div.wizStep span {
+	height: 29px;
+	background: url(/images/wizStepRight.gif) no-repeat top right;
+	padding-right: 8px;
+	padding-top: 8px;
+	float:left;
+}
+.wizContainer {
+	border: 5px solid #83b217;
+	background: #e8f3cf;
+	padding: 5px;
+}
+
+.alignRight {
+	text-align: right;
+	padding: 3px;
+}
+
+.alignLeft {
+	text-align: left;
+	padding: 3px;
+}
+
+.red {
+	background-color: #ff6000;
+	color: #000;
+}
+
+th.red {
+	background: url(../images/red-header.gif) bottom left repeat-x;
+	color: #000;
+}
+
+.green {
+	background-color: #00cc00;
+	color: #000;
+}
+th.green {
+	background: url(../images/green-header.gif) bottom left repeat-x;
+	color: #000;
+}
+
+.orange {
+	background-color: #ffde00;
+	color: #000;
+}
+
+th.orange {
+	background: url(../images/orange-header.gif) bottom left repeat-x;
+	color: #000;
+}
+
+/* For Date Picker: Creates a little calendar icon
+ * instead of a text link for "Choose date"
+ */
+a.dp-choose-date, a.dp-choose-date:hover {
+	float: left;
+	width: 16px;
+	height: 16px;
+	padding: 0;
+	margin: 5px 3px 0;
+	display: block;
+	text-indent: -2000px;
+	overflow: hidden;
+	background: url(../images/calendar.png) no-repeat; 
+}
+a.dp-choose-date.dp-disabled {
+	background-position: 0 -20px;
+	cursor: default;
+}
+/* For Date Picker: makes the input field shorter once the date picker code
+ * has run (to allow space for the calendar icon)
+ */
+input.dp-applied {
+	width: 140px;
+	float: left;
+}
+
+/* For search forms */
+.SearchDrawer {
+	background: #83b217 url(../images/green-corner.png) top left no-repeat;
+	color: #fff;
+	padding: 10px;
+	margin: 0;
+}
+.SearchDrawer form table tbody tr td {
+	background: #83b217;
+	color: #fff;
+}
+.SearchDrawer label {
+	background: #83b217;
+	color: #fff;
+	padding-left: 10px;
+}
+.SearchDrawer h1 {
+	color: #fff;
+}
+.DrawerClosed {
+	display: none;
+}
+.DrawerHandle {
+	margin: 0;
+	padding: 5px;
+	background: #fff url(../images/drawer-handle.gif) bottom no-repeat;
+	color: #fff;
+	cursor: pointer;
+	text-align: center;
+	/* center the block */
+	width: 100px;
+	margin-left: auto;
+	margin-right: auto;
+	margin-top: 0;
+	margin-bottom: 0;
+	display: block;
+}
+
+div.HRDrawer {
+	height: 5px;
+	width: 100%;
+	margin: 0;
+	background-color:  #83b217;
+	margin: 0;
+	padding: 0;
+	border: 0;
+	display: block;
+}

BIN
images/Resize of Resize of erwanIncidents.jpg


BIN
images/Resize of Resize of erwanNetwork.jpg


Some files were not shown because too many files changed in this diff