diff options
-rw-r--r-- | application/FileUtils.php | 75 | ||||
-rw-r--r-- | application/History.php | 200 | ||||
-rw-r--r-- | application/LinkDB.php | 31 | ||||
-rw-r--r-- | application/NetscapeBookmarkUtils.php | 5 | ||||
-rw-r--r-- | application/config/ConfigManager.php | 1 | ||||
-rw-r--r-- | application/exceptions/IOException.php | 22 | ||||
-rw-r--r-- | index.php | 23 | ||||
-rw-r--r-- | tests/FileUtilsTest.php | 108 | ||||
-rw-r--r-- | tests/HistoryTest.php | 207 | ||||
-rw-r--r-- | tests/LinkDBTest.php | 2 | ||||
-rw-r--r-- | tests/NetscapeBookmarkUtils/BookmarkImportTest.php | 81 |
11 files changed, 696 insertions, 59 deletions
diff --git a/application/FileUtils.php b/application/FileUtils.php index 6cac9825..b8ad8970 100644 --- a/application/FileUtils.php +++ b/application/FileUtils.php | |||
@@ -1,21 +1,76 @@ | |||
1 | <?php | 1 | <?php |
2 | |||
3 | require_once 'exceptions/IOException.php'; | ||
4 | |||
2 | /** | 5 | /** |
3 | * Exception class thrown when a filesystem access failure happens | 6 | * Class FileUtils |
7 | * | ||
8 | * Utility class for file manipulation. | ||
4 | */ | 9 | */ |
5 | class IOException extends Exception | 10 | class FileUtils |
6 | { | 11 | { |
7 | private $path; | 12 | /** |
13 | * @var string | ||
14 | */ | ||
15 | protected static $phpPrefix = '<?php /* '; | ||
16 | |||
17 | /** | ||
18 | * @var string | ||
19 | */ | ||
20 | protected static $phpSuffix = ' */ ?>'; | ||
8 | 21 | ||
9 | /** | 22 | /** |
10 | * Construct a new IOException | 23 | * Write data into a file (Shaarli database format). |
24 | * The data is stored in a PHP file, as a comment, in compressed base64 format. | ||
25 | * | ||
26 | * The file will be created if it doesn't exist. | ||
27 | * | ||
28 | * @param string $file File path. | ||
29 | * @param string $content Content to write. | ||
30 | * | ||
31 | * @return int|bool Number of bytes written or false if it fails. | ||
11 | * | 32 | * |
12 | * @param string $path path to the resource that cannot be accessed | 33 | * @throws IOException The destination file can't be written. |
13 | * @param string $message Custom exception message. | ||
14 | */ | 34 | */ |
15 | public function __construct($path, $message = '') | 35 | public static function writeFlatDB($file, $content) |
16 | { | 36 | { |
17 | $this->path = $path; | 37 | if (is_file($file) && !is_writeable($file)) { |
18 | $this->message = empty($message) ? 'Error accessing' : $message; | 38 | // The datastore exists but is not writeable |
19 | $this->message .= PHP_EOL . $this->path; | 39 | throw new IOException($file); |
40 | } else if (!is_file($file) && !is_writeable(dirname($file))) { | ||
41 | // The datastore does not exist and its parent directory is not writeable | ||
42 | throw new IOException(dirname($file)); | ||
43 | } | ||
44 | |||
45 | return file_put_contents( | ||
46 | $file, | ||
47 | self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix | ||
48 | ); | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Read data from a file containing Shaarli database format content. | ||
53 | * If the file isn't readable or doesn't exists, default data will be returned. | ||
54 | * | ||
55 | * @param string $file File path. | ||
56 | * @param mixed $default The default value to return if the file isn't readable. | ||
57 | * | ||
58 | * @return mixed The content unserialized, or default if the file isn't readable, or false if it fails. | ||
59 | */ | ||
60 | public static function readFlatDB($file, $default = null) | ||
61 | { | ||
62 | // Note that gzinflate is faster than gzuncompress. | ||
63 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
64 | if (is_readable($file)) { | ||
65 | return unserialize( | ||
66 | gzinflate( | ||
67 | base64_decode( | ||
68 | substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix)) | ||
69 | ) | ||
70 | ) | ||
71 | ); | ||
72 | } | ||
73 | |||
74 | return $default; | ||
20 | } | 75 | } |
21 | } | 76 | } |
diff --git a/application/History.php b/application/History.php new file mode 100644 index 00000000..f93b0356 --- /dev/null +++ b/application/History.php | |||
@@ -0,0 +1,200 @@ | |||
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 | */ | ||
22 | class 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 | } | ||
74 | |||
75 | /** | ||
76 | * Initialize: read history file. | ||
77 | * | ||
78 | * Allow lazy loading (don't read the file if it isn't necessary). | ||
79 | */ | ||
80 | protected function initialize() | ||
81 | { | ||
82 | $this->check(); | ||
83 | $this->read(); | ||
84 | } | ||
85 | |||
86 | /** | ||
87 | * Add Event: new link. | ||
88 | * | ||
89 | * @param array $link Link data. | ||
90 | */ | ||
91 | public function addLink($link) | ||
92 | { | ||
93 | $this->addEvent(self::CREATED, $link['id']); | ||
94 | } | ||
95 | |||
96 | /** | ||
97 | * Add Event: update existing link. | ||
98 | * | ||
99 | * @param array $link Link data. | ||
100 | */ | ||
101 | public function updateLink($link) | ||
102 | { | ||
103 | $this->addEvent(self::UPDATED, $link['id']); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Add Event: delete existing link. | ||
108 | * | ||
109 | * @param array $link Link data. | ||
110 | */ | ||
111 | public function deleteLink($link) | ||
112 | { | ||
113 | $this->addEvent(self::DELETED, $link['id']); | ||
114 | } | ||
115 | |||
116 | /** | ||
117 | * Add Event: settings updated. | ||
118 | */ | ||
119 | public function updateSettings() | ||
120 | { | ||
121 | $this->addEvent(self::SETTINGS); | ||
122 | } | ||
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 | |||
136 | $item = [ | ||
137 | 'event' => $status, | ||
138 | 'datetime' => (new DateTime())->format(DateTime::ATOM), | ||
139 | 'id' => $id !== null ? $id : '', | ||
140 | ]; | ||
141 | $this->history = array_merge([$item], $this->history); | ||
142 | $this->write(); | ||
143 | } | ||
144 | |||
145 | /** | ||
146 | * Check that the history file is writable. | ||
147 | * Create the file if it doesn't exist. | ||
148 | * | ||
149 | * @throws Exception if it isn't writable. | ||
150 | */ | ||
151 | protected function check() | ||
152 | { | ||
153 | if (! is_file($this->historyFilePath)) { | ||
154 | FileUtils::writeFlatDB($this->historyFilePath, []); | ||
155 | } | ||
156 | |||
157 | if (! is_writable($this->historyFilePath)) { | ||
158 | throw new Exception('History file isn\'t readable or writable'); | ||
159 | } | ||
160 | } | ||
161 | |||
162 | /** | ||
163 | * Read JSON history file. | ||
164 | */ | ||
165 | protected function read() | ||
166 | { | ||
167 | $this->history = FileUtils::readFlatDB($this->historyFilePath, []); | ||
168 | if ($this->history === false) { | ||
169 | throw new Exception('Could not parse history file'); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | /** | ||
174 | * Write JSON history file and delete old entries. | ||
175 | */ | ||
176 | protected function write() | ||
177 | { | ||
178 | $comparaison = new DateTime('-'. $this->retentionTime . ' seconds'); | ||
179 | foreach ($this->history as $key => $value) { | ||
180 | if (DateTime::createFromFormat(DateTime::ATOM, $value['datetime']) < $comparaison) { | ||
181 | unset($this->history[$key]); | ||
182 | } | ||
183 | } | ||
184 | FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history)); | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * Get the History. | ||
189 | * | ||
190 | * @return array | ||
191 | */ | ||
192 | public function getHistory() | ||
193 | { | ||
194 | if ($this->history === null) { | ||
195 | $this->initialize(); | ||
196 | } | ||
197 | |||
198 | return $this->history; | ||
199 | } | ||
200 | } | ||
diff --git a/application/LinkDB.php b/application/LinkDB.php index 1e4d7ce8..0d3c85bd 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php | |||
@@ -50,12 +50,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess | |||
50 | // Link date storage format | 50 | // Link date storage format |
51 | const LINK_DATE_FORMAT = 'Ymd_His'; | 51 | const LINK_DATE_FORMAT = 'Ymd_His'; |
52 | 52 | ||
53 | // Datastore PHP prefix | ||
54 | protected static $phpPrefix = '<?php /* '; | ||
55 | |||
56 | // Datastore PHP suffix | ||
57 | protected static $phpSuffix = ' */ ?>'; | ||
58 | |||
59 | // List of links (associative array) | 53 | // List of links (associative array) |
60 | // - key: link date (e.g. "20110823_124546"), | 54 | // - key: link date (e.g. "20110823_124546"), |
61 | // - value: associative array (keys: title, description...) | 55 | // - value: associative array (keys: title, description...) |
@@ -295,16 +289,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
295 | return; | 289 | return; |
296 | } | 290 | } |
297 | 291 | ||
298 | // Read data | 292 | $this->links = FileUtils::readFlatDB($this->datastore, []); |
299 | // Note that gzinflate is faster than gzuncompress. | ||
300 | // See: http://www.php.net/manual/en/function.gzdeflate.php#96439 | ||
301 | $this->links = array(); | ||
302 | |||
303 | if (file_exists($this->datastore)) { | ||
304 | $this->links = unserialize(gzinflate(base64_decode( | ||
305 | substr(file_get_contents($this->datastore), | ||
306 | strlen(self::$phpPrefix), -strlen(self::$phpSuffix))))); | ||
307 | } | ||
308 | 293 | ||
309 | $toremove = array(); | 294 | $toremove = array(); |
310 | foreach ($this->links as $key => &$link) { | 295 | foreach ($this->links as $key => &$link) { |
@@ -361,19 +346,7 @@ You use the community supported version of the original Shaarli project, by Seba | |||
361 | */ | 346 | */ |
362 | private function write() | 347 | private function write() |
363 | { | 348 | { |
364 | if (is_file($this->datastore) && !is_writeable($this->datastore)) { | 349 | FileUtils::writeFlatDB($this->datastore, $this->links); |
365 | // The datastore exists but is not writeable | ||
366 | throw new IOException($this->datastore); | ||
367 | } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) { | ||
368 | // The datastore does not exist and its parent directory is not writeable | ||
369 | throw new IOException(dirname($this->datastore)); | ||
370 | } | ||
371 | |||
372 | file_put_contents( | ||
373 | $this->datastore, | ||
374 | self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix | ||
375 | ); | ||
376 | |||
377 | } | 350 | } |
378 | 351 | ||
379 | /** | 352 | /** |
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/application/exceptions/IOException.php b/application/exceptions/IOException.php new file mode 100644 index 00000000..b563b23d --- /dev/null +++ b/application/exceptions/IOException.php | |||
@@ -0,0 +1,22 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Exception class thrown when a filesystem access failure happens | ||
5 | */ | ||
6 | class IOException extends Exception | ||
7 | { | ||
8 | private $path; | ||
9 | |||
10 | /** | ||
11 | * Construct a new IOException | ||
12 | * | ||
13 | * @param string $path path to the resource that cannot be accessed | ||
14 | * @param string $message Custom exception message. | ||
15 | */ | ||
16 | public function __construct($path, $message = '') | ||
17 | { | ||
18 | $this->path = $path; | ||
19 | $this->message = empty($message) ? 'Error accessing' : $message; | ||
20 | $this->message .= ' "' . $this->path .'"'; | ||
21 | } | ||
22 | } | ||
@@ -62,6 +62,7 @@ require_once 'application/CachedPage.php'; | |||
62 | require_once 'application/config/ConfigPlugin.php'; | 62 | require_once 'application/config/ConfigPlugin.php'; |
63 | require_once 'application/FeedBuilder.php'; | 63 | require_once 'application/FeedBuilder.php'; |
64 | require_once 'application/FileUtils.php'; | 64 | require_once 'application/FileUtils.php'; |
65 | require_once 'application/History.php'; | ||
65 | require_once 'application/HttpUtils.php'; | 66 | require_once 'application/HttpUtils.php'; |
66 | require_once 'application/Languages.php'; | 67 | require_once 'application/Languages.php'; |
67 | require_once 'application/LinkDB.php'; | 68 | require_once 'application/LinkDB.php'; |
@@ -727,6 +728,12 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
727 | die($e->getMessage()); | 728 | die($e->getMessage()); |
728 | } | 729 | } |
729 | 730 | ||
731 | try { | ||
732 | $history = new History($conf->get('resource.history')); | ||
733 | } catch(Exception $e) { | ||
734 | die($e->getMessage()); | ||
735 | } | ||
736 | |||
730 | $PAGE = new PageBuilder($conf); | 737 | $PAGE = new PageBuilder($conf); |
731 | $PAGE->assign('linkcount', count($LINKSDB)); | 738 | $PAGE->assign('linkcount', count($LINKSDB)); |
732 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); | 739 | $PAGE->assign('privateLinkcount', count_private($LINKSDB)); |
@@ -1125,6 +1132,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1125 | $conf->set('api.secret', escape($_POST['apiSecret'])); | 1132 | $conf->set('api.secret', escape($_POST['apiSecret'])); |
1126 | try { | 1133 | try { |
1127 | $conf->write(isLoggedIn()); | 1134 | $conf->write(isLoggedIn()); |
1135 | $history->updateSettings(); | ||
1128 | invalidateCaches($conf->get('resource.page_cache')); | 1136 | invalidateCaches($conf->get('resource.page_cache')); |
1129 | } | 1137 | } |
1130 | catch(Exception $e) { | 1138 | catch(Exception $e) { |
@@ -1159,6 +1167,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1159 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); | 1167 | $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); |
1160 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); | 1168 | $PAGE->assign('api_enabled', $conf->get('api.enabled', true)); |
1161 | $PAGE->assign('api_secret', $conf->get('api.secret')); | 1169 | $PAGE->assign('api_secret', $conf->get('api.secret')); |
1170 | $history->updateSettings(); | ||
1162 | $PAGE->renderPage('configure'); | 1171 | $PAGE->renderPage('configure'); |
1163 | exit; | 1172 | exit; |
1164 | } | 1173 | } |
@@ -1188,6 +1197,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1188 | unset($tags[array_search($needle,$tags)]); // Remove tag. | 1197 | unset($tags[array_search($needle,$tags)]); // Remove tag. |
1189 | $value['tags']=trim(implode(' ',$tags)); | 1198 | $value['tags']=trim(implode(' ',$tags)); |
1190 | $LINKSDB[$key]=$value; | 1199 | $LINKSDB[$key]=$value; |
1200 | $history->updateLink($LINKSDB[$key]); | ||
1191 | } | 1201 | } |
1192 | $LINKSDB->save($conf->get('resource.page_cache')); | 1202 | $LINKSDB->save($conf->get('resource.page_cache')); |
1193 | echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; | 1203 | echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; |
@@ -1205,6 +1215,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1205 | $tags[array_search($needle, $tags)] = trim($_POST['totag']); | 1215 | $tags[array_search($needle, $tags)] = trim($_POST['totag']); |
1206 | $value['tags'] = implode(' ', array_unique($tags)); | 1216 | $value['tags'] = implode(' ', array_unique($tags)); |
1207 | $LINKSDB[$key] = $value; | 1217 | $LINKSDB[$key] = $value; |
1218 | $history->updateLink($LINKSDB[$key]); | ||
1208 | } | 1219 | } |
1209 | $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. | 1220 | $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. |
1210 | echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; | 1221 | echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; |
@@ -1239,11 +1250,13 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1239 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); | 1250 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); |
1240 | $updated = new DateTime(); | 1251 | $updated = new DateTime(); |
1241 | $shortUrl = $LINKSDB[$id]['shorturl']; | 1252 | $shortUrl = $LINKSDB[$id]['shorturl']; |
1253 | $new = false; | ||
1242 | } else { | 1254 | } else { |
1243 | // New link | 1255 | // New link |
1244 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); | 1256 | $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); |
1245 | $updated = null; | 1257 | $updated = null; |
1246 | $shortUrl = link_small_hash($created, $id); | 1258 | $shortUrl = link_small_hash($created, $id); |
1259 | $new = true; | ||
1247 | } | 1260 | } |
1248 | 1261 | ||
1249 | // Remove multiple spaces. | 1262 | // Remove multiple spaces. |
@@ -1282,6 +1295,11 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1282 | 1295 | ||
1283 | $LINKSDB[$id] = $link; | 1296 | $LINKSDB[$id] = $link; |
1284 | $LINKSDB->save($conf->get('resource.page_cache')); | 1297 | $LINKSDB->save($conf->get('resource.page_cache')); |
1298 | if ($new) { | ||
1299 | $history->addLink($link); | ||
1300 | } else { | ||
1301 | $history->updateLink($link); | ||
1302 | } | ||
1285 | 1303 | ||
1286 | // If we are called from the bookmarklet, we must close the popup: | 1304 | // If we are called from the bookmarklet, we must close the popup: |
1287 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { | 1305 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { |
@@ -1332,6 +1350,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1332 | $pluginManager->executeHooks('delete_link', $link); | 1350 | $pluginManager->executeHooks('delete_link', $link); |
1333 | unset($LINKSDB[$id]); | 1351 | unset($LINKSDB[$id]); |
1334 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk | 1352 | $LINKSDB->save($conf->get('resource.page_cache')); // save to disk |
1353 | $history->deleteLink($link); | ||
1335 | 1354 | ||
1336 | // If we are called from the bookmarklet, we must close the popup: | 1355 | // If we are called from the bookmarklet, we must close the popup: |
1337 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } | 1356 | if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } |
@@ -1529,7 +1548,8 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1529 | $_POST, | 1548 | $_POST, |
1530 | $_FILES, | 1549 | $_FILES, |
1531 | $LINKSDB, | 1550 | $LINKSDB, |
1532 | $conf | 1551 | $conf, |
1552 | $history | ||
1533 | ); | 1553 | ); |
1534 | echo '<script>alert("'.$status.'");document.location=\'?do=' | 1554 | echo '<script>alert("'.$status.'");document.location=\'?do=' |
1535 | .Router::$PAGE_IMPORT .'\';</script>'; | 1555 | .Router::$PAGE_IMPORT .'\';</script>'; |
@@ -1558,6 +1578,7 @@ function renderPage($conf, $pluginManager, $LINKSDB) | |||
1558 | 1578 | ||
1559 | // Plugin administration form action | 1579 | // Plugin administration form action |
1560 | if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { | 1580 | if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { |
1581 | $history->updateSettings(); | ||
1561 | try { | 1582 | try { |
1562 | if (isset($_POST['parameters_form'])) { | 1583 | if (isset($_POST['parameters_form'])) { |
1563 | unset($_POST['parameters_form']); | 1584 | unset($_POST['parameters_form']); |
diff --git a/tests/FileUtilsTest.php b/tests/FileUtilsTest.php new file mode 100644 index 00000000..d764e495 --- /dev/null +++ b/tests/FileUtilsTest.php | |||
@@ -0,0 +1,108 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/FileUtils.php'; | ||
4 | |||
5 | /** | ||
6 | * Class FileUtilsTest | ||
7 | * | ||
8 | * Test file utility class. | ||
9 | */ | ||
10 | class FileUtilsTest extends PHPUnit_Framework_TestCase | ||
11 | { | ||
12 | /** | ||
13 | * @var string Test file path. | ||
14 | */ | ||
15 | protected static $file = 'sandbox/flat.db'; | ||
16 | |||
17 | /** | ||
18 | * Delete test file after every test. | ||
19 | */ | ||
20 | public function tearDown() | ||
21 | { | ||
22 | @unlink(self::$file); | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * Test writeDB, then readDB with different data. | ||
27 | */ | ||
28 | public function testSimpleWriteRead() | ||
29 | { | ||
30 | $data = ['blue', 'red']; | ||
31 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
32 | $this->assertTrue(startsWith(file_get_contents(self::$file), '<?php /*')); | ||
33 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
34 | |||
35 | $data = 0; | ||
36 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
37 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
38 | |||
39 | $data = null; | ||
40 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
41 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
42 | |||
43 | $data = false; | ||
44 | $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0); | ||
45 | $this->assertEquals($data, FileUtils::readFlatDB(self::$file)); | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * File not writable: raise an exception. | ||
50 | * | ||
51 | * @expectedException IOException | ||
52 | * @expectedExceptionMessage Error accessing "sandbox/flat.db" | ||
53 | */ | ||
54 | public function testWriteWithoutPermission() | ||
55 | { | ||
56 | touch(self::$file); | ||
57 | chmod(self::$file, 0440); | ||
58 | FileUtils::writeFlatDB(self::$file, null); | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * Folder non existent: raise an exception. | ||
63 | * | ||
64 | * @expectedException IOException | ||
65 | * @expectedExceptionMessage Error accessing "nopefolder" | ||
66 | */ | ||
67 | public function testWriteFolderDoesNotExist() | ||
68 | { | ||
69 | FileUtils::writeFlatDB('nopefolder/file', null); | ||
70 | } | ||
71 | |||
72 | /** | ||
73 | * Folder non writable: raise an exception. | ||
74 | * | ||
75 | * @expectedException IOException | ||
76 | * @expectedExceptionMessage Error accessing "sandbox" | ||
77 | */ | ||
78 | public function testWriteFolderPermission() | ||
79 | { | ||
80 | chmod(dirname(self::$file), 0555); | ||
81 | try { | ||
82 | FileUtils::writeFlatDB(self::$file, null); | ||
83 | } catch (Exception $e) { | ||
84 | chmod(dirname(self::$file), 0755); | ||
85 | throw $e; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Read non existent file, use default parameter. | ||
91 | */ | ||
92 | public function testReadNotExistentFile() | ||
93 | { | ||
94 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); | ||
95 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); | ||
96 | } | ||
97 | |||
98 | /** | ||
99 | * Read non readable file, use default parameter. | ||
100 | */ | ||
101 | public function testReadNotReadable() | ||
102 | { | ||
103 | touch(self::$file); | ||
104 | chmod(self::$file, 0220); | ||
105 | $this->assertEquals(null, FileUtils::readFlatDB(self::$file)); | ||
106 | $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test'])); | ||
107 | } | ||
108 | } | ||
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php new file mode 100644 index 00000000..91525845 --- /dev/null +++ b/tests/HistoryTest.php | |||
@@ -0,0 +1,207 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/History.php'; | ||
4 | |||
5 | |||
6 | class 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 testConstructLazyLoading() | ||
25 | { | ||
26 | new History(self::$historyFilePath); | ||
27 | $this->assertFileNotExists(self::$historyFilePath); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Test that the history file is created if it doesn't exist. | ||
32 | */ | ||
33 | public function testAddEventCreateFile() | ||
34 | { | ||
35 | $history = new History(self::$historyFilePath); | ||
36 | $history->updateSettings(); | ||
37 | $this->assertFileExists(self::$historyFilePath); | ||
38 | } | ||
39 | |||
40 | /** | ||
41 | * Not writable history file: raise an exception. | ||
42 | * | ||
43 | * @expectedException Exception | ||
44 | * @expectedExceptionMessage History file isn't readable or writable | ||
45 | */ | ||
46 | public function testConstructNotWritable() | ||
47 | { | ||
48 | touch(self::$historyFilePath); | ||
49 | chmod(self::$historyFilePath, 0440); | ||
50 | $history = new History(self::$historyFilePath); | ||
51 | $history->updateSettings(); | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Not parsable history file: raise an exception. | ||
56 | * | ||
57 | * @expectedException Exception | ||
58 | * @expectedExceptionMessageRegExp /Could not parse history file/ | ||
59 | */ | ||
60 | public function testConstructNotParsable() | ||
61 | { | ||
62 | file_put_contents(self::$historyFilePath, 'not parsable'); | ||
63 | $history = new History(self::$historyFilePath); | ||
64 | // gzinflate generates a warning | ||
65 | @$history->updateSettings(); | ||
66 | } | ||
67 | |||
68 | /** | ||
69 | * Test add link event | ||
70 | */ | ||
71 | public function testAddLink() | ||
72 | { | ||
73 | $history = new History(self::$historyFilePath); | ||
74 | $history->addLink(['id' => 0]); | ||
75 | $actual = $history->getHistory()[0]; | ||
76 | $this->assertEquals(History::CREATED, $actual['event']); | ||
77 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
78 | $this->assertEquals(0, $actual['id']); | ||
79 | |||
80 | $history = new History(self::$historyFilePath); | ||
81 | $history->addLink(['id' => 1]); | ||
82 | $actual = $history->getHistory()[0]; | ||
83 | $this->assertEquals(History::CREATED, $actual['event']); | ||
84 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
85 | $this->assertEquals(1, $actual['id']); | ||
86 | |||
87 | $history = new History(self::$historyFilePath); | ||
88 | $history->addLink(['id' => 'str']); | ||
89 | $actual = $history->getHistory()[0]; | ||
90 | $this->assertEquals(History::CREATED, $actual['event']); | ||
91 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
92 | $this->assertEquals('str', $actual['id']); | ||
93 | } | ||
94 | |||
95 | /** | ||
96 | * Test updated link event | ||
97 | */ | ||
98 | public function testUpdateLink() | ||
99 | { | ||
100 | $history = new History(self::$historyFilePath); | ||
101 | $history->updateLink(['id' => 1]); | ||
102 | $actual = $history->getHistory()[0]; | ||
103 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
104 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
105 | $this->assertEquals(1, $actual['id']); | ||
106 | } | ||
107 | |||
108 | /** | ||
109 | * Test delete link event | ||
110 | */ | ||
111 | public function testDeleteLink() | ||
112 | { | ||
113 | $history = new History(self::$historyFilePath); | ||
114 | $history->deleteLink(['id' => 1]); | ||
115 | $actual = $history->getHistory()[0]; | ||
116 | $this->assertEquals(History::DELETED, $actual['event']); | ||
117 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
118 | $this->assertEquals(1, $actual['id']); | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * Test updated settings event | ||
123 | */ | ||
124 | public function testUpdateSettings() | ||
125 | { | ||
126 | $history = new History(self::$historyFilePath); | ||
127 | $history->updateSettings(); | ||
128 | $actual = $history->getHistory()[0]; | ||
129 | $this->assertEquals(History::SETTINGS, $actual['event']); | ||
130 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
131 | $this->assertEmpty($actual['id']); | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Make sure that new items are stored at the beginning | ||
136 | */ | ||
137 | public function testHistoryOrder() | ||
138 | { | ||
139 | $history = new History(self::$historyFilePath); | ||
140 | $history->updateLink(['id' => 1]); | ||
141 | $actual = $history->getHistory()[0]; | ||
142 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
143 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
144 | $this->assertEquals(1, $actual['id']); | ||
145 | |||
146 | $history->addLink(['id' => 1]); | ||
147 | $actual = $history->getHistory()[0]; | ||
148 | $this->assertEquals(History::CREATED, $actual['event']); | ||
149 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
150 | $this->assertEquals(1, $actual['id']); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * Re-read history from file after writing an event | ||
155 | */ | ||
156 | public function testHistoryRead() | ||
157 | { | ||
158 | $history = new History(self::$historyFilePath); | ||
159 | $history->updateLink(['id' => 1]); | ||
160 | $history = new History(self::$historyFilePath); | ||
161 | $actual = $history->getHistory()[0]; | ||
162 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
163 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
164 | $this->assertEquals(1, $actual['id']); | ||
165 | } | ||
166 | |||
167 | /** | ||
168 | * Re-read history from file after writing an event and make sure that the order is correct | ||
169 | */ | ||
170 | public function testHistoryOrderRead() | ||
171 | { | ||
172 | $history = new History(self::$historyFilePath); | ||
173 | $history->updateLink(['id' => 1]); | ||
174 | $history->addLink(['id' => 1]); | ||
175 | |||
176 | $history = new History(self::$historyFilePath); | ||
177 | $actual = $history->getHistory()[0]; | ||
178 | $this->assertEquals(History::CREATED, $actual['event']); | ||
179 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
180 | $this->assertEquals(1, $actual['id']); | ||
181 | |||
182 | $actual = $history->getHistory()[1]; | ||
183 | $this->assertEquals(History::UPDATED, $actual['event']); | ||
184 | $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime'])); | ||
185 | $this->assertEquals(1, $actual['id']); | ||
186 | } | ||
187 | |||
188 | /** | ||
189 | * Test retention time: delete old entries. | ||
190 | */ | ||
191 | public function testHistoryRententionTime() | ||
192 | { | ||
193 | $history = new History(self::$historyFilePath, 5); | ||
194 | $history->updateLink(['id' => 1]); | ||
195 | $this->assertEquals(1, count($history->getHistory())); | ||
196 | $arr = $history->getHistory(); | ||
197 | $arr[0]['datetime'] = (new DateTime('-1 hour'))->format(DateTime::ATOM); | ||
198 | FileUtils::writeFlatDB(self::$historyFilePath, $arr); | ||
199 | |||
200 | $history = new History(self::$historyFilePath, 60); | ||
201 | $this->assertEquals(1, count($history->getHistory())); | ||
202 | $this->assertEquals(1, $history->getHistory()[0]['id']); | ||
203 | $history->updateLink(['id' => 2]); | ||
204 | $this->assertEquals(1, count($history->getHistory())); | ||
205 | $this->assertEquals(2, $history->getHistory()[0]['id']); | ||
206 | } | ||
207 | } | ||
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php index 1f62a34a..7bf98f92 100644 --- a/tests/LinkDBTest.php +++ b/tests/LinkDBTest.php | |||
@@ -101,7 +101,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase | |||
101 | * Attempt to instantiate a LinkDB whereas the datastore is not writable | 101 | * Attempt to instantiate a LinkDB whereas the datastore is not writable |
102 | * | 102 | * |
103 | * @expectedException IOException | 103 | * @expectedException IOException |
104 | * @expectedExceptionMessageRegExp /Error accessing\nnull/ | 104 | * @expectedExceptionMessageRegExp /Error accessing "null"/ |
105 | */ | 105 | */ |
106 | public function testConstructDatastoreNotWriteable() | 106 | public function testConstructDatastoreNotWriteable() |
107 | { | 107 | { |
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 | } |