ormcaselog.class.inc.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
  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. 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-2016 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. $iPos = $aIndex[0]['separator_length'] + $aIndex[0]['text_length'];
  362. array_shift($aIndex);
  363. }
  364. for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
  365. {
  366. if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
  367. {
  368. $sOpen = '';
  369. $sDisplay = 'style="display:none;"';
  370. }
  371. else
  372. {
  373. $sOpen = ' open';
  374. $sDisplay = '';
  375. }
  376. $iPos += $aIndex[$index]['separator_length'];
  377. $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']);
  378. $sCSSClass= 'caselog_entry_html';
  379. if (!array_key_exists('format', $aIndex[$index]) || ($aIndex[$index]['format'] == 'text'))
  380. {
  381. $sCSSClass= 'caselog_entry';
  382. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  383. if (!is_null($aTransfoHandler))
  384. {
  385. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  386. }
  387. }
  388. else
  389. {
  390. if (!is_null($aTransfoHandler))
  391. {
  392. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry, true /* wiki "links" only */);
  393. }
  394. $sTextEntry = InlineImage::FixUrls($sTextEntry);
  395. }
  396. $iPos += $aIndex[$index]['text_length'];
  397. $sEntry = '<div class="caselog_header'.$sOpen.'">';
  398. // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects,
  399. // therefore we have changed the format. To preserve the compatibility with existing
  400. // installations of iTop, both format are allowed:
  401. // the 'date' item is either a DateTime object, or a unix timestamp
  402. if (is_int($aIndex[$index]['date']))
  403. {
  404. // Unix timestamp
  405. $sDate = date((string)AttributeDateTime::GetFormat(),$aIndex[$index]['date']);
  406. }
  407. elseif (is_object($aIndex[$index]['date']))
  408. {
  409. if (version_compare(phpversion(), '5.3.0', '>='))
  410. {
  411. // DateTime
  412. $sDate = $aIndex[$index]['date']->format((string)AttributeDateTime::GetFormat());
  413. }
  414. else
  415. {
  416. // No Warning... but the date is unknown
  417. $sDate = '';
  418. }
  419. }
  420. $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), $sDate, $aIndex[$index]['user_name']);
  421. $sEntry .= '</div>';
  422. $sEntry .= '<div class="'.$sCSSClass.'"'.$sDisplay.'>';
  423. $sEntry .= $sTextEntry;
  424. $sEntry .= '</div>';
  425. $sHtml = $sHtml.$sEntry;
  426. }
  427. // Process the case of an eventual remainder (quick migration of AttributeText fields)
  428. if ($iPos < (strlen($this->m_sLog) - 1))
  429. {
  430. // In this case the format is always "text"
  431. $sTextEntry = substr($this->m_sLog, $iPos);
  432. $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "<br/>", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8'));
  433. if (!is_null($aTransfoHandler))
  434. {
  435. $sTextEntry = call_user_func($aTransfoHandler, $sTextEntry);
  436. }
  437. if (count($this->m_aIndex) == 0)
  438. {
  439. $sHtml .= '<div class="caselog_entry open">';
  440. $sHtml .= $sTextEntry;
  441. $sHtml .= '</div>';
  442. }
  443. else
  444. {
  445. if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
  446. {
  447. $sOpen = '';
  448. $sDisplay = 'style="display:none;"';
  449. }
  450. else
  451. {
  452. $sOpen = ' open';
  453. $sDisplay = '';
  454. }
  455. $sHtml .= '<div class="caselog_header'.$sOpen.'">';
  456. $sHtml .= Dict::S('UI:CaseLog:InitialValue');
  457. $sHtml .= '</div>';
  458. $sHtml .= '<div class="caselog_entry"'.$sDisplay.'>';
  459. $sHtml .= $sTextEntry;
  460. $sHtml .= '</div>';
  461. }
  462. }
  463. $sHtml .= '</td></tr></table>';
  464. return $sHtml;
  465. }
  466. /**
  467. * Add a new entry to the log or merge the given text into the currently modified entry
  468. * and updates the internal index
  469. * @param $sText string The text of the new entry
  470. */
  471. public function AddLogEntry($sText, $sOnBehalfOf = '')
  472. {
  473. $sText = HTMLSanitizer::Sanitize($sText);
  474. $bMergeEntries = false;
  475. $sDate = date(AttributeDateTime::GetInternalFormat());
  476. if ($sOnBehalfOf == '')
  477. {
  478. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  479. $iUserId = UserRights::GetUserId();
  480. }
  481. else
  482. {
  483. $iUserId = null;
  484. }
  485. if ($this->m_bModified)
  486. {
  487. $aLatestEntry = end($this->m_aIndex);
  488. if ($aLatestEntry['user_name'] != $sOnBehalfOf)
  489. {
  490. $bMergeEntries = false;
  491. }
  492. else
  493. {
  494. $bMergeEntries = true;
  495. }
  496. }
  497. if ($bMergeEntries)
  498. {
  499. $aLatestEntry = end($this->m_aIndex);
  500. $this->m_sLog = substr($this->m_sLog, $aLatestEntry['separator_length']);
  501. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  502. $iSepLength = strlen($sSeparator);
  503. $iTextlength = strlen($sText."\n");
  504. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  505. $this->m_aIndex[] = array(
  506. 'user_name' => $sOnBehalfOf,
  507. 'user_id' => $iUserId,
  508. 'date' => time(),
  509. 'text_length' => $aLatestEntry['text_length'] + $iTextlength,
  510. 'separator_length' => $iSepLength,
  511. 'format' => 'html',
  512. );
  513. }
  514. else
  515. {
  516. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  517. $iSepLength = strlen($sSeparator);
  518. $iTextlength = strlen($sText);
  519. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  520. $this->m_aIndex[] = array(
  521. 'user_name' => $sOnBehalfOf,
  522. 'user_id' => $iUserId,
  523. 'date' => time(),
  524. 'text_length' => $iTextlength,
  525. 'separator_length' => $iSepLength,
  526. 'format' => 'html',
  527. );
  528. }
  529. $this->m_bModified = true;
  530. }
  531. public function AddLogEntryFromJSON($oJson, $bCheckUserId = true)
  532. {
  533. $sText = HTMLSanitizer::Sanitize(isset($oJson->message) ? $oJson->message : '');
  534. if (isset($oJson->user_id))
  535. {
  536. if (!UserRights::IsAdministrator())
  537. {
  538. throw new Exception("Only administrators can set the user id", RestResult::UNAUTHORIZED);
  539. }
  540. if ($bCheckUserId && ($oJson->user_id != 0))
  541. {
  542. try
  543. {
  544. $oUser = RestUtils::FindObjectFromKey('User', $oJson->user_id);
  545. }
  546. catch(Exception $e)
  547. {
  548. throw new Exception('user_id: '.$e->getMessage(), $e->getCode());
  549. }
  550. $iUserId = $oUser->GetKey();
  551. $sOnBehalfOf = $oUser->GetFriendlyName();
  552. }
  553. else
  554. {
  555. $iUserId = $oJson->user_id;
  556. $sOnBehalfOf = $oJson->user_login;
  557. }
  558. }
  559. else
  560. {
  561. $iUserId = UserRights::GetUserId();
  562. $sOnBehalfOf = UserRights::GetUserFriendlyName();
  563. }
  564. if (isset($oJson->date))
  565. {
  566. $oDate = new DateTime($oJson->date);
  567. $iDate = (int) $oDate->format('U');
  568. }
  569. else
  570. {
  571. $iDate = time();
  572. }
  573. if (isset($oJson->format))
  574. {
  575. $sFormat = $oJson->format;
  576. }
  577. else
  578. {
  579. // TODO: what is the default format ? text ?
  580. $sFormat = 'html';
  581. }
  582. $sDate = date(AttributeDateTime::GetInternalFormat(), $iDate);
  583. $sSeparator = sprintf(CASELOG_SEPARATOR, $sDate, $sOnBehalfOf, $iUserId);
  584. $iSepLength = strlen($sSeparator);
  585. $iTextlength = strlen($sText);
  586. $this->m_sLog = $sSeparator.$sText.$this->m_sLog; // Latest entry printed first
  587. $this->m_aIndex[] = array(
  588. 'user_name' => $sOnBehalfOf,
  589. 'user_id' => $iUserId,
  590. 'date' => $iDate,
  591. 'text_length' => $iTextlength,
  592. 'separator_length' => $iSepLength,
  593. 'format' => $sFormat,
  594. );
  595. $this->m_bModified = true;
  596. }
  597. public function GetModifiedEntry()
  598. {
  599. $sModifiedEntry = '';
  600. if ($this->m_bModified)
  601. {
  602. $sModifiedEntry = $this->GetLatestEntry();
  603. }
  604. return $sModifiedEntry;
  605. }
  606. /**
  607. * Get the latest entry from the log
  608. * @param string The expected output format text|html
  609. * @return string
  610. */
  611. public function GetLatestEntry($sFormat = 'text')
  612. {
  613. $sRes = '';
  614. $aLastEntry = end($this->m_aIndex);
  615. $sRaw = substr($this->m_sLog, $aLastEntry['separator_length'], $aLastEntry['text_length']);
  616. switch($sFormat)
  617. {
  618. case 'text':
  619. if ($aLastEntry['format'] == 'text')
  620. {
  621. $sRes = $sRaw;
  622. }
  623. else
  624. {
  625. $sRes = utils::HtmlToText($sRaw);
  626. }
  627. break;
  628. case 'html':
  629. if ($aLastEntry['format'] == 'text')
  630. {
  631. $sRes = utils::TextToHtml($sRaw);
  632. }
  633. else
  634. {
  635. $sRes = $sRaw;
  636. }
  637. break;
  638. }
  639. return $sRes;
  640. }
  641. /**
  642. * Get the index of the latest entry from the log
  643. * @return integer
  644. */
  645. public function GetLatestEntryIndex()
  646. {
  647. $aKeys = array_keys($this->m_aIndex);
  648. $iLast = end($aKeys); // Strict standards: the parameter passed to 'end' must be a variable since it is passed by reference
  649. return $iLast;
  650. }
  651. /**
  652. * Get the text string corresponding to the given entry in the log (zero based index, older entries first)
  653. * @param integer $iIndex
  654. * @return string The text of the entry
  655. */
  656. public function GetEntryAt($iIndex)
  657. {
  658. $iPos = 0;
  659. $index = count($this->m_aIndex) - 1;
  660. $aIndex = $this->m_aIndex;
  661. while($index > $iIndex)
  662. {
  663. $iPos += $this->m_aIndex[$index]['separator_length'];
  664. $iPos += $this->m_aIndex[$index]['text_length'];
  665. $index--;
  666. }
  667. $iPos += $this->m_aIndex[$index]['separator_length'];
  668. $sText = substr($this->m_sLog, $iPos, $this->m_aIndex[$index]['text_length']);
  669. return $sText;
  670. }
  671. }
  672. ?>