action.class.inc.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. <?php
  2. // Copyright (C) 2010-2012 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Persistent classes (internal): user defined actions
  20. *
  21. * @copyright Copyright (C) 2010-2012 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. require_once(APPROOT.'/core/asynctask.class.inc.php');
  25. require_once(APPROOT.'/core/email.class.inc.php');
  26. /**
  27. * A user defined action, to customize the application
  28. *
  29. * @package iTopORM
  30. */
  31. abstract class Action extends cmdbAbstractObject
  32. {
  33. public static function Init()
  34. {
  35. $aParams = array
  36. (
  37. "category" => "core/cmdb",
  38. "key_type" => "autoincrement",
  39. "name_attcode" => "name",
  40. "state_attcode" => "",
  41. "reconc_keys" => array('name'),
  42. "db_table" => "priv_action",
  43. "db_key_field" => "id",
  44. "db_finalclass_field" => "realclass",
  45. "display_template" => "",
  46. );
  47. MetaModel::Init_Params($aParams);
  48. //MetaModel::Init_InheritAttributes();
  49. MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  50. MetaModel::Init_AddAttribute(new AttributeString("description", array("allowed_values"=>null, "sql"=>"description", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  51. MetaModel::Init_AddAttribute(new AttributeEnum("status", array("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())));
  52. MetaModel::Init_AddAttribute(new AttributeLinkedSetIndirect("trigger_list", array("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())));
  53. // Display lists
  54. MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
  55. MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
  56. // Search criteria
  57. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  58. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  59. }
  60. abstract public function DoExecute($oTrigger, $aContextArgs);
  61. public function IsActive()
  62. {
  63. switch($this->Get('status'))
  64. {
  65. case 'enabled':
  66. case 'test':
  67. return true;
  68. default:
  69. return false;
  70. }
  71. }
  72. public function IsBeingTested()
  73. {
  74. switch($this->Get('status'))
  75. {
  76. case 'test':
  77. return true;
  78. default:
  79. return false;
  80. }
  81. }
  82. }
  83. /**
  84. * A notification
  85. *
  86. * @package iTopORM
  87. */
  88. abstract class ActionNotification extends Action
  89. {
  90. public static function Init()
  91. {
  92. $aParams = array
  93. (
  94. "category" => "core/cmdb",
  95. "key_type" => "autoincrement",
  96. "name_attcode" => "name",
  97. "state_attcode" => "",
  98. "reconc_keys" => array('name'),
  99. "db_table" => "priv_action_notification",
  100. "db_key_field" => "id",
  101. "db_finalclass_field" => "",
  102. "display_template" => "",
  103. );
  104. MetaModel::Init_Params($aParams);
  105. MetaModel::Init_InheritAttributes();
  106. // Display lists
  107. MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'trigger_list')); // Attributes to be displayed for the complete details
  108. MetaModel::Init_SetZListItems('list', array('finalclass', 'name', 'description', 'status')); // Attributes to be displayed for a list
  109. // Search criteria
  110. // MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
  111. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  112. }
  113. }
  114. /**
  115. * An email notification
  116. *
  117. * @package iTopORM
  118. */
  119. class ActionEmail extends ActionNotification
  120. {
  121. public static function Init()
  122. {
  123. $aParams = array
  124. (
  125. "category" => "core/cmdb,bizmodel",
  126. "key_type" => "autoincrement",
  127. "name_attcode" => "name",
  128. "state_attcode" => "",
  129. "reconc_keys" => array('name'),
  130. "db_table" => "priv_action_email",
  131. "db_key_field" => "id",
  132. "db_finalclass_field" => "",
  133. "display_template" => "",
  134. );
  135. MetaModel::Init_Params($aParams);
  136. MetaModel::Init_InheritAttributes();
  137. MetaModel::Init_AddAttribute(new AttributeEmailAddress("test_recipient", array("allowed_values"=>null, "sql"=>"test_recipient", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
  138. MetaModel::Init_AddAttribute(new AttributeString("from", array("allowed_values"=>null, "sql"=>"from", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  139. MetaModel::Init_AddAttribute(new AttributeString("reply_to", array("allowed_values"=>null, "sql"=>"reply_to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  140. MetaModel::Init_AddAttribute(new AttributeOQL("to", array("allowed_values"=>null, "sql"=>"to", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  141. MetaModel::Init_AddAttribute(new AttributeOQL("cc", array("allowed_values"=>null, "sql"=>"cc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  142. MetaModel::Init_AddAttribute(new AttributeOQL("bcc", array("allowed_values"=>null, "sql"=>"bcc", "default_value"=>null, "is_null_allowed"=>true, "depends_on"=>array())));
  143. MetaModel::Init_AddAttribute(new AttributeTemplateString("subject", array("allowed_values"=>null, "sql"=>"subject", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  144. MetaModel::Init_AddAttribute(new AttributeTemplateText("body", array("allowed_values"=>null, "sql"=>"body", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
  145. MetaModel::Init_AddAttribute(new AttributeEnum("importance", array("allowed_values"=>new ValueSetEnum('low,normal,high'), "sql"=>"importance", "default_value"=>'normal', "is_null_allowed"=>false, "depends_on"=>array())));
  146. // Display lists
  147. MetaModel::Init_SetZListItems('details', array('name', 'description', 'status', 'test_recipient', 'from', 'reply_to', 'to', 'cc', 'bcc', 'subject', 'body', 'importance', 'trigger_list')); // Attributes to be displayed for the complete details
  148. MetaModel::Init_SetZListItems('list', array('name', 'status', 'to', 'subject')); // Attributes to be displayed for a list
  149. // Search criteria
  150. MetaModel::Init_SetZListItems('standard_search', array('name','description', 'status', 'subject')); // Criteria of the std search form
  151. // MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
  152. }
  153. // count the recipients found
  154. protected $m_iRecipients;
  155. // Errors management : not that simple because we need that function to be
  156. // executed in the background, while making sure that any issue would be reported clearly
  157. protected $m_aMailErrors; //array of strings explaining the issue
  158. // returns a the list of emails as a string, or a detailed error description
  159. protected function FindRecipients($sRecipAttCode, $aArgs)
  160. {
  161. $sOQL = $this->Get($sRecipAttCode);
  162. if (strlen($sOQL) == '') return '';
  163. try
  164. {
  165. $oSearch = DBObjectSearch::FromOQL($sOQL);
  166. $oSearch->AllowAllData();
  167. }
  168. catch (OQLException $e)
  169. {
  170. $this->m_aMailErrors[] = "query syntax error for recipient '$sRecipAttCode'";
  171. return $e->getMessage();
  172. }
  173. $sClass = $oSearch->GetClass();
  174. // Determine the email attribute (the first one will be our choice)
  175. foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  176. {
  177. if ($oAttDef instanceof AttributeEmailAddress)
  178. {
  179. $sEmailAttCode = $sAttCode;
  180. // we've got one, exit the loop
  181. break;
  182. }
  183. }
  184. if (!isset($sEmailAttCode))
  185. {
  186. $this->m_aMailErrors[] = "wrong target for recipient '$sRecipAttCode'";
  187. return "The objects of the class '$sClass' do not have any email attribute";
  188. }
  189. $oSet = new DBObjectSet($oSearch, array() /* order */, $aArgs);
  190. $aRecipients = array();
  191. while ($oObj = $oSet->Fetch())
  192. {
  193. $aRecipients[] = $oObj->Get($sEmailAttCode);
  194. $this->m_iRecipients++;
  195. }
  196. return implode(', ', $aRecipients);
  197. }
  198. public function DoExecute($oTrigger, $aContextArgs)
  199. {
  200. if (MetaModel::IsLogEnabledNotification())
  201. {
  202. $oLog = new EventNotificationEmail();
  203. if ($this->IsBeingTested())
  204. {
  205. $oLog->Set('message', 'TEST - Notification sent ('.$this->Get('test_recipient').')');
  206. }
  207. else
  208. {
  209. $oLog->Set('message', 'Notification pending');
  210. }
  211. $oLog->Set('userinfo', UserRights::GetUser());
  212. $oLog->Set('trigger_id', $oTrigger->GetKey());
  213. $oLog->Set('action_id', $this->GetKey());
  214. $oLog->Set('object_id', $aContextArgs['this->object()']->GetKey());
  215. // Must be inserted now so that it gets a valid id that will make the link
  216. // between an eventual asynchronous task (queued) and the log
  217. $oLog->DBInsertNoReload();
  218. }
  219. else
  220. {
  221. $oLog = null;
  222. }
  223. try
  224. {
  225. $sRes = $this->_DoExecute($oTrigger, $aContextArgs, $oLog);
  226. if ($this->IsBeingTested())
  227. {
  228. $sPrefix = 'TEST ('.$this->Get('test_recipient').') - ';
  229. }
  230. else
  231. {
  232. $sPrefix = '';
  233. }
  234. $oLog->Set('message', $sPrefix.$sRes);
  235. }
  236. catch (Exception $e)
  237. {
  238. if ($oLog)
  239. {
  240. $oLog->Set('message', 'Error: '.$e->getMessage());
  241. }
  242. }
  243. if ($oLog)
  244. {
  245. $oLog->DBUpdate();
  246. }
  247. }
  248. protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
  249. {
  250. $sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
  251. try
  252. {
  253. $this->m_iRecipients = 0;
  254. $this->m_aMailErrors = array();
  255. $bRes = false; // until we do succeed in sending the email
  256. // Determine recicipients
  257. //
  258. $sTo = $this->FindRecipients('to', $aContextArgs);
  259. $sCC = $this->FindRecipients('cc', $aContextArgs);
  260. $sBCC = $this->FindRecipients('bcc', $aContextArgs);
  261. $sFrom = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
  262. $sReplyTo = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
  263. $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
  264. $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
  265. $oObj = $aContextArgs['this->object()'];
  266. $sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetEnvironmentId());
  267. $sReference = '<'.$sMessageId.'>';
  268. }
  269. catch(Exception $e)
  270. {
  271. ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
  272. throw $e;
  273. }
  274. ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
  275. if (!is_null($oLog))
  276. {
  277. // Note: we have to secure this because those values are calculated
  278. // inside the try statement, and we would like to keep track of as
  279. // many data as we could while some variables may still be undefined
  280. if (isset($sTo)) $oLog->Set('to', $sTo);
  281. if (isset($sCC)) $oLog->Set('cc', $sCC);
  282. if (isset($sBCC)) $oLog->Set('bcc', $sBCC);
  283. if (isset($sFrom)) $oLog->Set('from', $sFrom);
  284. if (isset($sSubject)) $oLog->Set('subject', $sSubject);
  285. if (isset($sBody)) $oLog->Set('body', $sBody);
  286. }
  287. $oEmail = new EMail();
  288. if ($this->IsBeingTested())
  289. {
  290. $oEmail->SetSubject('TEST['.$sSubject.']');
  291. $sTestBody = $sBody;
  292. $sTestBody .= "<div style=\"border: dashed;\">\n";
  293. $sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
  294. $sTestBody .= "<p>The email should be sent with the following properties\n";
  295. $sTestBody .= "<ul>\n";
  296. $sTestBody .= "<li>TO: $sTo</li>\n";
  297. $sTestBody .= "<li>CC: $sCC</li>\n";
  298. $sTestBody .= "<li>BCC: $sBCC</li>\n";
  299. $sTestBody .= "<li>From: $sFrom</li>\n";
  300. $sTestBody .= "<li>Reply-To: $sReplyTo</li>\n";
  301. $sTestBody .= "<li>References: $sReference</li>\n";
  302. $sTestBody .= "</ul>\n";
  303. $sTestBody .= "</p>\n";
  304. $sTestBody .= "</div>\n";
  305. $oEmail->SetBody($sTestBody);
  306. $oEmail->SetRecipientTO($this->Get('test_recipient'));
  307. $oEmail->SetRecipientFrom($this->Get('test_recipient'));
  308. $oEmail->SetReferences($sReference);
  309. $oEmail->SetMessageId($sMessageId);
  310. }
  311. else
  312. {
  313. $oEmail->SetSubject($sSubject);
  314. $oEmail->SetBody($sBody);
  315. $oEmail->SetRecipientTO($sTo);
  316. $oEmail->SetRecipientCC($sCC);
  317. $oEmail->SetRecipientBCC($sBCC);
  318. $oEmail->SetRecipientFrom($sFrom);
  319. $oEmail->SetRecipientReplyTo($sReplyTo);
  320. $oEmail->SetReferences($sReference);
  321. $oEmail->SetMessageId($sMessageId);
  322. }
  323. if (isset($aContextArgs['attachments']))
  324. {
  325. foreach($aContextArgs['attachments'] as $oDocument)
  326. {
  327. $oEmail->AddAttachment($oDocument->GetData(), $oDocument->GetFileName(), $oDocument->GetMimeType());
  328. }
  329. }
  330. if (empty($this->m_aMailErrors))
  331. {
  332. if ($this->m_iRecipients == 0)
  333. {
  334. return 'No recipient';
  335. }
  336. else
  337. {
  338. $iRes = $oEmail->Send($aErrors, false, $oLog); // allow asynchronous mode
  339. switch ($iRes)
  340. {
  341. case EMAIL_SEND_OK:
  342. return "Sent";
  343. case EMAIL_SEND_PENDING:
  344. return "Pending";
  345. case EMAIL_SEND_ERROR:
  346. return "Errors: ".implode(', ', $aErrors);
  347. }
  348. }
  349. }
  350. else
  351. {
  352. if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0)
  353. {
  354. $sError = implode(', ', $this->m_aMailErrors);
  355. }
  356. else
  357. {
  358. $sError = 'Unknown reason';
  359. }
  360. return 'Notification was not sent: '.$sError;
  361. }
  362. }
  363. }
  364. ?>