aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2017-01-16 12:31:08 +0100
committerArthurHoaro <arthur@hoa.ro>2017-03-21 20:29:20 +0100
commit4306b184c4471825f916d895b047ed03fdf58985 (patch)
treeae4ffd760d74e58bf469f743076aecf838f634f2
parentb2306b0c783365e3f8110ae25bc93f2630b8b2c8 (diff)
downloadShaarli-4306b184c4471825f916d895b047ed03fdf58985.tar.gz
Shaarli-4306b184c4471825f916d895b047ed03fdf58985.tar.zst
Shaarli-4306b184c4471825f916d895b047ed03fdf58985.zip
History mechanism
Use case: rest API service * saved by default in data/history * same format as datastore.php * traced events: * save/edit/delete link * change settings or plugins settings * rename tag
-rw-r--r--application/History.php183
-rw-r--r--application/NetscapeBookmarkUtils.php5
-rw-r--r--application/config/ConfigManager.php1
-rw-r--r--index.php23
-rw-r--r--tests/HistoryTest.php195
-rw-r--r--tests/NetscapeBookmarkUtils/BookmarkImportTest.php81
6 files changed, 469 insertions, 19 deletions
diff --git a/application/History.php b/application/History.php
new file mode 100644
index 00000000..c06067df
--- /dev/null
+++ b/application/History.php
@@ -0,0 +1,183 @@
1<?php
2
3/**
4 * Class History
5 *
6 * Handle the history file tracing events in Shaarli.
7 * The history is stored as JSON in a file set by 'resource.history' setting.
8 *
9 * Available data:
10 * - event: event key
11 * - datetime: event date, in ISO8601 format.
12 * - id: event item identifier (currently only link IDs).
13 *
14 * Available event keys:
15 * - CREATED: new link
16 * - UPDATED: link updated
17 * - DELETED: link deleted
18 * - SETTINGS: the settings have been updated through the UI.
19 *
20 * Note: new events are put at the beginning of the file and history array.
21 */
22class History
23{
24 /**
25 * @var string Action key: a new link has been created.
26 */
27 const CREATED = 'CREATED';
28
29 /**
30 * @var string Action key: a link has been updated.
31 */
32 const UPDATED = 'UPDATED';
33
34 /**
35 * @var string Action key: a link has been deleted.
36 */
37 const DELETED = 'DELETED';
38
39 /**
40 * @var string Action key: settings have been updated.
41 */
42 const SETTINGS = 'SETTINGS';
43
44 /**
45 * @var string History file path.
46 */
47 protected $historyFilePath;
48
49 /**
50 * @var array History data.
51 */
52 protected $history;
53
54 /**
55 * @var int History retention time in seconds (1 month).
56 */
57 protected $retentionTime = 2678400;
58
59 /**
60 * History constructor.
61 *
62 * @param string $historyFilePath History file path.
63 * @param int $retentionTime History content rentention time in seconds.
64 *
65 * @throws Exception if something goes wrong.
66 */
67 public function __construct($historyFilePath, $retentionTime = null)
68 {
69 $this->historyFilePath = $historyFilePath;
70 if ($retentionTime !== null) {
71 $this->retentionTime = $retentionTime;
72 }
73 $this->check();
74 $this->read();
75 }
76
77 /**
78 * Add Event: new link.
79 *
80 * @param array $link Link data.
81 */
82 public function addLink($link)
83 {
84 $this->addEvent(self::CREATED, $link['id']);
85 }
86
87 /**
88 * Add Event: update existing link.
89 *
90 * @param array $link Link data.
91 */
92 public function updateLink($link)
93 {
94 $this->addEvent(self::UPDATED, $link['id']);
95 }
96
97 /**
98 * Add Event: delete existing link.
99 *
100 * @param array $link Link data.
101 */
102 public function deleteLink($link)
103 {
104 $this->addEvent(self::DELETED, $link['id']);
105 }
106
107 /**
108 * Add Event: settings updated.
109 */
110 public function updateSettings()
111 {
112 $this->addEvent(self::SETTINGS);
113 }
114
115 /**
116 * Save a new event and write it in the history file.
117 *
118 * @param string $status Event key, should be defined as constant.
119 * @param mixed $id Event item identifier (e.g. link ID).
120 */
121 protected function addEvent($status, $id = null)
122 {
123 $item = [
124 'event' => $status,
125 'datetime' => (new DateTime())->format(DateTime::ATOM),
126 'id' => $id !== null ? $id : '',
127 ];
128 $this->history = array_merge([$item], $this->history);
129 $this->write();
130 }
131
132 /**
133 * Check that the history file is writable.
134 * Create the file if it doesn't exist.
135 *
136 * @throws Exception if it isn't writable.
137 */
138 protected function check()
139 {
140 if (! is_file($this->historyFilePath)) {
141 FileUtils::writeFlatDB($this->historyFilePath, []);
142 }
143
144 if (! is_writable($this->historyFilePath)) {
145 throw new Exception('History file isn\'t readable or writable');
146 }
147 }
148
149 /**
150 * Read JSON history file.
151 */
152 protected function read()
153 {
154 $this->history = FileUtils::readFlatDB($this->historyFilePath, []);
155 if ($this->history === false) {
156 throw new Exception('Could not parse history file');
157 }
158 }
159
160 /**
161 * Write JSON history file and delete old entries.
162 */
163 protected function write()
164 {
165 $comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
166 foreach ($this->history as $key => $value) {
167 if (DateTime::createFromFormat(DateTime::ATOM, $value['datetime']) < $comparaison) {
168 unset($this->history[$key]);
169 }
170 }
171 FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history));
172 }
173
174 /**
175 * Get the History.
176 *
177 * @return array
178 */
179 public function getHistory()
180 {
181 return $this->history;
182 }
183}
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index ab346f81..bbfde138 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -95,10 +95,11 @@ class NetscapeBookmarkUtils
95 * @param array $files Server $_FILES parameters 95 * @param array $files Server $_FILES parameters
96 * @param LinkDB $linkDb Loaded LinkDB instance 96 * @param LinkDB $linkDb Loaded LinkDB instance
97 * @param ConfigManager $conf instance 97 * @param ConfigManager $conf instance
98 * @param History $history History instance
98 * 99 *
99 * @return string Summary of the bookmark import status 100 * @return string Summary of the bookmark import status
100 */ 101 */
101 public static function import($post, $files, $linkDb, $conf) 102 public static function import($post, $files, $linkDb, $conf, $history)
102 { 103 {
103 $filename = $files['filetoupload']['name']; 104 $filename = $files['filetoupload']['name'];
104 $filesize = $files['filetoupload']['size']; 105 $filesize = $files['filetoupload']['size'];
@@ -182,6 +183,7 @@ class NetscapeBookmarkUtils
182 $linkDb[$existingLink['id']] = $newLink; 183 $linkDb[$existingLink['id']] = $newLink;
183 $importCount++; 184 $importCount++;
184 $overwriteCount++; 185 $overwriteCount++;
186 $history->updateLink($newLink);
185 continue; 187 continue;
186 } 188 }
187 189
@@ -193,6 +195,7 @@ class NetscapeBookmarkUtils
193 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); 195 $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
194 $linkDb[$newLink['id']] = $newLink; 196 $linkDb[$newLink['id']] = $newLink;
195 $importCount++; 197 $importCount++;
198 $history->addLink($newLink);
196 } 199 }
197 200
198 $linkDb->save($conf->get('resource.page_cache')); 201 $linkDb->save($conf->get('resource.page_cache'));
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 7bfbfc72..86a917fb 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -301,6 +301,7 @@ class ConfigManager
301 $this->setEmpty('resource.updates', 'data/updates.txt'); 301 $this->setEmpty('resource.updates', 'data/updates.txt');
302 $this->setEmpty('resource.log', 'data/log.txt'); 302 $this->setEmpty('resource.log', 'data/log.txt');
303 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt'); 303 $this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
304 $this->setEmpty('resource.history', 'data/history.php');
304 $this->setEmpty('resource.raintpl_tpl', 'tpl/'); 305 $this->setEmpty('resource.raintpl_tpl', 'tpl/');
305 $this->setEmpty('resource.theme', 'default'); 306 $this->setEmpty('resource.theme', 'default');
306 $this->setEmpty('resource.raintpl_tmp', 'tmp/'); 307 $this->setEmpty('resource.raintpl_tmp', 'tmp/');
diff --git a/index.php b/index.php
index cc7f3ca3..7f357c69 100644
--- a/index.php
+++ b/index.php
@@ -65,6 +65,7 @@ require_once 'application/CachedPage.php';
65require_once 'application/config/ConfigPlugin.php'; 65require_once 'application/config/ConfigPlugin.php';
66require_once 'application/FeedBuilder.php'; 66require_once 'application/FeedBuilder.php';
67require_once 'application/FileUtils.php'; 67require_once 'application/FileUtils.php';
68require_once 'application/History.php';
68require_once 'application/HttpUtils.php'; 69require_once 'application/HttpUtils.php';
69require_once 'application/Languages.php'; 70require_once 'application/Languages.php';
70require_once 'application/LinkDB.php'; 71require_once 'application/LinkDB.php';
@@ -754,6 +755,12 @@ function renderPage($conf, $pluginManager, $LINKSDB)
754 die($e->getMessage()); 755 die($e->getMessage());
755 } 756 }
756 757
758 try {
759 $history = new History($conf->get('resource.history'));
760 } catch(Exception $e) {
761 die($e->getMessage());
762 }
763
757 $PAGE = new PageBuilder($conf); 764 $PAGE = new PageBuilder($conf);
758 $PAGE->assign('linkcount', count($LINKSDB)); 765 $PAGE->assign('linkcount', count($LINKSDB));
759 $PAGE->assign('privateLinkcount', count_private($LINKSDB)); 766 $PAGE->assign('privateLinkcount', count_private($LINKSDB));
@@ -1146,6 +1153,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1146 $conf->set('api.secret', escape($_POST['apiSecret'])); 1153 $conf->set('api.secret', escape($_POST['apiSecret']));
1147 try { 1154 try {
1148 $conf->write(isLoggedIn()); 1155 $conf->write(isLoggedIn());
1156 $history->updateSettings();
1149 invalidateCaches($conf->get('resource.page_cache')); 1157 invalidateCaches($conf->get('resource.page_cache'));
1150 } 1158 }
1151 catch(Exception $e) { 1159 catch(Exception $e) {
@@ -1177,6 +1185,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1177 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); 1185 $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
1178 $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); 1186 $PAGE->assign('api_enabled', $conf->get('api.enabled', true));
1179 $PAGE->assign('api_secret', $conf->get('api.secret')); 1187 $PAGE->assign('api_secret', $conf->get('api.secret'));
1188 $history->updateSettings();
1180 $PAGE->renderPage('configure'); 1189 $PAGE->renderPage('configure');
1181 exit; 1190 exit;
1182 } 1191 }
@@ -1206,6 +1215,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1206 unset($tags[array_search($needle,$tags)]); // Remove tag. 1215 unset($tags[array_search($needle,$tags)]); // Remove tag.
1207 $value['tags']=trim(implode(' ',$tags)); 1216 $value['tags']=trim(implode(' ',$tags));
1208 $LINKSDB[$key]=$value; 1217 $LINKSDB[$key]=$value;
1218 $history->updateLink($LINKSDB[$key]);
1209 } 1219 }
1210 $LINKSDB->save($conf->get('resource.page_cache')); 1220 $LINKSDB->save($conf->get('resource.page_cache'));
1211 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; 1221 echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>';
@@ -1223,6 +1233,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1223 $tags[array_search($needle, $tags)] = trim($_POST['totag']); 1233 $tags[array_search($needle, $tags)] = trim($_POST['totag']);
1224 $value['tags'] = implode(' ', array_unique($tags)); 1234 $value['tags'] = implode(' ', array_unique($tags));
1225 $LINKSDB[$key] = $value; 1235 $LINKSDB[$key] = $value;
1236 $history->updateLink($LINKSDB[$key]);
1226 } 1237 }
1227 $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. 1238 $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
1228 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; 1239 echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>';
@@ -1257,11 +1268,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1257 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); 1268 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1258 $updated = new DateTime(); 1269 $updated = new DateTime();
1259 $shortUrl = $LINKSDB[$id]['shorturl']; 1270 $shortUrl = $LINKSDB[$id]['shorturl'];
1271 $new = false;
1260 } else { 1272 } else {
1261 // New link 1273 // New link
1262 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); 1274 $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
1263 $updated = null; 1275 $updated = null;
1264 $shortUrl = link_small_hash($created, $id); 1276 $shortUrl = link_small_hash($created, $id);
1277 $new = true;
1265 } 1278 }
1266 1279
1267 // Remove multiple spaces. 1280 // Remove multiple spaces.
@@ -1300,6 +1313,11 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1300 1313
1301 $LINKSDB[$id] = $link; 1314 $LINKSDB[$id] = $link;
1302 $LINKSDB->save($conf->get('resource.page_cache')); 1315 $LINKSDB->save($conf->get('resource.page_cache'));
1316 if ($new) {
1317 $history->addLink($link);
1318 } else {
1319 $history->updateLink($link);
1320 }
1303 1321
1304 // If we are called from the bookmarklet, we must close the popup: 1322 // If we are called from the bookmarklet, we must close the popup:
1305 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { 1323 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
@@ -1346,6 +1364,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1346 $pluginManager->executeHooks('delete_link', $link); 1364 $pluginManager->executeHooks('delete_link', $link);
1347 unset($LINKSDB[$id]); 1365 unset($LINKSDB[$id]);
1348 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk 1366 $LINKSDB->save($conf->get('resource.page_cache')); // save to disk
1367 $history->deleteLink($link);
1349 1368
1350 // If we are called from the bookmarklet, we must close the popup: 1369 // If we are called from the bookmarklet, we must close the popup:
1351 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 1370 if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
@@ -1528,7 +1547,8 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1528 $_POST, 1547 $_POST,
1529 $_FILES, 1548 $_FILES,
1530 $LINKSDB, 1549 $LINKSDB,
1531 $conf 1550 $conf,
1551 $history
1532 ); 1552 );
1533 echo '<script>alert("'.$status.'");document.location=\'?do=' 1553 echo '<script>alert("'.$status.'");document.location=\'?do='
1534 .Router::$PAGE_IMPORT .'\';</script>'; 1554 .Router::$PAGE_IMPORT .'\';</script>';
@@ -1557,6 +1577,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
1557 1577
1558 // Plugin administration form action 1578 // Plugin administration form action
1559 if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { 1579 if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
1580 $history->updateSettings();
1560 try { 1581 try {
1561 if (isset($_POST['parameters_form'])) { 1582 if (isset($_POST['parameters_form'])) {
1562 unset($_POST['parameters_form']); 1583 unset($_POST['parameters_form']);
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php
new file mode 100644
index 00000000..79322249
--- /dev/null
+++ b/tests/HistoryTest.php
@@ -0,0 +1,195 @@
1<?php
2
3require_once 'application/History.php';
4
5
6class HistoryTest extends PHPUnit_Framework_TestCase
7{
8 /**
9 * @var string History file path
10 */
11 protected static $historyFilePath = 'sandbox/history.php';
12
13 /**
14 * Delete history file.
15 */
16 public function tearDown()
17 {
18 @unlink(self::$historyFilePath);
19 }
20
21 /**
22 * Test that the history file is created if it doesn't exist.
23 */
24 public function testConstructFileCreated()
25 {
26 new History(self::$historyFilePath);
27 $this->assertFileExists(self::$historyFilePath);
28 }
29
30 /**
31 * Not writable history file: raise an exception.
32 *
33 * @expectedException Exception
34 * @expectedExceptionMessage History file isn't readable or writable
35 */
36 public function testConstructNotWritable()
37 {
38 touch(self::$historyFilePath);
39 chmod(self::$historyFilePath, 0440);
40 new History(self::$historyFilePath);
41 }
42
43 /**
44 * Not parsable history file: raise an exception.
45 *
46 * @expectedException Exception
47 * @expectedExceptionMessageRegExp /Could not parse history file/
48 */
49 public function testConstructNotParsable()
50 {
51 file_put_contents(self::$historyFilePath, 'not parsable');
52 // gzinflate generates a warning
53 @new History(self::$historyFilePath);
54 }
55
56 /**
57 * Test add link event
58 */
59 public function testAddLink()
60 {
61 $history = new History(self::$historyFilePath);
62 $history->addLink(['id' => 0]);
63 $actual = $history->getHistory()[0];
64 $this->assertEquals(History::CREATED, $actual['event']);
65 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
66 $this->assertEquals(0, $actual['id']);
67
68 $history = new History(self::$historyFilePath);
69 $history->addLink(['id' => 1]);
70 $actual = $history->getHistory()[0];
71 $this->assertEquals(History::CREATED, $actual['event']);
72 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
73 $this->assertEquals(1, $actual['id']);
74
75 $history = new History(self::$historyFilePath);
76 $history->addLink(['id' => 'str']);
77 $actual = $history->getHistory()[0];
78 $this->assertEquals(History::CREATED, $actual['event']);
79 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
80 $this->assertEquals('str', $actual['id']);
81 }
82
83 /**
84 * Test updated link event
85 */
86 public function testUpdateLink()
87 {
88 $history = new History(self::$historyFilePath);
89 $history->updateLink(['id' => 1]);
90 $actual = $history->getHistory()[0];
91 $this->assertEquals(History::UPDATED, $actual['event']);
92 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
93 $this->assertEquals(1, $actual['id']);
94 }
95
96 /**
97 * Test delete link event
98 */
99 public function testDeleteLink()
100 {
101 $history = new History(self::$historyFilePath);
102 $history->deleteLink(['id' => 1]);
103 $actual = $history->getHistory()[0];
104 $this->assertEquals(History::DELETED, $actual['event']);
105 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
106 $this->assertEquals(1, $actual['id']);
107 }
108
109 /**
110 * Test updated settings event
111 */
112 public function testUpdateSettings()
113 {
114 $history = new History(self::$historyFilePath);
115 $history->updateSettings();
116 $actual = $history->getHistory()[0];
117 $this->assertEquals(History::SETTINGS, $actual['event']);
118 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
119 $this->assertEmpty($actual['id']);
120 }
121
122 /**
123 * Make sure that new items are stored at the beginning
124 */
125 public function testHistoryOrder()
126 {
127 $history = new History(self::$historyFilePath);
128 $history->updateLink(['id' => 1]);
129 $actual = $history->getHistory()[0];
130 $this->assertEquals(History::UPDATED, $actual['event']);
131 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
132 $this->assertEquals(1, $actual['id']);
133
134 $history->addLink(['id' => 1]);
135 $actual = $history->getHistory()[0];
136 $this->assertEquals(History::CREATED, $actual['event']);
137 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
138 $this->assertEquals(1, $actual['id']);
139 }
140
141 /**
142 * Re-read history from file after writing an event
143 */
144 public function testHistoryRead()
145 {
146 $history = new History(self::$historyFilePath);
147 $history->updateLink(['id' => 1]);
148 $history = new History(self::$historyFilePath);
149 $actual = $history->getHistory()[0];
150 $this->assertEquals(History::UPDATED, $actual['event']);
151 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
152 $this->assertEquals(1, $actual['id']);
153 }
154
155 /**
156 * Re-read history from file after writing an event and make sure that the order is correct
157 */
158 public function testHistoryOrderRead()
159 {
160 $history = new History(self::$historyFilePath);
161 $history->updateLink(['id' => 1]);
162 $history->addLink(['id' => 1]);
163
164 $history = new History(self::$historyFilePath);
165 $actual = $history->getHistory()[0];
166 $this->assertEquals(History::CREATED, $actual['event']);
167 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
168 $this->assertEquals(1, $actual['id']);
169
170 $actual = $history->getHistory()[1];
171 $this->assertEquals(History::UPDATED, $actual['event']);
172 $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
173 $this->assertEquals(1, $actual['id']);
174 }
175
176 /**
177 * Test retention time: delete old entries.
178 */
179 public function testHistoryRententionTime()
180 {
181 $history = new History(self::$historyFilePath, 5);
182 $history->updateLink(['id' => 1]);
183 $this->assertEquals(1, count($history->getHistory()));
184 $arr = $history->getHistory();
185 $arr[0]['datetime'] = (new DateTime('-1 hour'))->format(DateTime::ATOM);
186 FileUtils::writeFlatDB(self::$historyFilePath, $arr);
187
188 $history = new History(self::$historyFilePath, 60);
189 $this->assertEquals(1, count($history->getHistory()));
190 $this->assertEquals(1, $history->getHistory()[0]['id']);
191 $history->updateLink(['id' => 2]);
192 $this->assertEquals(1, count($history->getHistory()));
193 $this->assertEquals(2, $history->getHistory()[0]['id']);
194 }
195}
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index 5925a8e1..f838f259 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -34,6 +34,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
34 protected static $testDatastore = 'sandbox/datastore.php'; 34 protected static $testDatastore = 'sandbox/datastore.php';
35 35
36 /** 36 /**
37 * @var string History file path
38 */
39 protected static $historyFilePath = 'sandbox/history.php';
40
41 /**
37 * @var LinkDB private LinkDB instance 42 * @var LinkDB private LinkDB instance
38 */ 43 */
39 protected $linkDb = null; 44 protected $linkDb = null;
@@ -49,6 +54,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
49 protected $conf; 54 protected $conf;
50 55
51 /** 56 /**
57 * @var History instance.
58 */
59 protected $history;
60
61 /**
52 * @var string Save the current timezone. 62 * @var string Save the current timezone.
53 */ 63 */
54 protected static $defaultTimeZone; 64 protected static $defaultTimeZone;
@@ -73,6 +83,15 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
73 $this->linkDb = new LinkDB(self::$testDatastore, true, false); 83 $this->linkDb = new LinkDB(self::$testDatastore, true, false);
74 $this->conf = new ConfigManager('tests/utils/config/configJson'); 84 $this->conf = new ConfigManager('tests/utils/config/configJson');
75 $this->conf->set('resource.page_cache', $this->pagecache); 85 $this->conf->set('resource.page_cache', $this->pagecache);
86 $this->history = new History(self::$historyFilePath);
87 }
88
89 /**
90 * Delete history file.
91 */
92 public function tearDown()
93 {
94 @unlink(self::$historyFilePath);
76 } 95 }
77 96
78 public static function tearDownAfterClass() 97 public static function tearDownAfterClass()
@@ -89,7 +108,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
89 $this->assertEquals( 108 $this->assertEquals(
90 'File empty.htm (0 bytes) has an unknown file format.' 109 'File empty.htm (0 bytes) has an unknown file format.'
91 .' Nothing was imported.', 110 .' Nothing was imported.',
92 NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf) 111 NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history)
93 ); 112 );
94 $this->assertEquals(0, count($this->linkDb)); 113 $this->assertEquals(0, count($this->linkDb));
95 } 114 }
@@ -102,7 +121,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
102 $files = file2array('no_doctype.htm'); 121 $files = file2array('no_doctype.htm');
103 $this->assertEquals( 122 $this->assertEquals(
104 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', 123 'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.',
105 NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf) 124 NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history)
106 ); 125 );
107 $this->assertEquals(0, count($this->linkDb)); 126 $this->assertEquals(0, count($this->linkDb));
108 } 127 }
@@ -116,7 +135,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
116 $this->assertEquals( 135 $this->assertEquals(
117 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' 136 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
118 .' 1 links imported, 0 links overwritten, 0 links skipped.', 137 .' 1 links imported, 0 links overwritten, 0 links skipped.',
119 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) 138 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
120 ); 139 );
121 $this->assertEquals(1, count($this->linkDb)); 140 $this->assertEquals(1, count($this->linkDb));
122 $this->assertEquals(0, count_private($this->linkDb)); 141 $this->assertEquals(0, count_private($this->linkDb));
@@ -145,7 +164,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
145 $this->assertEquals( 164 $this->assertEquals(
146 'File netscape_nested.htm (1337 bytes) was successfully processed:' 165 'File netscape_nested.htm (1337 bytes) was successfully processed:'
147 .' 8 links imported, 0 links overwritten, 0 links skipped.', 166 .' 8 links imported, 0 links overwritten, 0 links skipped.',
148 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) 167 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
149 ); 168 );
150 $this->assertEquals(8, count($this->linkDb)); 169 $this->assertEquals(8, count($this->linkDb));
151 $this->assertEquals(2, count_private($this->linkDb)); 170 $this->assertEquals(2, count_private($this->linkDb));
@@ -267,7 +286,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
267 $this->assertEquals( 286 $this->assertEquals(
268 'File netscape_basic.htm (482 bytes) was successfully processed:' 287 'File netscape_basic.htm (482 bytes) was successfully processed:'
269 .' 2 links imported, 0 links overwritten, 0 links skipped.', 288 .' 2 links imported, 0 links overwritten, 0 links skipped.',
270 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) 289 NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
271 ); 290 );
272 291
273 $this->assertEquals(2, count($this->linkDb)); 292 $this->assertEquals(2, count($this->linkDb));
@@ -312,7 +331,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
312 $this->assertEquals( 331 $this->assertEquals(
313 'File netscape_basic.htm (482 bytes) was successfully processed:' 332 'File netscape_basic.htm (482 bytes) was successfully processed:'
314 .' 2 links imported, 0 links overwritten, 0 links skipped.', 333 .' 2 links imported, 0 links overwritten, 0 links skipped.',
315 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 334 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
316 ); 335 );
317 $this->assertEquals(2, count($this->linkDb)); 336 $this->assertEquals(2, count($this->linkDb));
318 $this->assertEquals(1, count_private($this->linkDb)); 337 $this->assertEquals(1, count_private($this->linkDb));
@@ -356,7 +375,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
356 $this->assertEquals( 375 $this->assertEquals(
357 'File netscape_basic.htm (482 bytes) was successfully processed:' 376 'File netscape_basic.htm (482 bytes) was successfully processed:'
358 .' 2 links imported, 0 links overwritten, 0 links skipped.', 377 .' 2 links imported, 0 links overwritten, 0 links skipped.',
359 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 378 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
360 ); 379 );
361 $this->assertEquals(2, count($this->linkDb)); 380 $this->assertEquals(2, count($this->linkDb));
362 $this->assertEquals(0, count_private($this->linkDb)); 381 $this->assertEquals(0, count_private($this->linkDb));
@@ -380,7 +399,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
380 $this->assertEquals( 399 $this->assertEquals(
381 'File netscape_basic.htm (482 bytes) was successfully processed:' 400 'File netscape_basic.htm (482 bytes) was successfully processed:'
382 .' 2 links imported, 0 links overwritten, 0 links skipped.', 401 .' 2 links imported, 0 links overwritten, 0 links skipped.',
383 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 402 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
384 ); 403 );
385 $this->assertEquals(2, count($this->linkDb)); 404 $this->assertEquals(2, count($this->linkDb));
386 $this->assertEquals(2, count_private($this->linkDb)); 405 $this->assertEquals(2, count_private($this->linkDb));
@@ -406,7 +425,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
406 $this->assertEquals( 425 $this->assertEquals(
407 'File netscape_basic.htm (482 bytes) was successfully processed:' 426 'File netscape_basic.htm (482 bytes) was successfully processed:'
408 .' 2 links imported, 0 links overwritten, 0 links skipped.', 427 .' 2 links imported, 0 links overwritten, 0 links skipped.',
409 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 428 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
410 ); 429 );
411 $this->assertEquals(2, count($this->linkDb)); 430 $this->assertEquals(2, count($this->linkDb));
412 $this->assertEquals(2, count_private($this->linkDb)); 431 $this->assertEquals(2, count_private($this->linkDb));
@@ -426,7 +445,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
426 $this->assertEquals( 445 $this->assertEquals(
427 'File netscape_basic.htm (482 bytes) was successfully processed:' 446 'File netscape_basic.htm (482 bytes) was successfully processed:'
428 .' 2 links imported, 2 links overwritten, 0 links skipped.', 447 .' 2 links imported, 2 links overwritten, 0 links skipped.',
429 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 448 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
430 ); 449 );
431 $this->assertEquals(2, count($this->linkDb)); 450 $this->assertEquals(2, count($this->linkDb));
432 $this->assertEquals(0, count_private($this->linkDb)); 451 $this->assertEquals(0, count_private($this->linkDb));
@@ -452,7 +471,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
452 $this->assertEquals( 471 $this->assertEquals(
453 'File netscape_basic.htm (482 bytes) was successfully processed:' 472 'File netscape_basic.htm (482 bytes) was successfully processed:'
454 .' 2 links imported, 0 links overwritten, 0 links skipped.', 473 .' 2 links imported, 0 links overwritten, 0 links skipped.',
455 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 474 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
456 ); 475 );
457 $this->assertEquals(2, count($this->linkDb)); 476 $this->assertEquals(2, count($this->linkDb));
458 $this->assertEquals(0, count_private($this->linkDb)); 477 $this->assertEquals(0, count_private($this->linkDb));
@@ -473,7 +492,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
473 $this->assertEquals( 492 $this->assertEquals(
474 'File netscape_basic.htm (482 bytes) was successfully processed:' 493 'File netscape_basic.htm (482 bytes) was successfully processed:'
475 .' 2 links imported, 2 links overwritten, 0 links skipped.', 494 .' 2 links imported, 2 links overwritten, 0 links skipped.',
476 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 495 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
477 ); 496 );
478 $this->assertEquals(2, count($this->linkDb)); 497 $this->assertEquals(2, count($this->linkDb));
479 $this->assertEquals(2, count_private($this->linkDb)); 498 $this->assertEquals(2, count_private($this->linkDb));
@@ -497,7 +516,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
497 $this->assertEquals( 516 $this->assertEquals(
498 'File netscape_basic.htm (482 bytes) was successfully processed:' 517 'File netscape_basic.htm (482 bytes) was successfully processed:'
499 .' 2 links imported, 0 links overwritten, 0 links skipped.', 518 .' 2 links imported, 0 links overwritten, 0 links skipped.',
500 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 519 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
501 ); 520 );
502 $this->assertEquals(2, count($this->linkDb)); 521 $this->assertEquals(2, count($this->linkDb));
503 $this->assertEquals(0, count_private($this->linkDb)); 522 $this->assertEquals(0, count_private($this->linkDb));
@@ -507,7 +526,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
507 $this->assertEquals( 526 $this->assertEquals(
508 'File netscape_basic.htm (482 bytes) was successfully processed:' 527 'File netscape_basic.htm (482 bytes) was successfully processed:'
509 .' 0 links imported, 0 links overwritten, 2 links skipped.', 528 .' 0 links imported, 0 links overwritten, 2 links skipped.',
510 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 529 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
511 ); 530 );
512 $this->assertEquals(2, count($this->linkDb)); 531 $this->assertEquals(2, count($this->linkDb));
513 $this->assertEquals(0, count_private($this->linkDb)); 532 $this->assertEquals(0, count_private($this->linkDb));
@@ -526,7 +545,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
526 $this->assertEquals( 545 $this->assertEquals(
527 'File netscape_basic.htm (482 bytes) was successfully processed:' 546 'File netscape_basic.htm (482 bytes) was successfully processed:'
528 .' 2 links imported, 0 links overwritten, 0 links skipped.', 547 .' 2 links imported, 0 links overwritten, 0 links skipped.',
529 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 548 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
530 ); 549 );
531 $this->assertEquals(2, count($this->linkDb)); 550 $this->assertEquals(2, count($this->linkDb));
532 $this->assertEquals(0, count_private($this->linkDb)); 551 $this->assertEquals(0, count_private($this->linkDb));
@@ -553,7 +572,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
553 $this->assertEquals( 572 $this->assertEquals(
554 'File netscape_basic.htm (482 bytes) was successfully processed:' 573 'File netscape_basic.htm (482 bytes) was successfully processed:'
555 .' 2 links imported, 0 links overwritten, 0 links skipped.', 574 .' 2 links imported, 0 links overwritten, 0 links skipped.',
556 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf) 575 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
557 ); 576 );
558 $this->assertEquals(2, count($this->linkDb)); 577 $this->assertEquals(2, count($this->linkDb));
559 $this->assertEquals(0, count_private($this->linkDb)); 578 $this->assertEquals(0, count_private($this->linkDb));
@@ -578,7 +597,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
578 $this->assertEquals( 597 $this->assertEquals(
579 'File same_date.htm (453 bytes) was successfully processed:' 598 'File same_date.htm (453 bytes) was successfully processed:'
580 .' 3 links imported, 0 links overwritten, 0 links skipped.', 599 .' 3 links imported, 0 links overwritten, 0 links skipped.',
581 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf) 600 NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
582 ); 601 );
583 $this->assertEquals(3, count($this->linkDb)); 602 $this->assertEquals(3, count($this->linkDb));
584 $this->assertEquals(0, count_private($this->linkDb)); 603 $this->assertEquals(0, count_private($this->linkDb));
@@ -595,4 +614,32 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
595 $this->linkDb[2]['id'] 614 $this->linkDb[2]['id']
596 ); 615 );
597 } 616 }
617
618 public function testImportCreateUpdateHistory()
619 {
620 $post = [
621 'privacy' => 'public',
622 'overwrite' => 'true',
623 ];
624 $files = file2array('netscape_basic.htm');
625 $nbLinks = 2;
626 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
627 $history = $this->history->getHistory();
628 $this->assertEquals($nbLinks, count($history));
629 foreach ($history as $value) {
630 $this->assertEquals(History::CREATED, $value['event']);
631 $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $value['datetime']));
632 $this->assertTrue(is_int($value['id']));
633 }
634
635 // re-import as private, enable overwriting
636 NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
637 $history = $this->history->getHistory();
638 $this->assertEquals($nbLinks * 2, count($history));
639 for ($i = 0 ; $i < $nbLinks ; $i++) {
640 $this->assertEquals(History::UPDATED, $history[$i]['event']);
641 $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $history[$i]['datetime']));
642 $this->assertTrue(is_int($history[$i]['id']));
643 }
644 }
598} 645}