History.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. <?php
  2. /**
  3. * Class History
  4. *
  5. * Handle the history file tracing events in Shaarli.
  6. * The history is stored as JSON in a file set by 'resource.history' setting.
  7. *
  8. * Available data:
  9. * - event: event key
  10. * - datetime: event date, in ISO8601 format.
  11. * - id: event item identifier (currently only link IDs).
  12. *
  13. * Available event keys:
  14. * - CREATED: new link
  15. * - UPDATED: link updated
  16. * - DELETED: link deleted
  17. * - SETTINGS: the settings have been updated through the UI.
  18. * - IMPORT: bulk links import
  19. *
  20. * Note: new events are put at the beginning of the file and history array.
  21. */
  22. class History
  23. {
  24. /**
  25. * @var string Action key: a new link has been created.
  26. */
  27. const CREATED = 'CREATED';
  28. /**
  29. * @var string Action key: a link has been updated.
  30. */
  31. const UPDATED = 'UPDATED';
  32. /**
  33. * @var string Action key: a link has been deleted.
  34. */
  35. const DELETED = 'DELETED';
  36. /**
  37. * @var string Action key: settings have been updated.
  38. */
  39. const SETTINGS = 'SETTINGS';
  40. /**
  41. * @var string Action key: a bulk import has been processed.
  42. */
  43. const IMPORT = 'IMPORT';
  44. /**
  45. * @var string History file path.
  46. */
  47. protected $historyFilePath;
  48. /**
  49. * @var array History data.
  50. */
  51. protected $history;
  52. /**
  53. * @var int History retention time in seconds (1 month).
  54. */
  55. protected $retentionTime = 2678400;
  56. /**
  57. * History constructor.
  58. *
  59. * @param string $historyFilePath History file path.
  60. * @param int $retentionTime History content rentention time in seconds.
  61. *
  62. * @throws Exception if something goes wrong.
  63. */
  64. public function __construct($historyFilePath, $retentionTime = null)
  65. {
  66. $this->historyFilePath = $historyFilePath;
  67. if ($retentionTime !== null) {
  68. $this->retentionTime = $retentionTime;
  69. }
  70. }
  71. /**
  72. * Initialize: read history file.
  73. *
  74. * Allow lazy loading (don't read the file if it isn't necessary).
  75. */
  76. protected function initialize()
  77. {
  78. $this->check();
  79. $this->read();
  80. }
  81. /**
  82. * Add Event: new link.
  83. *
  84. * @param array $link Link data.
  85. */
  86. public function addLink($link)
  87. {
  88. $this->addEvent(self::CREATED, $link['id']);
  89. }
  90. /**
  91. * Add Event: update existing link.
  92. *
  93. * @param array $link Link data.
  94. */
  95. public function updateLink($link)
  96. {
  97. $this->addEvent(self::UPDATED, $link['id']);
  98. }
  99. /**
  100. * Add Event: delete existing link.
  101. *
  102. * @param array $link Link data.
  103. */
  104. public function deleteLink($link)
  105. {
  106. $this->addEvent(self::DELETED, $link['id']);
  107. }
  108. /**
  109. * Add Event: settings updated.
  110. */
  111. public function updateSettings()
  112. {
  113. $this->addEvent(self::SETTINGS);
  114. }
  115. /**
  116. * Add Event: bulk import.
  117. *
  118. * Note: we don't store links add/update one by one since it can have a huge impact on performances.
  119. */
  120. public function importLinks()
  121. {
  122. $this->addEvent(self::IMPORT);
  123. }
  124. /**
  125. * Save a new event and write it in the history file.
  126. *
  127. * @param string $status Event key, should be defined as constant.
  128. * @param mixed $id Event item identifier (e.g. link ID).
  129. */
  130. protected function addEvent($status, $id = null)
  131. {
  132. if ($this->history === null) {
  133. $this->initialize();
  134. }
  135. $item = [
  136. 'event' => $status,
  137. 'datetime' => new DateTime(),
  138. 'id' => $id !== null ? $id : '',
  139. ];
  140. $this->history = array_merge([$item], $this->history);
  141. $this->write();
  142. }
  143. /**
  144. * Check that the history file is writable.
  145. * Create the file if it doesn't exist.
  146. *
  147. * @throws Exception if it isn't writable.
  148. */
  149. protected function check()
  150. {
  151. if (! is_file($this->historyFilePath)) {
  152. FileUtils::writeFlatDB($this->historyFilePath, []);
  153. }
  154. if (! is_writable($this->historyFilePath)) {
  155. throw new Exception(t('History file isn\'t readable or writable'));
  156. }
  157. }
  158. /**
  159. * Read JSON history file.
  160. */
  161. protected function read()
  162. {
  163. $this->history = FileUtils::readFlatDB($this->historyFilePath, []);
  164. if ($this->history === false) {
  165. throw new Exception(t('Could not parse history file'));
  166. }
  167. }
  168. /**
  169. * Write JSON history file and delete old entries.
  170. */
  171. protected function write()
  172. {
  173. $comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
  174. foreach ($this->history as $key => $value) {
  175. if ($value['datetime'] < $comparaison) {
  176. unset($this->history[$key]);
  177. }
  178. }
  179. FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history));
  180. }
  181. /**
  182. * Get the History.
  183. *
  184. * @return array
  185. */
  186. public function getHistory()
  187. {
  188. if ($this->history === null) {
  189. $this->initialize();
  190. }
  191. return $this->history;
  192. }
  193. }