ormcaselog.class.inc.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707
  1. <?php
  2. // Copyright (C) 2010-2017 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-2017 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($bConvertToPlainText = false)
  42. {
  43. if ($bConvertToPlainText)
  44. {
  45. // Rebuild the log, but filtering any HTML markup for the all 'html' entries in the log
  46. return $this->GetAsPlainText();
  47. }
  48. else
  49. {
  50. return $this->m_sLog;
  51. }
  52. }
  53. public static function FromJSON($oJson)
  54. {
  55. if (!isset($oJson->items))
  56. {
  57. throw new Exception("Missing 'items' elements");
  58. }
  59. $oCaseLog = new ormCaseLog();
  60. foreach($oJson->items as $oItem)
  61. {
  62. $oCaseLog->AddLogEntryFromJSON($oItem);
  63. }
  64. return $oCaseLog;
  65. }
  66. /**
  67. * Return a value that will be further JSON encoded
  68. */
  69. public function GetForJSON()
  70. {
  71. // Order by ascending date
  72. $aRet = array('entries' => array_reverse($this->GetAsArray()));
  73. return $aRet;
  74. }
  75. /**
  76. * Return all the data, in a format that is suitable for programmatic usages:
  77. * -> dates not formatted
  78. * -> to preserve backward compatibility, to the returned structure must grow (new array entries)
  79. *
  80. * Format:
  81. * array (
  82. * array (
  83. * 'date' => <yyyy-mm-dd hh:mm:ss>,
  84. * 'user_login' => <user friendly name>
  85. * 'user_id' => OPTIONAL <id of the user account (caution: the object might have been deleted since)>
  86. * 'message' => <message as plain text (CR/LF), empty if message_html is given>
  87. * 'message_html' => <message with HTML markup, empty if message is given>
  88. * )
  89. *
  90. * @return array
  91. * @throws DictExceptionMissingString
  92. */
  93. public function GetAsArray()
  94. {
  95. $aEntries = array();
  96. $iPos = 0;
  97. for($index=count($this->m_aIndex)-1 ; $index >= 0 ; $index--)
  98. {
  99. $iPos += $this->m_aIndex[$index]['separator_length'];
  100. $sTextEntry = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
  101. $iPos += $this->m_aIndex[$index]['text_length'];
  102. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  103. // therefore we have changed the format. To preserve the compatibility with existing
  104. // installations of iTop, both format are allowed:
  105. // the 'date' item is either a DateTime object, or a unix timestamp
  106. if (is_int($this->m_aIndex[$index]['date']))
  107. {
  108. // Unix timestamp
  109. $sDate = date(AttributeDateTime::GetInternalFormat(),$this->m_aIndex[$index]['date']);
  110. }
  111. elseif (is_object($this->m_aIndex[$index]['date']))
  112. {
  113. if (version_compare(phpversion(), '5.3.0', '>='))
  114. {
  115. // DateTime
  116. $sDate = $this->m_aIndex[$index]['date']->format(AttributeDateTime::GetInternalFormat());
  117. }
  118. else
  119. {
  120. // No Warning... but the date is unknown
  121. $sDate = '';
  122. }
  123. }
  124. $sFormat = array_key_exists('format', $this->m_aIndex[$index]) ? $this->m_aIndex[$index]['format'] : 'text';
  125. switch($sFormat)
  126. {
  127. case 'text':
  128. $sHtmlEntry = utils::TextToHtml($sTextEntry);
  129. break;
  130. case 'html':
  131. $sHtmlEntry = $sTextEntry;
  132. $sTextEntry = utils::HtmlToText($sHtmlEntry);
  133. break;
  134. }
  135. $aEntries[] = array(
  136. 'date' => $sDate,
  137. 'user_login' => $this->m_aIndex[$index]['user_name'],
  138. 'user_id' => $this->m_aIndex[$index]['user_id'],
  139. 'message' => $sTextEntry,
  140. 'message_html' => $sHtmlEntry,
  141. );
  142. }
  143. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  144. if ($iPos < (strlen($this->m_sLog) - 1))
  145. {
  146. $sTextEntry = substr($this->m_sLog, $iPos);
  147. $aEntries[] = array(
  148. 'date' => '',
  149. 'user_login' => '',
  150. 'message' => $sTextEntry,
  151. 'message_html' => utils::TextToHtml($sTextEntry),
  152. );
  153. }
  154. return $aEntries;
  155. }
  156. /**
  157. * Returns a "plain text" version of the log (equivalent to $this->m_sLog) where all the HTML markup from the 'html' entries have been removed
  158. * @return string
  159. */
  160. public function GetAsPlainText()
  161. {
  162. $sPlainText = '';
  163. $aJSON = $this->GetForJSON();
  164. foreach($aJSON['entries'] as $aData)
  165. {
  166. $sSeparator = sprintf(CASELOG_SEPARATOR, $aData['date'], $aData['user_login'], $aData['user_id']);
  167. $sPlainText .= $sSeparator.$aData['message'];
  168. }
  169. return $sPlainText;
  170. }
  171. public function GetIndex()
  172. {
  173. return $this->m_aIndex;
  174. }
  175. public function __toString()
  176. {
  177. return $this->m_sLog;
  178. }
  179. public function ClearModifiedFlag()
  180. {
  181. $this->m_bModified = false;
  182. }
  183. /**
  184. * Produces an HTML representation, aimed at being used within an email
  185. */
  186. public function GetAsEmailHtml()
  187. {
  188. $sStyleCaseLogHeader = '';
  189. $sStyleCaseLogEntry = '';
  190. $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
  191. $iPos = 0;
  192. $aIndex = $this->m_aIndex;
  193. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  194. {
  195. $iPos += $aIndex[$index]['separator_length'];
  196. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  197. $sCSSClass = 'caselog_entry_html';
  198. if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
  199. {
  200. $sCSSClass = 'caselog_entry';
  201. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  202. }
  203. else
  204. {
  205. $sTextEntry = InlineImage::FixUrls($sTextEntry);
  206. }
  207. $iPos += $aIndex[$index]['text_length'];
  208. $sEntry = '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
  209. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  210. // therefore we have changed the format. To preserve the compatibility with existing
  211. // installations of iTop, both format are allowed:
  212. // the 'date' item is either a DateTime object, or a unix timestamp
  213. if (is_int($aIndex[$index]['date']))
  214. {
  215. // Unix timestamp
  216. $sDate = date((string)AttributeDateTime::GetFormat(), $aIndex[$index]['date']);
  217. }
  218. elseif (is_object($aIndex[$index]['date']))
  219. {
  220. if (version_compare(phpversion(), '5.3.0', '>='))
  221. {
  222. // DateTime
  223. $sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
  224. }
  225. else
  226. {
  227. // No Warning... but the date is unknown
  228. $sDate = '';
  229. }
  230. }
  231. $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>');
  232. $sEntry .= '</div>';
  233. $sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
  234. $sEntry .= $sTextEntry;
  235. $sEntry .= '</div>';
  236. $sHtml = $sHtml.$sEntry;
  237. }
  238. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  239. if ($iPos < (strlen($this->m_sLog) - 1))
  240. {
  241. $sTextEntry = substr($this->m_sLog, $iPos);
  242. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  243. if (count($this->m_aIndex) == 0)
  244. {
  245. $sHtml .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'"">';
  246. $sHtml .= $sTextEntry;
  247. $sHtml .= '</div>';
  248. }
  249. else
  250. {
  251. $sHtml .= '<div class="caselog_header" style="'.$sStyleCaseLogHeader.'">';
  252. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  253. $sHtml .= '</div>';
  254. $sHtml .= '<div class="caselog_entry" style="'.$sStyleCaseLogEntry.'">';
  255. $sHtml .= $sTextEntry;
  256. $sHtml .= '</div>';
  257. }
  258. }
  259. $sHtml .= '</td></tr></table>';
  260. return $sHtml;
  261. }
  262. /**
  263. * Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table)
  264. */
  265. public function GetAsSimpleHtml($aTransfoHandler = null)
  266. {
  267. $sStyleCaseLogHeader = '';
  268. $sStyleCaseLogEntry = '';
  269. $sHtml = '<ul class="case_log_simple_html">';
  270. $iPos = 0;
  271. $aIndex = $this->m_aIndex;
  272. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  273. {
  274. $iPos += $aIndex[$index]['separator_length'];
  275. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  276. $sCSSClass = 'case_log_simple_html_entry_html';
  277. if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
  278. {
  279. $sCSSClass = 'case_log_simple_html_entry';
  280. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  281. if (!is_null($aTransfoHandler))
  282. {
  283. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  284. }
  285. }
  286. else
  287. {
  288. if (!is_null($aTransfoHandler))
  289. {
  290. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
  291. }
  292. $sTextEntry = InlineImage::FixUrls($sTextEntry);
  293. }
  294. $iPos += $aIndex[$index]['text_length'];
  295. $sEntry = '<li>';
  296. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  297. // therefore we have changed the format. To preserve the compatibility with existing
  298. // installations of iTop, both format are allowed:
  299. // the 'date' item is either a DateTime object, or a unix timestamp
  300. if (is_int($aIndex[$index]['date']))
  301. {
  302. // Unix timestamp
  303. $sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
  304. }
  305. elseif (is_object($aIndex[$index]['date']))
  306. {
  307. if (version_compare(phpversion(), '5.3.0', '>='))
  308. {
  309. // DateTime
  310. $sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
  311. }
  312. else
  313. {
  314. // No Warning... but the date is unknown
  315. $sDate = '';
  316. }
  317. }
  318. $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>');
  319. $sEntry .= '<div class="'.$sCSSClass.'" style="'.$sStyleCaseLogEntry.'">';
  320. $sEntry .= $sTextEntry;
  321. $sEntry .= '</div>';
  322. $sEntry .= '</li>';
  323. $sHtml = $sHtml.$sEntry;
  324. }
  325. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  326. if ($iPos < (strlen($this->m_sLog) - 1))
  327. {
  328. $sTextEntry = substr($this->m_sLog, $iPos);
  329. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  330. if (count($this->m_aIndex) == 0)
  331. {
  332. $sHtml .= '<li>';
  333. $sHtml .= $sTextEntry;
  334. $sHtml .= '</li>';
  335. }
  336. else
  337. {
  338. $sHtml .= '<li>';
  339. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  340. $sHtml .= '<div class="case_log_simple_html_entry" style="'.$sStyleCaseLogEntry.'">';
  341. $sHtml .= $sTextEntry;
  342. $sHtml .= '</div>';
  343. $sHtml .= '</li>';
  344. }
  345. }
  346. $sHtml .= '</ul>';
  347. return $sHtml;
  348. }
  349. /**
  350. * Produces an HTML representation, aimed at being used within the iTop framework
  351. */
  352. public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
  353. {
  354. $bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
  355. $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
  356. $iPos = 0;
  357. $aIndex = $this->m_aIndex;
  358. if (($bEditMode) && (count($aIndex) > 0) && $this->m_bModified)
  359. {
  360. // Don't display the first element, that is still considered as editable
  361. $aLastEntry = end($aIndex);
  362. $iPos = $aLastEntry['separator_length'] + $aLastEntry['text_length'];
  363. array_pop($aIndex);
  364. }
  365. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  366. {
  367. if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
  368. {
  369. $sOpen = '';
  370. $sDisplay = 'style="display:none;"';
  371. }
  372. else
  373. {
  374. $sOpen = ' open';
  375. $sDisplay = '';
  376. }
  377. $iPos += $aIndex[$index]['separator_length'];
  378. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  379. $sCSSClass= 'caselog_entry_html';
  380. if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
  381. {
  382. $sCSSClass= 'caselog_entry';
  383. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  384. if (!is_null($aTransfoHandler))
  385. {
  386. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  387. }
  388. }
  389. else
  390. {
  391. if (!is_null($aTransfoHandler))
  392. {
  393. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
  394. }
  395. $sTextEntry = InlineImage::FixUrls($sTextEntry);
  396. }
  397. $iPos += $aIndex[$index]['text_length'];
  398. $sEntry = '<div class="caselog_header'.$sOpen.'">';
  399. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  400. // therefore we have changed the format. To preserve the compatibility with existing
  401. // installations of iTop, both format are allowed:
  402. // the 'date' item is either a DateTime object, or a unix timestamp
  403. if (is_int($aIndex[$index]['date']))
  404. {
  405. // Unix timestamp
  406. $sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
  407. }
  408. elseif (is_object($aIndex[$index]['date']))
  409. {
  410. if (version_compare(phpversion(), '5.3.0', '>='))
  411. {
  412. // DateTime
  413. $sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
  414. }
  415. else
  416. {
  417. // No Warning... but the date is unknown
  418. $sDate = '';
  419. }
  420. }
  421. $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
  422. $sEntry .= '</div>';
  423. $sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
  424. $sEntry .= $sTextEntry;
  425. $sEntry .= '</div>';
  426. $sHtml = $sHtml.$sEntry;
  427. }
  428. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  429. if ($iPos < (strlen($this->m_sLog) - 1))
  430. {
  431. // In this case the format is always "text"
  432. $sTextEntry = substr($this->m_sLog, $iPos);
  433. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  434. if (!is_null($aTransfoHandler))
  435. {
  436. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  437. }
  438. if (count($this->m_aIndex) == 0)
  439. {
  440. $sHtml .= '<div class="caselog_entry open">';
  441. $sHtml .= $sTextEntry;
  442. $sHtml .= '</div>';
  443. }
  444. else
  445. {
  446. if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
  447. {
  448. $sOpen = '';
  449. $sDisplay = 'style="display:none;"';
  450. }
  451. else
  452. {
  453. $sOpen = ' open';
  454. $sDisplay = '';
  455. }
  456. $sHtml .= '<div class="caselog_header'.$sOpen.'">';
  457. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  458. $sHtml .= '</div>';
  459. $sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
  460. $sHtml .= $sTextEntry;
  461. $sHtml .= '</div>';
  462. }
  463. }
  464. $sHtml .= '</td></tr></table>';
  465. return $sHtml;
  466. }
  467. /**
  468. * Add a new entry to the log or merge the given text into the currently modified entry
  469. * and updates the internal index
  470. * @param $sText string The text of the new entry
  471. */
  472. public function AddLogEntry($sText, $sOnBehalfOf = '')
  473. {
  474. $sText = HTMLSanitizer::Sanitize($sText);
  475. $bMergeEntries = false;
  476. $sDate = date(AttributeDateTime::GetInternalFormat());
  477. if ($sOnBehalfOf == '')
  478. {
  479. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  480. $iUserId = UserRights::GetUserId();
  481. }
  482. else
  483. {
  484. $iUserId = null;
  485. }
  486. if ($this->m_bModified)
  487. {
  488. $aLatestEntry = end($this->m_aIndex);
  489. if ($aLatestEntry['user_name'] == $sOnBehalfOf)
  490. {
  491. // Append the new text to the previous one
  492. $sPreviousText = substr($this->m_sLog, $aLatestEntry['separator_length'], $aLatestEntry['text_length']);
  493. $sText = $sPreviousText."\n".$sText;
  494. // Cleanup the previous entry
  495. array_pop($this->m_aIndex);
  496. $this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length'] + $aLatestEntry['text_length']);
  497. }
  498. }
  499. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  500. $iSepLength = strlen($sSeparator);
  501. $iTextlength = strlen($sText);
  502. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  503. $this->m_aIndex[] = array(
  504. 'user_name' => $sOnBehalfOf,
  505. 'user_id' => $iUserId,
  506. 'date' => time(),
  507. 'text_length' => $iTextlength,
  508. 'separator_length' => $iSepLength,
  509. 'format' => 'html',
  510. );
  511. $this->m_bModified = true;
  512. }
  513. public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
  514. {
  515. if (isset($oJson->user_id))
  516. {
  517. if (!UserRights::IsAdministrator())
  518. {
  519. throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
  520. }
  521. if ($bCheckUserId && ($oJson->user_id != 0))
  522. {
  523. try
  524. {
  525. $oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
  526. }
  527. catch(Exception $e)
  528. {
  529. throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
  530. }
  531. $iUserId = $oUser->GetKey();
  532. $sOnBehalfOf = $oUser->GetFriendlyName();
  533. }
  534. else
  535. {
  536. $iUserId = $oJson->user_id;
  537. $sOnBehalfOf = $oJson->user_login;
  538. }
  539. }
  540. else
  541. {
  542. $iUserId = UserRights::GetUserId();
  543. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  544. }
  545. if (isset($oJson->date))
  546. {
  547. $oDate = new DateTime($oJson->date);
  548. $iDate = (int) $oDate->format('U');
  549. }
  550. else
  551. {
  552. $iDate = time();
  553. }
  554. if (isset($oJson->format))
  555. {
  556. $sFormat = $oJson->format;
  557. }
  558. else
  559. {
  560. // The default is HTML
  561. $sFormat = 'html';
  562. }
  563. $sText = isset($oJson->message) ? $oJson->message : '';
  564. if ($sFormat == 'html')
  565. {
  566. $sText = HTMLSanitizer::Sanitize($sText);
  567. }
  568. $sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
  569. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  570. $iSepLength = strlen($sSeparator);
  571. $iTextlength = strlen($sText);
  572. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  573. $this->m_aIndex[] = array(
  574. 'user_name' => $sOnBehalfOf,
  575. 'user_id' => $iUserId,
  576. 'date' => $iDate,
  577. 'text_length' => $iTextlength,
  578. 'separator_length' => $iSepLength,
  579. 'format' => $sFormat,
  580. );
  581. $this->m_bModified = true;
  582. }
  583. public function GetModifiedEntry($sFormat = 'text')
  584. {
  585. $sModifiedEntry = '';
  586. if ($this->m_bModified)
  587. {
  588. $sModifiedEntry = $this->GetLatestEntry($sFormat);
  589. }
  590. return $sModifiedEntry;
  591. }
  592. /**
  593. * Get the latest entry from the log
  594. * @param string The expected output format text|html
  595. * @return string
  596. */
  597. public function GetLatestEntry($sFormat = 'text')
  598. {
  599. $sRes = '';
  600. $aLastEntry = end($this->m_aIndex);
  601. $sRaw = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
  602. switch($sFormat)
  603. {
  604. case 'text':
  605. if ($aLastEntry['format'] == 'text')
  606. {
  607. $sRes = $sRaw;
  608. }
  609. else
  610. {
  611. $sRes = utils::HtmlToText($sRaw);
  612. }
  613. break;
  614. case 'html':
  615. if ($aLastEntry['format'] == 'text')
  616. {
  617. $sRes = utils::TextToHtml($sRaw);
  618. }
  619. else
  620. {
  621. $sRes = $sRaw;
  622. }
  623. break;
  624. }
  625. return $sRes;
  626. }
  627. /**
  628. * Get the index of the latest entry from the log
  629. * @return integer
  630. */
  631. public function GetLatestEntryIndex()
  632. {
  633. $aKeys = array_keys($this->m_aIndex);
  634. $iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
  635. return $iLast;
  636. }
  637. /**
  638. * Get the text string corresponding to the given entry in the log (zero based index, older entries first)
  639. * @param integer $iIndex
  640. * @return string The text of the entry
  641. */
  642. public function GetEntryAt($iIndex)
  643. {
  644. $iPos = 0;
  645. $index = count($this->m_aIndex) - 1;
  646. $aIndex = $this->m_aIndex;
  647. while($index > $iIndex)
  648. {
  649. $iPos += $this->m_aIndex[$index]['separator_length'];
  650. $iPos += $this->m_aIndex[$index]['text_length'];
  651. $index--;
  652. }
  653. $iPos += $this->m_aIndex[$index]['separator_length'];
  654. $sText = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
  655. return $sText;
  656. }
  657. }
  658. ?>