ormcaselog.class.inc.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579
  1. <?php
  2. // Copyright (C) 2010-2015 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. define('CASELOG_VISIBLE_ITEMS', 2);
  19. define('CASELOG_SEPARATOR', "\n".'========== %1$s : %2$s (%3$d) ============'."\n\n");
  20. /**
  21. * Class to store a "case log" in a structured way, keeping track of its successive entries
  22. *
  23. * @copyright Copyright (C) 2010-2015 Combodo SARL
  24. * @license http://opensource.org/licenses/AGPL-3.0
  25. */
  26. class ormCaseLog {
  27. protected $m_sLog;
  28. protected $m_aIndex;
  29. protected $m_bModified;
  30. /**
  31. * Initializes the log with the first (initial) entry
  32. * @param $sLog string The text of the whole case log
  33. * @param $aIndex hash The case log index
  34. */
  35. public function __construct($sLog = '', $aIndex = array())
  36. {
  37. $this->m_sLog = $sLog;
  38. $this->m_aIndex = $aIndex;
  39. $this->m_bModified = false;
  40. }
  41. public function GetText()
  42. {
  43. return $this->m_sLog;
  44. }
  45. public static function FromJSON($oJson)
  46. {
  47. if (!isset($oJson->items))
  48. {
  49. throw new Exception("Missing 'items' elements");
  50. }
  51. $oCaseLog = new ormCaseLog();
  52. foreach($oJson->items as $oItem)
  53. {
  54. $oCaseLog->AddLogEntryFromJSON($oItem);
  55. }
  56. return $oCaseLog;
  57. }
  58. /**
  59. * Return a value that will be further JSON encoded
  60. */
  61. public function GetForJSON()
  62. {
  63. $aEntries = array();
  64. $iPos = 0;
  65. for($index=count($this->m_aIndex)-1 ; $index >= 0 ; $index--)
  66. {
  67. $iPos += $this->m_aIndex[$index]['separator_length'];
  68. $sTextEntry = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
  69. $iPos += $this->m_aIndex[$index]['text_length'];
  70. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  71. // therefore we have changed the format. To preserve the compatibility with existing
  72. // installations of iTop, both format are allowed:
  73. // the 'date' item is either a DateTime object, or a unix timestamp
  74. if (is_int($this->m_aIndex[$index]['date']))
  75. {
  76. // Unix timestamp
  77. $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$this->m_aIndex[$index]['date']);
  78. }
  79. elseif (is_object($this->m_aIndex[$index]['date']))
  80. {
  81. if (version_compare(phpversion(), '5.3.0', '>='))
  82. {
  83. // DateTime
  84. $sDate = $this->m_aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
  85. }
  86. else
  87. {
  88. // No Warning... but the date is unknown
  89. $sDate = '';
  90. }
  91. }
  92. $aEntries[] = array(
  93. 'date' => $sDate,
  94. 'user_login' => $this->m_aIndex[$index]['user_name'],
  95. 'user_id' => $this->m_aIndex[$index]['user_id'],
  96. 'message' => $sTextEntry
  97. );
  98. }
  99. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  100. if ($iPos < (strlen($this->m_sLog) - 1))
  101. {
  102. $sTextEntry = substr($this->m_sLog, $iPos);
  103. $aEntries[] = array(
  104. 'date' => '',
  105. 'user_login' => '',
  106. 'message' => $sTextEntry
  107. );
  108. }
  109. // Order by ascending date
  110. $aRet = array('entries' => array_reverse($aEntries));
  111. return $aRet;
  112. }
  113. public function GetIndex()
  114. {
  115. return $this->m_aIndex;
  116. }
  117. public function __toString()
  118. {
  119. return $this->m_sLog;
  120. }
  121. public function ClearModifiedFlag()
  122. {
  123. $this->m_bModified = false;
  124. }
  125. /**
  126. * Produces an HTML representation, aimed at being used within an email
  127. */
  128. public function GetAsEmailHtml()
  129. {
  130. $sStyleCaseLogHeader = '';
  131. $sStyleCaseLogEntry = '';
  132. $sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
  133. $iPos = 0;
  134. $aIndex = $this->m_aIndex;
  135. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  136. {
  137. $iPos += $aIndex[$index]['separator_length'];
  138. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  139. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  140. $iPos += $aIndex[$index]['text_length'];
  141. $sEntry = '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
  142. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  143. // therefore we have changed the format. To preserve the compatibility with existing
  144. // installations of iTop, both format are allowed:
  145. // the 'date' item is either a DateTime object, or a unix timestamp
  146. if (is_int($aIndex[$index]['date']))
  147. {
  148. // Unix timestamp
  149. $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
  150. }
  151. elseif (is_object($aIndex[$index]['date']))
  152. {
  153. if (version_compare(phpversion(), '5.3.0', '>='))
  154. {
  155. // DateTime
  156. $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
  157. }
  158. else
  159. {
  160. // No Warning... but the date is unknown
  161. $sDate = '';
  162. }
  163. }
  164. $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
  165. $sEntry .= '</div>';
  166. $sEntry .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'">';
  167. $sEntry .= $sTextEntry;
  168. $sEntry .= '</div>';
  169. $sHtml = $sHtml.$sEntry;
  170. }
  171. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  172. if ($iPos < (strlen($this->m_sLog) - 1))
  173. {
  174. $sTextEntry = substr($this->m_sLog, $iPos);
  175. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  176. if (count($this->m_aIndex) == 0)
  177. {
  178. $sHtml .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'"">';
  179. $sHtml .= $sTextEntry;
  180. $sHtml .= '</div>';
  181. }
  182. else
  183. {
  184. $sHtml .= '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
  185. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  186. $sHtml .= '</div>';
  187. $sHtml .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'">';
  188. $sHtml .= $sTextEntry;
  189. $sHtml .= '</div>';
  190. }
  191. }
  192. $sHtml .= '</td></tr></table>';
  193. return $sHtml;
  194. }
  195. /**
  196. * Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
  197. */
  198. public function GetAsSimpleHtml()
  199. {
  200. $sStyleCaseLogHeader = '';
  201. $sStyleCaseLogEntry = '';
  202. $sHtml = '<ul class="case_log_simple_html">';
  203. $iPos = 0;
  204. $aIndex = $this->m_aIndex;
  205. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  206. {
  207. $iPos += $aIndex[$index]['separator_length'];
  208. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  209. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  210. $iPos += $aIndex[$index]['text_length'];
  211. $sEntry = '<li>';
  212. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  213. // therefore we have changed the format. To preserve the compatibility with existing
  214. // installations of iTop, both format are allowed:
  215. // the 'date' item is either a DateTime object, or a unix timestamp
  216. if (is_int($aIndex[$index]['date']))
  217. {
  218. // Unix timestamp
  219. $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
  220. }
  221. elseif (is_object($aIndex[$index]['date']))
  222. {
  223. if (version_compare(phpversion(), '5.3.0', '>='))
  224. {
  225. // DateTime
  226. $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
  227. }
  228. else
  229. {
  230. // No Warning... but the date is unknown
  231. $sDate = '';
  232. }
  233. }
  234. $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), '<span class="caselog_header_date">'.$sDate.'</span>', '<span class="caselog_header_user">'.$aIndex[$index]['user_name'].'</span>');
  235. $sEntry .= '<div class="case_log_simple_html_entry" style="'.$sStyleCaseLogEntry.'">';
  236. $sEntry .= $sTextEntry;
  237. $sEntry .= '</div>';
  238. $sEntry .= '</li>';
  239. $sHtml = $sHtml.$sEntry;
  240. }
  241. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  242. if ($iPos < (strlen($this->m_sLog) - 1))
  243. {
  244. $sTextEntry = substr($this->m_sLog, $iPos);
  245. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  246. if (count($this->m_aIndex) == 0)
  247. {
  248. $sHtml .= '<li>';
  249. $sHtml .= $sTextEntry;
  250. $sHtml .= '</li>';
  251. }
  252. else
  253. {
  254. $sHtml .= '<li>';
  255. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  256. $sHtml .= '<div class="case_log_simple_html_entry" style="'.$sStyleCaseLogEntry.'">';
  257. $sHtml .= $sTextEntry;
  258. $sHtml .= '</div>';
  259. $sHtml .= '</li>';
  260. }
  261. }
  262. $sHtml .= '</ul>';
  263. return $sHtml;
  264. }
  265. /**
  266. * Produces an HTML representation, aimed at being used within the iTop framework
  267. */
  268. public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
  269. {
  270. $bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
  271. $sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
  272. $iPos = 0;
  273. $aIndex = $this->m_aIndex;
  274. if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
  275. {
  276. // Don't display the first element, that is still considered as editable
  277. $iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
  278. array_shift($aIndex);
  279. }
  280. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  281. {
  282. if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
  283. {
  284. $sOpen = '';
  285. $sDisplay = 'style="display:none;"';
  286. }
  287. else
  288. {
  289. $sOpen = ' open';
  290. $sDisplay = '';
  291. }
  292. $iPos += $aIndex[$index]['separator_length'];
  293. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  294. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  295. if (!is_null($aTransfoHandler))
  296. {
  297. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  298. }
  299. $iPos += $aIndex[$index]['text_length'];
  300. $sEntry = '<div class="caselog_header'.$sOpen.'">';
  301. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  302. // therefore we have changed the format. To preserve the compatibility with existing
  303. // installations of iTop, both format are allowed:
  304. // the 'date' item is either a DateTime object, or a unix timestamp
  305. if (is_int($aIndex[$index]['date']))
  306. {
  307. // Unix timestamp
  308. $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']);
  309. }
  310. elseif (is_object($aIndex[$index]['date']))
  311. {
  312. if (version_compare(phpversion(), '5.3.0', '>='))
  313. {
  314. // DateTime
  315. $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat'));
  316. }
  317. else
  318. {
  319. // No Warning... but the date is unknown
  320. $sDate = '';
  321. }
  322. }
  323. $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
  324. $sEntry .= '</div>';
  325. $sEntry .= '<div class="caselog_entry"'.$sDisplay.'>';
  326. $sEntry .= $sTextEntry;
  327. $sEntry .= '</div>';
  328. $sHtml = $sHtml.$sEntry;
  329. }
  330. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  331. if ($iPos < (strlen($this->m_sLog) - 1))
  332. {
  333. $sTextEntry = substr($this->m_sLog, $iPos);
  334. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  335. if (!is_null($aTransfoHandler))
  336. {
  337. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  338. }
  339. if (count($this->m_aIndex) == 0)
  340. {
  341. $sHtml .= '<div class="caselog_entry open">';
  342. $sHtml .= $sTextEntry;
  343. $sHtml .= '</div>';
  344. }
  345. else
  346. {
  347. if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
  348. {
  349. $sOpen = '';
  350. $sDisplay = 'style="display:none;"';
  351. }
  352. else
  353. {
  354. $sOpen = ' open';
  355. $sDisplay = '';
  356. }
  357. $sHtml .= '<div class="caselog_header'.$sOpen.'">';
  358. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  359. $sHtml .= '</div>';
  360. $sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
  361. $sHtml .= $sTextEntry;
  362. $sHtml .= '</div>';
  363. }
  364. }
  365. $sHtml .= '</td></tr></table>';
  366. return $sHtml;
  367. }
  368. /**
  369. * Add a new entry to the log or merge the given text into the currently modified entry
  370. * and updates the internal index
  371. * @param $sText string The text of the new entry
  372. */
  373. public function AddLogEntry($sText, $sOnBehalfOf = '')
  374. {
  375. $bMergeEntries = false;
  376. $sDate = date(Dict::S('UI:CaseLog:DateFormat'));
  377. if ($sOnBehalfOf == '')
  378. {
  379. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  380. $iUserId = UserRights::GetUserId();
  381. }
  382. else
  383. {
  384. $iUserId = null;
  385. }
  386. if ($this->m_bModified)
  387. {
  388. $aLatestEntry = end($this->m_aIndex);
  389. if ($aLatestEntry['user_name'] != $sOnBehalfOf)
  390. {
  391. $bMergeEntries = false;
  392. }
  393. else
  394. {
  395. $bMergeEntries = true;
  396. }
  397. }
  398. if ($bMergeEntries)
  399. {
  400. $aLatestEntry = end($this->m_aIndex);
  401. $this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length']);
  402. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  403. $iSepLength = strlen($sSeparator);
  404. $iTextlength = strlen($sText."\n");
  405. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  406. $this->m_aIndex[] = array(
  407. 'user_name' => $sOnBehalfOf,
  408. 'user_id' => $iUserId,
  409. 'date' => time(),
  410. 'text_length' => $aLatestEntry['text_length'] + $iTextlength,
  411. 'separator_length' => $iSepLength,
  412. );
  413. }
  414. else
  415. {
  416. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  417. $iSepLength = strlen($sSeparator);
  418. $iTextlength = strlen($sText);
  419. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  420. $this->m_aIndex[] = array(
  421. 'user_name' => $sOnBehalfOf,
  422. 'user_id' => $iUserId,
  423. 'date' => time(),
  424. 'text_length' => $iTextlength,
  425. 'separator_length' => $iSepLength,
  426. );
  427. }
  428. $this->m_bModified = true;
  429. }
  430. public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
  431. {
  432. $sText = isset($oJson->message) ? $oJson->message : '';
  433. if (isset($oJson->user_id))
  434. {
  435. if (!UserRights::IsAdministrator())
  436. {
  437. throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
  438. }
  439. if ($bCheckUserId && ($oJson->user_id != 0))
  440. {
  441. try
  442. {
  443. $oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
  444. }
  445. catch(Exception $e)
  446. {
  447. throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
  448. }
  449. $iUserId = $oUser->GetKey();
  450. $sOnBehalfOf = $oUser->GetFriendlyName();
  451. }
  452. else
  453. {
  454. $iUserId = $oJson->user_id;
  455. $sOnBehalfOf = $oJson->user_login;
  456. }
  457. }
  458. else
  459. {
  460. $iUserId = UserRights::GetUserId();
  461. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  462. }
  463. if (isset($oJson->date))
  464. {
  465. $oDate = new DateTime($oJson->date);
  466. $iDate = (int) $oDate->format('U');
  467. }
  468. else
  469. {
  470. $iDate = time();
  471. }
  472. $sDate = date(Dict::S('UI:CaseLog:DateFormat'), $iDate);
  473. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  474. $iSepLength = strlen($sSeparator);
  475. $iTextlength = strlen($sText);
  476. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  477. $this->m_aIndex[] = array(
  478. 'user_name' => $sOnBehalfOf,
  479. 'user_id' => $iUserId,
  480. 'date' => $iDate,
  481. 'text_length' => $iTextlength,
  482. 'separator_length' => $iSepLength,
  483. );
  484. $this->m_bModified = true;
  485. }
  486. public function GetModifiedEntry()
  487. {
  488. $sModifiedEntry = '';
  489. if ($this->m_bModified)
  490. {
  491. $sModifiedEntry = $this->GetLatestEntry();
  492. }
  493. return $sModifiedEntry;
  494. }
  495. /**
  496. * Get the latest entry from the log
  497. * @return string
  498. */
  499. public function GetLatestEntry()
  500. {
  501. $aLastEntry = end($this->m_aIndex);
  502. $sRes = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
  503. return $sRes;
  504. }
  505. /**
  506. * Get the index of the latest entry from the log
  507. * @return integer
  508. */
  509. public function GetLatestEntryIndex()
  510. {
  511. $aKeys = array_keys($this->m_aIndex);
  512. $iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
  513. return $iLast;
  514. }
  515. /**
  516. * Get the text string corresponding to the given entry in the log (zero based index, older entries first)
  517. * @param integer $iIndex
  518. * @return string The text of the entry
  519. */
  520. public function GetEntryAt($iIndex)
  521. {
  522. $iPos = 0;
  523. $index = count($this->m_aIndex) - 1;
  524. $aIndex = $this->m_aIndex;
  525. while($index > $iIndex)
  526. {
  527. $iPos += $this->m_aIndex[$index]['separator_length'];
  528. $iPos += $this->m_aIndex[$index]['text_length'];
  529. $index--;
  530. }
  531. $iPos += $this->m_aIndex[$index]['separator_length'];
  532. $sText = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
  533. return $sText;
  534. }
  535. }
  536. ?>