action.class.inc.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. <?php
  2. // Copyright (C) 2010-2016 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-2016 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,application",
  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 AttributeTemplateHTML("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. $sAddress = trim($oObj->Get($sEmailAttCode));
  194. if (strlen($sAddress) > 0)
  195. {
  196. $aRecipients[] = $sAddress;
  197. $this->m_iRecipients++;
  198. }
  199. }
  200. return implode(', ', $aRecipients);
  201. }
  202. public function DoExecute($oTrigger, $aContextArgs)
  203. {
  204. if (MetaModel::IsLogEnabledNotification())
  205. {
  206. $oLog = new EventNotificationEmail();
  207. if ($this->IsBeingTested())
  208. {
  209. $oLog->Set('message', 'TEST - Notification sent ('.$this->Get('test_recipient').')');
  210. }
  211. else
  212. {
  213. $oLog->Set('message', 'Notification pending');
  214. }
  215. $oLog->Set('userinfo', UserRights::GetUser());
  216. $oLog->Set('trigger_id', $oTrigger->GetKey());
  217. $oLog->Set('action_id', $this->GetKey());
  218. $oLog->Set('object_id', $aContextArgs['this->object()']->GetKey());
  219. // Must be inserted now so that it gets a valid id that will make the link
  220. // between an eventual asynchronous task (queued) and the log
  221. $oLog->DBInsertNoReload();
  222. }
  223. else
  224. {
  225. $oLog = null;
  226. }
  227. try
  228. {
  229. $sRes = $this->_DoExecute($oTrigger, $aContextArgs, $oLog);
  230. if ($this->IsBeingTested())
  231. {
  232. $sPrefix = 'TEST ('.$this->Get('test_recipient').') - ';
  233. }
  234. else
  235. {
  236. $sPrefix = '';
  237. }
  238. if ($oLog)
  239. {
  240. $oLog->Set('message', $sPrefix . $sRes);
  241. }
  242. }
  243. catch (Exception $e)
  244. {
  245. if ($oLog)
  246. {
  247. $oLog->Set('message', 'Error: '.$e->getMessage());
  248. }
  249. }
  250. if ($oLog)
  251. {
  252. $oLog->DBUpdate();
  253. }
  254. }
  255. protected function _DoExecute($oTrigger, $aContextArgs, &$oLog)
  256. {
  257. $sPreviousUrlMaker = ApplicationContext::SetUrlMakerClass();
  258. try
  259. {
  260. $this->m_iRecipients = 0;
  261. $this->m_aMailErrors = array();
  262. $bRes = false; // until we do succeed in sending the email
  263. // Determine recicipients
  264. //
  265. $sTo = $this->FindRecipients('to', $aContextArgs);
  266. $sCC = $this->FindRecipients('cc', $aContextArgs);
  267. $sBCC = $this->FindRecipients('bcc', $aContextArgs);
  268. $sFrom = MetaModel::ApplyParams($this->Get('from'), $aContextArgs);
  269. $sReplyTo = MetaModel::ApplyParams($this->Get('reply_to'), $aContextArgs);
  270. $sSubject = MetaModel::ApplyParams($this->Get('subject'), $aContextArgs);
  271. $sBody = MetaModel::ApplyParams($this->Get('body'), $aContextArgs);
  272. $oObj = $aContextArgs['this->object()'];
  273. $sMessageId = sprintf('iTop_%s_%d_%f@%s.openitop.org', get_class($oObj), $oObj->GetKey(), microtime(true /* get as float*/), MetaModel::GetEnvironmentId());
  274. $sReference = '<'.$sMessageId.'>';
  275. }
  276. catch(Exception $e)
  277. {
  278. ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
  279. throw $e;
  280. }
  281. ApplicationContext::SetUrlMakerClass($sPreviousUrlMaker);
  282. if (!is_null($oLog))
  283. {
  284. // Note: we have to secure this because those values are calculated
  285. // inside the try statement, and we would like to keep track of as
  286. // many data as we could while some variables may still be undefined
  287. if (isset($sTo)) $oLog->Set('to', $sTo);
  288. if (isset($sCC)) $oLog->Set('cc', $sCC);
  289. if (isset($sBCC)) $oLog->Set('bcc', $sBCC);
  290. if (isset($sFrom)) $oLog->Set('from', $sFrom);
  291. if (isset($sSubject)) $oLog->Set('subject', $sSubject);
  292. if (isset($sBody)) $oLog->Set('body', $sBody);
  293. }
  294. $oEmail = new EMail();
  295. if ($this->IsBeingTested())
  296. {
  297. $oEmail->SetSubject('TEST['.$sSubject.']');
  298. $sTestBody = $sBody;
  299. $sTestBody .= "<div style=\"border: dashed;\">\n";
  300. $sTestBody .= "<h1>Testing email notification ".$this->GetHyperlink()."</h1>\n";
  301. $sTestBody .= "<p>The email should be sent with the following properties\n";
  302. $sTestBody .= "<ul>\n";
  303. $sTestBody .= "<li>TO: $sTo</li>\n";
  304. $sTestBody .= "<li>CC: $sCC</li>\n";
  305. $sTestBody .= "<li>BCC: $sBCC</li>\n";
  306. $sTestBody .= "<li>From: $sFrom</li>\n";
  307. $sTestBody .= "<li>Reply-To: $sReplyTo</li>\n";
  308. $sTestBody .= "<li>References: $sReference</li>\n";
  309. $sTestBody .= "</ul>\n";
  310. $sTestBody .= "</p>\n";
  311. $sTestBody .= "</div>\n";
  312. $oEmail->SetBody($sTestBody);
  313. $oEmail->SetRecipientTO($this->Get('test_recipient'));
  314. $oEmail->SetRecipientFrom($this->Get('test_recipient'));
  315. $oEmail->SetReferences($sReference);
  316. $oEmail->SetMessageId($sMessageId);
  317. }
  318. else
  319. {
  320. $oEmail->SetSubject($sSubject);
  321. $oEmail->SetBody($sBody);
  322. $oEmail->SetRecipientTO($sTo);
  323. $oEmail->SetRecipientCC($sCC);
  324. $oEmail->SetRecipientBCC($sBCC);
  325. $oEmail->SetRecipientFrom($sFrom);
  326. $oEmail->SetRecipientReplyTo($sReplyTo);
  327. $oEmail->SetReferences($sReference);
  328. $oEmail->SetMessageId($sMessageId);
  329. }
  330. if (isset($aContextArgs['attachments']))
  331. {
  332. $aAttachmentReport = array();
  333. foreach($aContextArgs['attachments'] as $oDocument)
  334. {
  335. $oEmail->AddAttachment($oDocument->GetData(), $oDocument->GetFileName(), $oDocument->GetMimeType());
  336. $aAttachmentReport[] = array($oDocument->GetFileName(), $oDocument->GetMimeType(), strlen($oDocument->GetData()));
  337. }
  338. $oLog->Set('attachments', $aAttachmentReport);
  339. }
  340. if (empty($this->m_aMailErrors))
  341. {
  342. if ($this->m_iRecipients == 0)
  343. {
  344. return 'No recipient';
  345. }
  346. else
  347. {
  348. $iRes = $oEmail->Send($aErrors, false, $oLog); // allow asynchronous mode
  349. switch ($iRes)
  350. {
  351. case EMAIL_SEND_OK:
  352. return "Sent";
  353. case EMAIL_SEND_PENDING:
  354. return "Pending";
  355. case EMAIL_SEND_ERROR:
  356. return "Errors: ".implode(', ', $aErrors);
  357. }
  358. }
  359. }
  360. else
  361. {
  362. if (is_array($this->m_aMailErrors) && count($this->m_aMailErrors) > 0)
  363. {
  364. $sError = implode(', ', $this->m_aMailErrors);
  365. }
  366. else
  367. {
  368. $sError = 'Unknown reason';
  369. }
  370. return 'Notification was not sent: '.$sError;
  371. }
  372. }
  373. }
  374. ?>