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