diff --git a/core/MyHelpers.class.inc.php b/core/MyHelpers.class.inc.php
index 16e149cd9..fded6913a 100644
--- a/core/MyHelpers.class.inc.php
+++ b/core/MyHelpers.class.inc.php
@@ -331,15 +331,6 @@ class MyHelpers
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 !
diff --git a/core/action.class.inc.php b/core/action.class.inc.php
index dbeaeeea8..6f8dfcb0f 100644
--- a/core/action.class.inc.php
+++ b/core/action.class.inc.php
@@ -1,5 +1,7 @@
"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');
-
- $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";
-
- // Additional headers
- if (strlen($sFrom) > 0)
+ $this->m_iRecipients = 0;
+ $this->m_aMailErrors = array();
+ $bRes = false; // until we do succeed in sending the email
+ try
{
- $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);
+ // 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');
+
+ $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
+ $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
+
+ $oEmail = new Email();
+
+ if ($this->IsBeingTested())
+ {
+ $oEmail->SetSubject('TEST['.$sSubject.']');
+ $sTestBody = $sBody;
+ $sTestBody .= "
\n";
+ $sTestBody .= "
Testing email notification ".$this->GetHyperlink()."
\n";
+ $sTestBody .= "
The email should be sent with the following properties\n";
+ $sTestBody .= "
\n";
+ $sTestBody .= "- TO: $sTo
\n";
+ $sTestBody .= "- CC: $sCC
\n";
+ $sTestBody .= "- BCC: $sBCC
\n";
+ $sTestBody .= "- From: $sFrom
\n";
+ $sTestBody .= "- Reply-To: $sReplyTo
\n";
+ $sTestBody .= "
\n";
+ $sTestBody .= "\n";
+ $sTestBody .= "
\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($sReplyTo) > 0)
+ catch (Exception $e)
{
- $sHeaders .= "Reply-To: $sReplyTo\r\n";
- }
- if (strlen($sCC) > 0)
- {
- $sHeaders .= "Cc: $sCC\r\n";
- }
- if (strlen($sBCC) > 0)
- {
- $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();
}
}
diff --git a/core/dbobject.class.php b/core/dbobject.class.php
index e20943a7a..68479ca11 100644
--- a/core/dbobject.class.php
+++ b/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
diff --git a/core/event.class.inc.php b/core/event.class.inc.php
index 9955dddbd..cbac9e792 100644
--- a/core/event.class.inc.php
+++ b/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
diff --git a/core/test.class.inc.php b/core/test.class.inc.php
index 905e7ad06..87af7e165 100644
--- a/core/test.class.inc.php
+++ b/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
\n";
+ $this->ReportWarning("Unknown error type: [$errno] $errstr in $errfile at $errline");
+ echo "Unknown error type: [$errno] $errstr in $errfile at $errline
\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())
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index e9de6eaa3..511bb42a3 100644
--- a/core/trigger.class.inc.php
+++ b/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);
+ }
}
}
}
diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php
index b8b300ce6..4a9c9b077 100644
--- a/pages/UniversalSearch.php
+++ b/pages/UniversalSearch.php
@@ -145,7 +145,8 @@ EOF;
}
}
- if ($oMenuNode->CheckToInsert())
+ list($bRes, $aIssues) = $oMenuNode->CheckToInsert();
+ if ($bRes)
{
$oMenuNode->DBInsert();
$oP->add("\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("iTop configuration wizard
\n");
+ $oP->add("Step 2: send an email
\n");
+ $oP->add("Sending an email to '$sTo'... (From: '$sFrom')
\n");
+ $oP->add("