Ver código fonte

Finalized the email error management:
- errors in OQL
- no recipient
- smtp server response (windows only)
- test option (status)
- test page

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

romainq 15 anos atrás
pai
commit
819dc9bbcf

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

@@ -332,15 +332,6 @@ class MyHelpers
 	}
 
 	/**
-	* 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 !
 	*/

+ 158 - 53
core/action.class.inc.php

@@ -1,5 +1,7 @@
 <?php
 
+require_once('../core/email.class.inc.php');
+
 /**
  * A user defined action, to customize the application  
  *
@@ -34,6 +36,7 @@ abstract class Action extends cmdbAbstractObject
 		//MetaModel::Init_InheritAttributes();
 		MetaModel::Init_AddAttribute(new AttributeString("name", array("label"=>"Name", "description"=>"label", "allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeString("description", array("label"=>"Description", "description"=>"one line description", "allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("label"=>"Status", "description"=>"In production or ?", "allowed_values"=>new ValueSetEnum(array('test'=>'Being tested' ,'enabled'=>'In production', 'disabled'=>'Inactive')), "sql"=>"status", "default_value"=>"test", "is_null_allowed"=>false, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("related_triggers", array("label"=>"Related Triggers", "description"=>"Triggers linked to this action", "linked_class"=>"lnkTriggerAction", "ext_key_to_me"=>"action_id", "ext_key_to_remote"=>"trigger_id", "allowed_values"=>null, "count_min"=>0, "count_max"=>0, "depends_on"=>array())));
 
 		//MetaModel::Init_InheritFilters();
@@ -41,14 +44,39 @@ abstract class Action extends cmdbAbstractObject
 		MetaModel::Init_AddFilterFromAttribute("description");
 
 		// Display lists
-		MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('name', 'description')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'status')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
 		// Search criteria
 //		MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
 	}
 
 	abstract public function DoExecute($oTrigger, $aContextArgs);
+
+	public function IsActive()
+	{
+		switch($this->Get('status'))
+		{
+			case 'enabled':
+			case 'test':
+				return true;
+
+			default:
+				return false;
+		}
+	}
+
+	public function IsBeingTested()
+	{
+		switch($this->Get('status'))
+		{
+			case 'test':
+				return true;
+
+			default:
+				return false;
+		}
+	}
 }
 
 /**
@@ -87,8 +115,8 @@ abstract class ActionNotification extends Action
 		MetaModel::Init_InheritFilters();
 
 		// Display lists
-		MetaModel::Init_SetZListItems('details', array('name', 'description')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'status')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
 		// Search criteria
 //		MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
@@ -128,6 +156,8 @@ class ActionEmail extends ActionNotification
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_InheritAttributes();
 
+		MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("label"=>"Test recipient", "description"=>"Detination in case status is set to \"Test\"", "allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
+
 		MetaModel::Init_AddAttribute(new AttributeString("from", array("label"=>"From", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("label"=>"Reply to", "description"=>"Will be sent into the email header", "allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeOQL("to", array("label"=>"To", "description"=>"Destination of the email", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
@@ -140,21 +170,36 @@ class ActionEmail extends ActionNotification
 		MetaModel::Init_InheritFilters();
 
 		// Display lists
-		MetaModel::Init_SetZListItems('details', array('name', 'description', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('name', 'to', 'subject')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list
 		// Search criteria
 //		MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
 	}
 
-	// args: a search object
-	// returns an array of emails
-	protected function FindRecipients($sAttCode, $aArgs)
+	// count the recipients found
+	protected $m_iRecipients;
+
+	// Errors management : not that simple because we need that function to be
+	// executed in the background, while making sure that any issue would be reported clearly
+	protected $m_aMailErrors; //array of strings explaining the issue
+
+	// returns a the list of emails as a string, or a detailed error description
+	protected function FindRecipients($sRecipAttCode, $aArgs)
 	{
-		$sOQL = $this->Get($sAttCode);
+		$sOQL = $this->Get($sRecipAttCode);
 		if (strlen($sOQL) == '') return '';
 
-		$oSearch = DBObjectSearch::FromOQL($sOQL);
+		try
+		{
+			$oSearch = DBObjectSearch::FromOQL($sOQL);
+		}
+		catch (OqlException $e)
+		{
+			$this->m_aMailErrors[] = "query syntax error for recipient '$sRecipAttCode'";
+			return $e->getMessage();
+		}
+
 		$sClass = $oSearch->GetClass();
 		// Determine the email attribute (the first one will be our choice)
 		foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
@@ -166,78 +211,138 @@ class ActionEmail extends ActionNotification
 				break;
 			}
 		}
+		if (!isset($sEmailAttCode))
+		{
+			$this->m_aMailErrors[] = "wrong target for recipient '$sRecipAttCode'";
+			return "The objects of the class '$sClass' do not have any email attribute";
+		}
 
 		$oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs);
 		$aRecipients = array();
 		while ($oObj = $oSet->Fetch())
 		{
 			$aRecipients[] = $oObj->Get($sEmailAttCode);
+			$this->m_iRecipients++;
 		}
 		return implode(', ', $aRecipients);
 	}
 
+
 	public function DoExecute($oTrigger, $aContextArgs)
 	{
-		// Determine recicipients
-		//
-		$sTo = $this->FindRecipients('to', $aContextArgs);
-		$sCC = $this->FindRecipients('cc', $aContextArgs);
-		$sBCC = $this->FindRecipients('bcc', $aContextArgs);
-
-		$sFrom = $this->Get('from');
-		$sReplyTo = $this->Get('reply_to');
+		$this->m_iRecipients = 0;
+		$this->m_aMailErrors = array();
+		$bRes = false; // until we do succeed in sending the email
+		try
+		{
+			// Determine recicipients
+			//
+			$sTo = $this->FindRecipients('to', $aContextArgs);
+			$sCC = $this->FindRecipients('cc', $aContextArgs);
+			$sBCC = $this->FindRecipients('bcc', $aContextArgs);
 
-		$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
-		$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
+			$sFrom = $this->Get('from');
+			$sReplyTo = $this->Get('reply_to');
+	
+			$sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
+			$sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
 
-		// To send HTML mail, the Content-type header must be set
-		$sHeaders  = 'MIME-Version: 1.0' . "\r\n";
-		$sHeaders .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
+			$oEmail = new Email();
 
-		// Additional headers
-		if (strlen($sFrom) > 0)
-		{
-			$sHeaders .= "From: $sFrom\r\n";
-			// This is required on Windows because otherwise I would get the error
-			// "sendmail_from" not set in php.ini" even if it is correctly working
-			// (apparently, once it worked the SMTP server won't claim anymore for it)
-			ini_set("sendmail_from", $sFrom);
-		}
-		if (strlen($sReplyTo) > 0)
-		{
-			$sHeaders .= "Reply-To: $sReplyTo\r\n";
-		}
-		if (strlen($sCC) > 0)
-		{
-			$sHeaders .= "Cc: $sCC\r\n";
+			if ($this->IsBeingTested())
+			{
+				$oEmail->SetSubject('TEST['.$sSubject.']');
+				$sTestBody = $sBody;
+				$sTestBody .= "<div style=\"border: dashed;\">\n";
+				$sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
+				$sTestBody .= "<p>The email should be sent with the following properties\n";
+				$sTestBody .= "<ul>\n";
+				$sTestBody .= "<li>TO: $sTo</li>\n";
+				$sTestBody .= "<li>CC: $sCC</li>\n";
+				$sTestBody .= "<li>BCC: $sBCC</li>\n";
+				$sTestBody .= "<li>From: $sFrom</li>\n";
+				$sTestBody .= "<li>Reply-To: $sReplyTo</li>\n";
+				$sTestBody .= "</ul>\n";
+				$sTestBody .= "</p>\n";
+				$sTestBody .= "</div>\n";
+				$oEmail->SetBody($sTestBody);
+				$oEmail->SetRecipientTO($this->Get('test_recipient'));
+				$oEmail->SetRecipientFrom($this->Get('test_recipient'));
+			}
+			else
+			{
+				$oEmail->SetSubject($sSubject);
+				$oEmail->SetBody($sBody);
+				$oEmail->SetRecipientTO($sTo);
+				$oEmail->SetRecipientCC($sCC);
+				$oEmail->SetRecipientBCC($sBCC);
+				$oEmail->SetRecipientFrom($sFrom);
+				$oEmail->SetRecipientReplyTo($sReplyTo);
+			}
+	
+			if (empty($this->m_aMailErrors))
+			{
+				if ($this->m_iRecipients == 0)
+				{
+					$this->m_aMailErrors[] = 'No recipient';
+				}
+				else
+				{
+					$this->m_aMailErrors = array_merge($this->m_aMailErrors, $oEmail->Send());
+				}
+			}
 		}
-		if (strlen($sBCC) > 0)
+		catch (Exception $e)
 		{
-			$sHeaders .= "Bcc: $sBCC\r\n";
+			$this->m_aMailErrors[] = $e->getMessage();
 		}
 
 		$oLog = new EventNotificationEmail();
-		if (mail($sTo, $sSubject, $sBody, $sHeaders))
+		if (empty($this->m_aMailErrors))
 		{
-			$oLog->Set('message', 'Notification sent');
+			if ($this->IsBeingTested())
+			{
+				$oLog->Set('message', 'TEST - Notification sent ('.$this->Get('test_recipient').')');
+			}
+			else
+			{
+				$oLog->Set('message', 'Notification sent');
+			}
 		}
 		else
 		{
-			$aLastError = error_get_last();
-			$oLog->Set('message', 'Mail could not be sent: '.$aLastError['message']);
-			//throw new CoreException('mail not sent', array('action'=>$this->GetKey(), 'to'=>$sTo, 'subject'=>$sSubject, 'headers'=>$sHeaders));
+			if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0)
+			{
+				$sError = implode(', ', $this->m_aMailErrors);
+			}
+			else
+			{
+				$sError = 'Unknown reason';
+			}
+			if ($this->IsBeingTested())
+			{
+				$oLog->Set('message', 'TEST - Notification was not sent: '.$sError);
+			}
+			else
+			{
+				$oLog->Set('message', 'Notification was not sent: '.$sError);
+			}
 		}
 
 		$oLog->Set('userinfo', UserRights::GetUser());
 		$oLog->Set('trigger_id', $oTrigger->GetKey());
 		$oLog->Set('action_id', $this->GetKey());
 		$oLog->Set('object_id', $aContextArgs['this->id']);
-		$oLog->Set('from', $sFrom);
-		$oLog->Set('to', $sTo);
-		$oLog->Set('cc', $sCC);
-		$oLog->Set('bcc', $sBCC);
-		$oLog->Set('subject', $sSubject);
-		$oLog->Set('body', $sBody);
+
+		// Note: we have to secure this because those values are calculated
+		// inside the try statement, and we would like to keep track of as
+		// many data as we could while some variables may still be undefined
+		if (isset($sTo))       $oLog->Set('to', $sTo);
+		if (isset($sCC))       $oLog->Set('cc', $sCC);
+		if (isset($sBCC))      $oLog->Set('bcc', $sBCC);
+		if (isset($sFrom))     $oLog->Set('from', $sFrom);
+		if (isset($sSubject))  $oLog->Set('subject', $sSubject);
+		if (isset($sBody))     $oLog->Set('body', $sBody);
 		$oLog->DBInsertNoReload();
 	}
 }

+ 17 - 4
core/dbobject.class.php

@@ -522,14 +522,27 @@ abstract class DBObject
 	// Note: checks the values and consistency
 	public function CheckToInsert()
 	{
+		$aIssues = array();
 		foreach(MetaModel::ListAttributeDefs(get_class($this)) as $sAttCode=>$oAttDef)
 		{
-			if (!$this->CheckValue($sAttCode)) return false;
+			if (!$this->CheckValue($sAttCode))
+			{
+				$aIssues[$sAttCode] = array(
+					'issue' => 'unexpected value'
+				);
+			}
 		}
-		if (!$this->CheckConsistency()) return false;
-		return true;
+		if (count($aIssues) > 0)
+		{
+			return array(false, $aIssues);
+		}
+		if (!$this->CheckConsistency())
+		{
+			return array(false, $aIssues);
+		}
+		return array(true, $aIssues);
 	}
-	
+
 	// check if it is allowed to update the existing object into the database
 	// a displayable error is returned
 	// Note: checks the values and consistency

+ 10 - 10
core/event.class.inc.php

@@ -32,7 +32,7 @@ class Event extends cmdbAbstractObject
 		);
 		MetaModel::Init_Params($aParams);
 		//MetaModel::Init_InheritAttributes();
-		MetaModel::Init_AddAttribute(new AttributeString("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("message", array("label"=>"message", "description"=>"short description of the event", "allowed_values"=>null, "sql"=>"message", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
 		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"=>"user info", "description"=>"identification of the user that was doing the action that triggered this event", "allowed_values"=>null, "sql"=>"userinfo", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 
@@ -110,18 +110,18 @@ class EventNotificationEmail extends EventNotification
 		);
 		MetaModel::Init_Params($aParams);
 		MetaModel::Init_InheritAttributes();
-		MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-		MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
-
+		MetaModel::Init_AddAttribute(new AttributeText("to", array("label"=>"TO", "description"=>"TO", "allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("cc", array("label"=>"CC", "description"=>"CC", "allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("bcc", array("label"=>"BCC", "description"=>"BCC", "allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("from", array("label"=>"From", "description"=>"Sender of the message", "allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("subject", array("label"=>"Subject", "description"=>"Subject", "allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
+		MetaModel::Init_AddAttribute(new AttributeText("body", array("label"=>"Body", "description"=>"Body", "allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_InheritFilters();
 
 		// Display lists
-		MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'trigger_id', 'action_id', 'object_id', 'from', 'to', 'cc', 'bcc', 'subject', 'body')); // Attributes to be displayed for the complete details
-		MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'subject')); // Attributes to be displayed for a list
+		MetaModel::Init_SetZListItems('details', array('date', 'userinfo', 'message', 'trigger_id', 'action_id', 'object_id', 'to', 'cc', 'bcc', 'from', 'subject', 'body')); // Attributes to be displayed for the complete details
+		MetaModel::Init_SetZListItems('list', array('date', 'userinfo', 'message', 'subject')); // Attributes to be displayed for a list
+
 		// Search criteria
 //		MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
 //		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form

+ 44 - 3
core/test.class.inc.php

@@ -130,9 +130,8 @@ abstract class TestHandler
 			$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";
+			$this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline");
+			echo "Unknown error type: [$errno] $errstr in $errfile at $errline<br />\n";
 			break;
 		}
 		return true; // do not call the default handler
@@ -418,6 +417,48 @@ abstract class TestBizModel extends TestHandler
 		// something here to create records... but that's another story
 	}
 
+	protected $m_oChange;
+	protected function ObjectToDB($oNew, $bReload = false)
+	{
+		list($bRes, $aIssues) = $oNew->CheckToInsert();
+		if (!$bRes)
+		{
+			throw new CoreException('Could not create object, unexpected values', array('attributes' => $aIssues));
+		}
+		if ($oNew instanceof CMDBObject)
+		{
+			if (!isset($this->m_oChange))
+			{
+				 new CMDBChange();
+				$oMyChange = MetaModel::NewObject("CMDBChange");
+				$oMyChange->Set("date", time());
+				$oMyChange->Set("userinfo", "Someone doing some tests");
+				$iChangeId = $oMyChange->DBInsertNoReload();
+				$this->m_oChange = $oMyChange; 
+			}
+			if ($bReload)
+			{
+				$iId = $oNew->DBInsertTracked($this->m_oChange);
+			}
+			else
+			{
+				$iId = $oNew->DBInsertTrackedNoReload($this->m_oChange);
+			}
+		}
+		else
+		{
+			if ($bReload)
+			{
+				$iId = $oNew->DBInsert();
+			}
+			else
+			{
+				$iId = $oNew->DBInsertNoReload();
+			}
+		}
+		return $iId;
+	}
+
 	protected function ResetDB()
 	{
 		if (MetaModel::DBExists())

+ 4 - 1
core/trigger.class.inc.php

@@ -56,7 +56,10 @@ class Trigger extends cmdbAbstractObject
 		{
 			$iActionId = $oLink->Get('action_id');
 			$oAction = MetaModel::GetObject('Action', $iActionId);
-			$oAction->DoExecute($this, $aContextArgs);
+			if ($oAction->IsActive())
+			{
+				$oAction->DoExecute($this, $aContextArgs);
+			}
 		}
 	}
 }

+ 2 - 1
pages/UniversalSearch.php

@@ -145,7 +145,8 @@ EOF;
 			}
 
 		}
-		if ($oMenuNode->CheckToInsert())
+		list($bRes, $aIssues) = $oMenuNode->CheckToInsert();
+		if ($bRes)
 		{
 			$oMenuNode->DBInsert();
 			$oP->add("<form method=\"get\">");

+ 16 - 2
pages/index.php

@@ -393,7 +393,8 @@ function CreateObject(WebPage $oPage, $sClassName, $aAttributes)
 			$oObj->Set($sAttCode, $aAttributes[$sAttCode]);
 		}
 	}
-	if ($oObj->CheckToInsert())
+	list($bRes, $aIssues) = $oObj->CheckToInsert();
+	if ($bRes)
 	{
 		// By rom
 		// $oObj->DBInsert();
@@ -413,6 +414,12 @@ function CreateObject(WebPage $oPage, $sClassName, $aAttributes)
 	else
 	{
 		$oPage->p("<strong>Error: object can not be created!</strong>\n");
+		$oPage->add("<ul>Issues:");
+		foreach($aIssues as $sErrorMsg)
+		{
+			$oPage->add("<li>$sErrorMsg</li>");
+		}
+		$oPage->add("</ul>");
 	}
 	$oPage->p("<a href=\"\">Return to main page</a>");
 }
@@ -442,7 +449,8 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe
 			$oNewLink = MetaModel::NewObject($sLinkClass);
 			$oNewLink->Set($sExtKeyToMe, $sKey);
 			$oNewLink->Set($sExtKeyToPartner, $oPartnerObj->GetKey());
-			if ($oNewLink->CheckToInsert())
+			list($bRes, $aIssues) = $oNewLink->CheckToInsert();
+			if ($bRes)
 			{
 				$oMyChange = MetaModel::NewObject("CMDBChange");
 				$oMyChange->Set("date", time());
@@ -455,6 +463,12 @@ function AddLinks($oPage, $sClassName, $sKey, $sLinkClass, $sExtKeyToMe, $sExtKe
 			else
 			{
 				$oPage->p("<strong>Error: link can not be created!</strong>\n");
+				$oPage->add("<ul>Issues:");
+				foreach($aIssues as $sErrorMsg)
+				{
+					$oPage->add("<li>$sErrorMsg</li>");
+				}
+				$oPage->add("</ul>");
 			}
 		}
 	}

+ 136 - 16
pages/testlist.inc.php

@@ -601,22 +601,6 @@ abstract class MyFarm extends TestBizModel
 		MetaModel::DBCheckIntegrity();
 	}
 
-	protected $m_oChange;
-	protected function ObjectToDB(CMDBObject $oNew)
-	{
-		if (!isset($this->m_oChange))
-		{
-			 new CMDBChange();
-			$oMyChange = MetaModel::NewObject("CMDBChange");
-			$oMyChange->Set("date", time());
-			$oMyChange->Set("userinfo", "Administrator");
-			$iChangeId = $oMyChange->DBInsertNoReload();
-			$this->m_oChange = $oMyChange; 
-		}
-		$iId = $oNew->DBInsertTrackedNoReload($this->m_oChange);
-		return $iId;
-}
-
 	protected function InsertMammal($sSpecies, $sSex, $iSpeed, $iMotherid, $iFatherId, $sName, $iHeight, $sBirth)
 	{
 		$oNew = MetaModel::NewObject('Mammal');
@@ -1467,4 +1451,140 @@ class TestWebServicesDirect extends TestBizModel
 		return true;
 	}
 }
+
+class TestTriggerAndEmail extends TestBizModel
+{
+	static public function GetName() {return 'Test trigger and email';}
+	static public function GetDescription() {return 'Create a trigger and an email, then activates the trigger';}
+
+	static public function GetConfigFile() {return '../config-itop.php';}
+
+	protected function CreateEmailSpec($oTrigger, $sStatus, $sTo, $sCC, $sTesterEmail)
+	{
+		$oAction = MetaModel::NewObject("ActionEmail");
+		$oAction->Set("status", $sStatus);
+		$oAction->Set("name", "New server");
+		$oAction->Set("test_recipient", $sTesterEmail);
+		$oAction->Set("from", $sTesterEmail);
+		$oAction->Set("reply_to", $sTesterEmail);
+		$oAction->Set("to", $sTo);
+		$oAction->Set("cc", $sCC);
+		$oAction->Set("bcc", "");
+		$oAction->Set("subject", "New server: '\$this->name()$'");
+		$oAction->Set("body", "<html><body><p>Dear customer,</p><p>We have created the server \$this->hyperlink()$ in the IT infrastructure database.</p><p>You will be further notified when it is in <strong>Production</strong>.</p><p>The IT infrastructure management team.</p><p>Here are some accentuated characters for french people: 'ééà'</p></body></html>");
+		$oAction->Set("importance", "low");
+		$iActionId = $this->ObjectToDB($oAction, true);
+
+		$oLink = MetaModel::NewObject("lnkTriggerAction");
+		$oLink->Set("trigger_id", $oTrigger->GetKey());
+		$oLink->Set("action_id", $iActionId);
+		$oLink->Set("order", "1");
+		$iLink = $this->ObjectToDB($oLink, true);
+	}
+
+	protected function DoExecute()
+	{
+		$oMyPerson = MetaModel::NewObject("bizPerson");
+		$oMyPerson->Set("name", "testemail1");
+		$oMyPerson->Set("org_id", "1");
+		$oMyPerson->Set("email", "romain.quetiez@hp.com");
+		$iPersonId = $this->ObjectToDB($oMyPerson, true);
+
+		$oMyPerson = MetaModel::NewObject("bizPerson");
+		$oMyPerson->Set("name", "testemail2");
+		$oMyPerson->Set("org_id", "1");
+		$oMyPerson->Set("email", "denis.flaven@hp.com");
+		$iPersonId = $this->ObjectToDB($oMyPerson, true);
+
+		$oMyPerson = MetaModel::NewObject("bizPerson");
+		$oMyPerson->Set("name", "testemail3");
+		$oMyPerson->Set("org_id", "1");
+		$oMyPerson->Set("email", "erwan.taloc@hp.com");
+		$iPersonId = $this->ObjectToDB($oMyPerson, true);
+
+		$oMyServer = MetaModel::NewObject("bizServer");
+		$oMyServer->Set("name", "wfr.terminator.com");
+		$oMyServer->Set("severity", "low");
+		$oMyServer->Set("status", "InStore");
+		$oMyServer->Set("org_id", 2);
+		$oMyServer->Set("location_id", 2);
+		$iServerId = $this->ObjectToDB($oMyServer, true);
+
+		$oMyTrigger = MetaModel::NewObject("TriggerOnStateEnter");
+		$oMyTrigger->Set("description", "Testor");
+		$oMyTrigger->Set("target_class", "bizServer");
+		$oMyTrigger->Set("state", "Shipped");
+		$iTriggerId = $this->ObjectToDB($oMyTrigger, true);
+
+		// Error in OQL field(s)
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'test',
+			"SELECT bizPerson WHERE naime = 'Dali'",
+			"SELECT bizServer",
+			'romain.quetiez@hp.com'
+		);
+
+		// Error: no recipient
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'test',
+			"",
+			"",
+			'romain.quetiez@hp.com'
+		);
+
+		// Test
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'test',
+			"SELECT bizPerson WHERE name LIKE 'testemail%'",
+			"SELECT bizPerson",
+			'romain.quetiez@hp.com'
+		);
+
+		// Test failing because of a wrong test recipient address
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'test',
+			"SELECT bizPerson WHERE name LIKE 'testemail%'",
+			"",
+			'toto@walibi.bg'
+		);
+
+		// Normal behavior
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'enabled',
+			"SELECT bizPerson WHERE name LIKE 'testemail%'",
+			"",
+			'romain.quetiez@hp.com'
+		);
+
+		// Does nothing, because it is disabled
+		//
+		$this->CreateEmailSpec
+		(
+			$oMyTrigger,
+			'disabled',
+			"SELECT bizPerson WHERE name = 'testemail%'",
+			"",
+			'romain.quetiez@hp.com'
+		);
+
+		$oMyTrigger->DoActivate($oMyServer->ToArgs('this'));
+
+		return true;
+	}
+}
 ?>

+ 184 - 0
setup/email.test.php

@@ -0,0 +1,184 @@
+<?php
+/**
+ * Wizard to configure and initialize the iTop application
+ */
+require_once('../application/utils.inc.php');
+require_once('../core/email.class.inc.php');
+require_once('./setuppage.class.inc.php');
+
+$sOperation = Utils::ReadParam('operation', 'step1');
+$oP = new SetupWebPage('iTop email test utility');
+
+
+/**
+ * Helper to check server setting required to send an email
+ */  
+function CheckEmailSetting($oP)
+{
+	$bRet = true;
+
+	if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4
+	{
+		$sPhpIniFile = php_ini_loaded_file();
+	}
+	else
+	{
+		$sPhpIniFile = 'php.ini';
+	}
+
+	$bIsWindows = (array_key_exists('WINDIR', $_SERVER)); 
+	if ($bIsWindows)
+	{	
+		$sSmtpServer = ini_get('SMTP');
+		if (empty($sSmtpServer))
+		{
+			$oP->error("The SMTP server is not defined. Please add the 'SMTP' directive into $sPhpIniFile");
+			$bRet = false;
+		}
+		else if (strcasecmp($sSmtpServer, 'localhost') == 0)
+		{
+			$oP->warning("Your SMTP server is configured to 'localhost'. You might want to set or change the 'SMTP' directive into $sPhpIniFile");
+		}
+		else
+		{
+			$oP->info("Your SMTP server: <strong>$sSmtpServer</strong>. To change this value, modify the 'SMTP' directive into $sPhpIniFile");
+		}
+
+		$iSmtpPort = (int) ini_get('smtp_port');
+		if (empty($iSmtpPort))
+		{
+			$oP->info("The SMTP port is not defined. Please add the 'smtp_port' directive into $sPhpIniFile");
+			$bRet = false;
+		}
+		else if ($iSmtpPort = 25)
+		{
+			$oP->info("Your SMTP port is configured to the default value: 25. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
+		}
+		else
+		{
+			$oP->info("Your SMTP port is configured to $iSmtpPort. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
+		}
+	}
+	else
+	{
+		// Not a windows system
+	}
+	if ($bRet)
+	{
+		$oP->ok("PHP settings are ok to proceed with a test of the email");
+	}
+	return $bRet;
+}
+
+
+/**
+ * Display the form for the first step of the test wizard
+ * which consists in a basic check of the configuration and display of a form for testing
+ */  
+function DisplayStep1(SetupWebPage $oP)
+{
+	$sNextOperation = 'step2';
+	$oP->add("<h1>iTop email test</h1>\n");
+	$oP->add("<h2>Checking prerequisites</h2>\n");
+	if (CheckEmailSetting($oP))
+	{
+		$sRedStar = '<span class="hilite">*</span>';
+		$oP->add("<h2>Try to send an email</h2>\n");
+		$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Sending an email...', 10)\">\n");
+		// Form goes here
+		$oP->add("<fieldset><legend>Test configuration</legend>\n");
+		$aForm = array();
+		$aForm[] = array(
+			'label' => "To$sRedStar:",
+			'input' => "<input id=\"to\" type=\"text\" name=\"to\" value=\"\">",
+			'help' => ' pure email address (john.foo@worldcompany.com)',
+		);
+		$aForm[] = array(
+			'label' => "From:",
+			'input' => "<input id=\"from\" type=\"text\" name=\"from\" value=\"\">",
+			'help' => ' defaults to \'To\'',
+		);
+		$oP->form($aForm);
+		$oP->add("</fieldset>\n");
+		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
+		$oP->add("<button type=\"submit\">Next >></button>\n");
+		$oP->add("</form>\n");
+	}
+}
+
+/**
+ * Display the form for the second step of the configuration wizard
+ * which consists in sending an email, which maybe a problem under Windows
+ */  
+function DisplayStep2(SetupWebPage $oP, $sFrom, $sTo)
+{
+	//$sNextOperation = 'step3';
+	$oP->add("<h1>iTop configuration wizard</h1>\n");
+	$oP->add("<h2>Step 2: send an email</h2>\n");
+	$oP->add("<p>Sending an email to '$sTo'... (From: '$sFrom')</p>\n");
+	$oP->add("<form method=\"post\">\n");
+
+	$oEmail = new Email();
+	$oEmail->SetRecipientTO($sTo);
+	$oEmail->SetRecipientFrom($sFrom);
+	$oEmail->SetSubject("Test iTop");
+	$oEmail->SetBody("<p>Hello,</p><p>The email function is now working fine.</p><p>You may now be able to use the notification function.</p><p>iTop</p>");
+	$aIssues = $oEmail->send();
+	if (count($aIssues) > 0)
+	{
+		foreach ($aIssues as $sError)
+		{
+			$oP->error($sError);
+		}
+		$oP->add("<button onClick=\"window.history.back();\"><< Back</button>\n");
+	}
+	else
+	{
+		$oP->ok("The email has been sent, you may now check that the email will arrive...");
+	}
+}
+
+/**
+ * Main program
+ */
+
+// #@# Init default timezone -> do not get a notice... to be improved !!!
+// duplicated from 'attributedef.class.inc.php', needed here because mail() does
+// generate a notice
+date_default_timezone_set('Europe/Paris');
+
+
+try
+{
+	switch($sOperation)
+	{
+		case 'step1':
+		DisplayStep1($oP);
+		break;
+		
+		case 'step2':
+		$oP->no_cache();
+		$sTo = Utils::ReadParam('to');
+		$sFrom = Utils::ReadParam('from');
+		if (strlen($sFrom) == 0)
+		{
+			$sFrom = $sTo;
+		}
+		DisplayStep2($oP, $sFrom, $sTo);
+		break;
+
+		default:
+		$oP->error("Error: unsupported operation '$sOperation'");
+		
+	}
+}
+catch(Exception $e)
+{
+	$oP->error("Error: '".$e->getMessage()."'");	
+}
+catch(CoreException $e)
+{
+	$oP->error("Error: '".$e->getHtmlDesc()."'");	
+}
+$oP->output();
+?>

+ 9 - 0
setup/setup.js

@@ -77,6 +77,15 @@ function DoSubmit(sMsg, iStep)
 		
 		case 4:
 		bResult = DoLoadDataAsynchronous();
+		break;
+
+		// Email test page
+		case 10:
+		if ($('#to').val() == '')
+		{
+			alert('Please specify a destination address');
+			bResult = false;
+		}
 	}
 	if (bResult)
 	{

+ 1 - 0
toolkit.php

@@ -15,6 +15,7 @@ echo "<h2>Itop consultant</h2>\n";
 echo "<a href=\"./pages/ITopConsultant.php?config=..%2Fconfig-itop.php\">Check model, Create DB, Update DB (new class, new attribute)</a></br>\n";
 echo "<a href=\"./pages/db_importer.php\">Backup and restore (shortcut)</a></br>\n";
 echo "<a href=\"./pages/schema.php\">Objects schema (shortcut)</a></br>\n";
+echo "<a href=\"./setup/email.test.php\">Setup the email</a></br>\n";
 echo "<h2>Web services</h2>\n";
 echo "<a href=\"./webservices/soapserver.php\">Available functions</a></br>\n";
 echo "<a href=\"./webservices/itop.wsdl.php\">WSDL (dynamically generated)</a></br>\n";

+ 2 - 1
webservices/webservices.class.inc.php

@@ -455,7 +455,8 @@ class WebServices
 	{
 		if ($oRes->IsOk())
 		{
-			if ($oTargetObj->CheckToInsert())
+			list($bRes, $aIssues) = $oTargetObj->CheckToInsert();
+			if ($bRes)
 			{
 				$iId = $oTargetObj->DBInsertTrackedNoReload($oChange);
 				$oRes->LogInfo("Created object ".get_class($$oTargetObj)."::$iId");