diff --git a/application/FileUtils.php b/application/FileUtils.php
index 6cac982..b8ad897 100644
--- a/application/FileUtils.php
+++ b/application/FileUtils.php
@@ -1,21 +1,76 @@
';
+
+ /**
+ * Write data into a file (Shaarli database format).
+ * The data is stored in a PHP file, as a comment, in compressed base64 format.
+ *
+ * The file will be created if it doesn't exist.
+ *
+ * @param string $file File path.
+ * @param string $content Content to write.
+ *
+ * @return int|bool Number of bytes written or false if it fails.
+ *
+ * @throws IOException The destination file can't be written.
+ */
+ public static function writeFlatDB($file, $content)
{
- $this->path = $path;
- $this->message = empty($message) ? 'Error accessing' : $message;
- $this->message .= PHP_EOL . $this->path;
+ if (is_file($file) && !is_writeable($file)) {
+ // The datastore exists but is not writeable
+ throw new IOException($file);
+ } else if (!is_file($file) && !is_writeable(dirname($file))) {
+ // The datastore does not exist and its parent directory is not writeable
+ throw new IOException(dirname($file));
+ }
+
+ return file_put_contents(
+ $file,
+ self::$phpPrefix.base64_encode(gzdeflate(serialize($content))).self::$phpSuffix
+ );
+ }
+
+ /**
+ * Read data from a file containing Shaarli database format content.
+ * If the file isn't readable or doesn't exists, default data will be returned.
+ *
+ * @param string $file File path.
+ * @param mixed $default The default value to return if the file isn't readable.
+ *
+ * @return mixed The content unserialized, or default if the file isn't readable, or false if it fails.
+ */
+ public static function readFlatDB($file, $default = null)
+ {
+ // Note that gzinflate is faster than gzuncompress.
+ // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
+ if (is_readable($file)) {
+ return unserialize(
+ gzinflate(
+ base64_decode(
+ substr(file_get_contents($file), strlen(self::$phpPrefix), -strlen(self::$phpSuffix))
+ )
+ )
+ );
+ }
+
+ return $default;
}
}
diff --git a/application/History.php b/application/History.php
new file mode 100644
index 0000000..f93b035
--- /dev/null
+++ b/application/History.php
@@ -0,0 +1,200 @@
+historyFilePath = $historyFilePath;
+ if ($retentionTime !== null) {
+ $this->retentionTime = $retentionTime;
+ }
+ }
+
+ /**
+ * Initialize: read history file.
+ *
+ * Allow lazy loading (don't read the file if it isn't necessary).
+ */
+ protected function initialize()
+ {
+ $this->check();
+ $this->read();
+ }
+
+ /**
+ * Add Event: new link.
+ *
+ * @param array $link Link data.
+ */
+ public function addLink($link)
+ {
+ $this->addEvent(self::CREATED, $link['id']);
+ }
+
+ /**
+ * Add Event: update existing link.
+ *
+ * @param array $link Link data.
+ */
+ public function updateLink($link)
+ {
+ $this->addEvent(self::UPDATED, $link['id']);
+ }
+
+ /**
+ * Add Event: delete existing link.
+ *
+ * @param array $link Link data.
+ */
+ public function deleteLink($link)
+ {
+ $this->addEvent(self::DELETED, $link['id']);
+ }
+
+ /**
+ * Add Event: settings updated.
+ */
+ public function updateSettings()
+ {
+ $this->addEvent(self::SETTINGS);
+ }
+
+ /**
+ * Save a new event and write it in the history file.
+ *
+ * @param string $status Event key, should be defined as constant.
+ * @param mixed $id Event item identifier (e.g. link ID).
+ */
+ protected function addEvent($status, $id = null)
+ {
+ if ($this->history === null) {
+ $this->initialize();
+ }
+
+ $item = [
+ 'event' => $status,
+ 'datetime' => (new DateTime())->format(DateTime::ATOM),
+ 'id' => $id !== null ? $id : '',
+ ];
+ $this->history = array_merge([$item], $this->history);
+ $this->write();
+ }
+
+ /**
+ * Check that the history file is writable.
+ * Create the file if it doesn't exist.
+ *
+ * @throws Exception if it isn't writable.
+ */
+ protected function check()
+ {
+ if (! is_file($this->historyFilePath)) {
+ FileUtils::writeFlatDB($this->historyFilePath, []);
+ }
+
+ if (! is_writable($this->historyFilePath)) {
+ throw new Exception('History file isn\'t readable or writable');
+ }
+ }
+
+ /**
+ * Read JSON history file.
+ */
+ protected function read()
+ {
+ $this->history = FileUtils::readFlatDB($this->historyFilePath, []);
+ if ($this->history === false) {
+ throw new Exception('Could not parse history file');
+ }
+ }
+
+ /**
+ * Write JSON history file and delete old entries.
+ */
+ protected function write()
+ {
+ $comparaison = new DateTime('-'. $this->retentionTime . ' seconds');
+ foreach ($this->history as $key => $value) {
+ if (DateTime::createFromFormat(DateTime::ATOM, $value['datetime']) < $comparaison) {
+ unset($this->history[$key]);
+ }
+ }
+ FileUtils::writeFlatDB($this->historyFilePath, array_values($this->history));
+ }
+
+ /**
+ * Get the History.
+ *
+ * @return array
+ */
+ public function getHistory()
+ {
+ if ($this->history === null) {
+ $this->initialize();
+ }
+
+ return $this->history;
+ }
+}
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 1e4d7ce..0d3c85b 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -50,12 +50,6 @@ class LinkDB implements Iterator, Countable, ArrayAccess
// Link date storage format
const LINK_DATE_FORMAT = 'Ymd_His';
- // Datastore PHP prefix
- protected static $phpPrefix = '';
-
// List of links (associative array)
// - key: link date (e.g. "20110823_124546"),
// - value: associative array (keys: title, description...)
@@ -295,16 +289,7 @@ You use the community supported version of the original Shaarli project, by Seba
return;
}
- // Read data
- // Note that gzinflate is faster than gzuncompress.
- // See: http://www.php.net/manual/en/function.gzdeflate.php#96439
- $this->links = array();
-
- if (file_exists($this->datastore)) {
- $this->links = unserialize(gzinflate(base64_decode(
- substr(file_get_contents($this->datastore),
- strlen(self::$phpPrefix), -strlen(self::$phpSuffix)))));
- }
+ $this->links = FileUtils::readFlatDB($this->datastore, []);
$toremove = array();
foreach ($this->links as $key => &$link) {
@@ -361,19 +346,7 @@ You use the community supported version of the original Shaarli project, by Seba
*/
private function write()
{
- if (is_file($this->datastore) && !is_writeable($this->datastore)) {
- // The datastore exists but is not writeable
- throw new IOException($this->datastore);
- } else if (!is_file($this->datastore) && !is_writeable(dirname($this->datastore))) {
- // The datastore does not exist and its parent directory is not writeable
- throw new IOException(dirname($this->datastore));
- }
-
- file_put_contents(
- $this->datastore,
- self::$phpPrefix.base64_encode(gzdeflate(serialize($this->links))).self::$phpSuffix
- );
-
+ FileUtils::writeFlatDB($this->datastore, $this->links);
}
/**
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
index ab346f8..bbfde13 100644
--- a/application/NetscapeBookmarkUtils.php
+++ b/application/NetscapeBookmarkUtils.php
@@ -95,10 +95,11 @@ class NetscapeBookmarkUtils
* @param array $files Server $_FILES parameters
* @param LinkDB $linkDb Loaded LinkDB instance
* @param ConfigManager $conf instance
+ * @param History $history History instance
*
* @return string Summary of the bookmark import status
*/
- public static function import($post, $files, $linkDb, $conf)
+ public static function import($post, $files, $linkDb, $conf, $history)
{
$filename = $files['filetoupload']['name'];
$filesize = $files['filetoupload']['size'];
@@ -182,6 +183,7 @@ class NetscapeBookmarkUtils
$linkDb[$existingLink['id']] = $newLink;
$importCount++;
$overwriteCount++;
+ $history->updateLink($newLink);
continue;
}
@@ -193,6 +195,7 @@ class NetscapeBookmarkUtils
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
$linkDb[$newLink['id']] = $newLink;
$importCount++;
+ $history->addLink($newLink);
}
$linkDb->save($conf->get('resource.page_cache'));
diff --git a/application/config/ConfigManager.php b/application/config/ConfigManager.php
index 7bfbfc7..86a917f 100644
--- a/application/config/ConfigManager.php
+++ b/application/config/ConfigManager.php
@@ -301,6 +301,7 @@ class ConfigManager
$this->setEmpty('resource.updates', 'data/updates.txt');
$this->setEmpty('resource.log', 'data/log.txt');
$this->setEmpty('resource.update_check', 'data/lastupdatecheck.txt');
+ $this->setEmpty('resource.history', 'data/history.php');
$this->setEmpty('resource.raintpl_tpl', 'tpl/');
$this->setEmpty('resource.theme', 'default');
$this->setEmpty('resource.raintpl_tmp', 'tmp/');
diff --git a/application/exceptions/IOException.php b/application/exceptions/IOException.php
new file mode 100644
index 0000000..b563b23
--- /dev/null
+++ b/application/exceptions/IOException.php
@@ -0,0 +1,22 @@
+path = $path;
+ $this->message = empty($message) ? 'Error accessing' : $message;
+ $this->message .= ' "' . $this->path .'"';
+ }
+}
diff --git a/index.php b/index.php
index e392e50..76aa1ae 100644
--- a/index.php
+++ b/index.php
@@ -62,6 +62,7 @@ require_once 'application/CachedPage.php';
require_once 'application/config/ConfigPlugin.php';
require_once 'application/FeedBuilder.php';
require_once 'application/FileUtils.php';
+require_once 'application/History.php';
require_once 'application/HttpUtils.php';
require_once 'application/Languages.php';
require_once 'application/LinkDB.php';
@@ -727,6 +728,12 @@ function renderPage($conf, $pluginManager, $LINKSDB)
die($e->getMessage());
}
+ try {
+ $history = new History($conf->get('resource.history'));
+ } catch(Exception $e) {
+ die($e->getMessage());
+ }
+
$PAGE = new PageBuilder($conf);
$PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB));
@@ -1125,6 +1132,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$conf->set('api.secret', escape($_POST['apiSecret']));
try {
$conf->write(isLoggedIn());
+ $history->updateSettings();
invalidateCaches($conf->get('resource.page_cache'));
}
catch(Exception $e) {
@@ -1159,6 +1167,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
$PAGE->assign('api_enabled', $conf->get('api.enabled', true));
$PAGE->assign('api_secret', $conf->get('api.secret'));
+ $history->updateSettings();
$PAGE->renderPage('configure');
exit;
}
@@ -1188,6 +1197,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
unset($tags[array_search($needle,$tags)]); // Remove tag.
$value['tags']=trim(implode(' ',$tags));
$LINKSDB[$key]=$value;
+ $history->updateLink($LINKSDB[$key]);
}
$LINKSDB->save($conf->get('resource.page_cache'));
echo '';
@@ -1205,6 +1215,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$tags[array_search($needle, $tags)] = trim($_POST['totag']);
$value['tags'] = implode(' ', array_unique($tags));
$LINKSDB[$key] = $value;
+ $history->updateLink($LINKSDB[$key]);
}
$LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
echo '';
@@ -1239,11 +1250,13 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = new DateTime();
$shortUrl = $LINKSDB[$id]['shorturl'];
+ $new = false;
} else {
// New link
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = null;
$shortUrl = link_small_hash($created, $id);
+ $new = true;
}
// Remove multiple spaces.
@@ -1282,6 +1295,11 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache'));
+ if ($new) {
+ $history->addLink($link);
+ } else {
+ $history->updateLink($link);
+ }
// If we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
@@ -1332,6 +1350,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$pluginManager->executeHooks('delete_link', $link);
unset($LINKSDB[$id]);
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk
+ $history->deleteLink($link);
// If we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo ''; exit; }
@@ -1529,7 +1548,8 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$_POST,
$_FILES,
$LINKSDB,
- $conf
+ $conf,
+ $history
);
echo '';
@@ -1558,6 +1578,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
// Plugin administration form action
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
+ $history->updateSettings();
try {
if (isset($_POST['parameters_form'])) {
unset($_POST['parameters_form']);
diff --git a/tests/FileUtilsTest.php b/tests/FileUtilsTest.php
new file mode 100644
index 0000000..d764e49
--- /dev/null
+++ b/tests/FileUtilsTest.php
@@ -0,0 +1,108 @@
+assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0);
+ $this->assertTrue(startsWith(file_get_contents(self::$file), 'assertEquals($data, FileUtils::readFlatDB(self::$file));
+
+ $data = 0;
+ $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0);
+ $this->assertEquals($data, FileUtils::readFlatDB(self::$file));
+
+ $data = null;
+ $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0);
+ $this->assertEquals($data, FileUtils::readFlatDB(self::$file));
+
+ $data = false;
+ $this->assertTrue(FileUtils::writeFlatDB(self::$file, $data) > 0);
+ $this->assertEquals($data, FileUtils::readFlatDB(self::$file));
+ }
+
+ /**
+ * File not writable: raise an exception.
+ *
+ * @expectedException IOException
+ * @expectedExceptionMessage Error accessing "sandbox/flat.db"
+ */
+ public function testWriteWithoutPermission()
+ {
+ touch(self::$file);
+ chmod(self::$file, 0440);
+ FileUtils::writeFlatDB(self::$file, null);
+ }
+
+ /**
+ * Folder non existent: raise an exception.
+ *
+ * @expectedException IOException
+ * @expectedExceptionMessage Error accessing "nopefolder"
+ */
+ public function testWriteFolderDoesNotExist()
+ {
+ FileUtils::writeFlatDB('nopefolder/file', null);
+ }
+
+ /**
+ * Folder non writable: raise an exception.
+ *
+ * @expectedException IOException
+ * @expectedExceptionMessage Error accessing "sandbox"
+ */
+ public function testWriteFolderPermission()
+ {
+ chmod(dirname(self::$file), 0555);
+ try {
+ FileUtils::writeFlatDB(self::$file, null);
+ } catch (Exception $e) {
+ chmod(dirname(self::$file), 0755);
+ throw $e;
+ }
+ }
+
+ /**
+ * Read non existent file, use default parameter.
+ */
+ public function testReadNotExistentFile()
+ {
+ $this->assertEquals(null, FileUtils::readFlatDB(self::$file));
+ $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test']));
+ }
+
+ /**
+ * Read non readable file, use default parameter.
+ */
+ public function testReadNotReadable()
+ {
+ touch(self::$file);
+ chmod(self::$file, 0220);
+ $this->assertEquals(null, FileUtils::readFlatDB(self::$file));
+ $this->assertEquals(['test'], FileUtils::readFlatDB(self::$file, ['test']));
+ }
+}
diff --git a/tests/HistoryTest.php b/tests/HistoryTest.php
new file mode 100644
index 0000000..9152584
--- /dev/null
+++ b/tests/HistoryTest.php
@@ -0,0 +1,207 @@
+assertFileNotExists(self::$historyFilePath);
+ }
+
+ /**
+ * Test that the history file is created if it doesn't exist.
+ */
+ public function testAddEventCreateFile()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateSettings();
+ $this->assertFileExists(self::$historyFilePath);
+ }
+
+ /**
+ * Not writable history file: raise an exception.
+ *
+ * @expectedException Exception
+ * @expectedExceptionMessage History file isn't readable or writable
+ */
+ public function testConstructNotWritable()
+ {
+ touch(self::$historyFilePath);
+ chmod(self::$historyFilePath, 0440);
+ $history = new History(self::$historyFilePath);
+ $history->updateSettings();
+ }
+
+ /**
+ * Not parsable history file: raise an exception.
+ *
+ * @expectedException Exception
+ * @expectedExceptionMessageRegExp /Could not parse history file/
+ */
+ public function testConstructNotParsable()
+ {
+ file_put_contents(self::$historyFilePath, 'not parsable');
+ $history = new History(self::$historyFilePath);
+ // gzinflate generates a warning
+ @$history->updateSettings();
+ }
+
+ /**
+ * Test add link event
+ */
+ public function testAddLink()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->addLink(['id' => 0]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::CREATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(0, $actual['id']);
+
+ $history = new History(self::$historyFilePath);
+ $history->addLink(['id' => 1]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::CREATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+
+ $history = new History(self::$historyFilePath);
+ $history->addLink(['id' => 'str']);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::CREATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals('str', $actual['id']);
+ }
+
+ /**
+ * Test updated link event
+ */
+ public function testUpdateLink()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateLink(['id' => 1]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::UPDATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+ }
+
+ /**
+ * Test delete link event
+ */
+ public function testDeleteLink()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->deleteLink(['id' => 1]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::DELETED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+ }
+
+ /**
+ * Test updated settings event
+ */
+ public function testUpdateSettings()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateSettings();
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::SETTINGS, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEmpty($actual['id']);
+ }
+
+ /**
+ * Make sure that new items are stored at the beginning
+ */
+ public function testHistoryOrder()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateLink(['id' => 1]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::UPDATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+
+ $history->addLink(['id' => 1]);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::CREATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+ }
+
+ /**
+ * Re-read history from file after writing an event
+ */
+ public function testHistoryRead()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateLink(['id' => 1]);
+ $history = new History(self::$historyFilePath);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::UPDATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+ }
+
+ /**
+ * Re-read history from file after writing an event and make sure that the order is correct
+ */
+ public function testHistoryOrderRead()
+ {
+ $history = new History(self::$historyFilePath);
+ $history->updateLink(['id' => 1]);
+ $history->addLink(['id' => 1]);
+
+ $history = new History(self::$historyFilePath);
+ $actual = $history->getHistory()[0];
+ $this->assertEquals(History::CREATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+
+ $actual = $history->getHistory()[1];
+ $this->assertEquals(History::UPDATED, $actual['event']);
+ $this->assertTrue(new DateTime('-2 seconds') < DateTime::createFromFormat(DateTime::ATOM, $actual['datetime']));
+ $this->assertEquals(1, $actual['id']);
+ }
+
+ /**
+ * Test retention time: delete old entries.
+ */
+ public function testHistoryRententionTime()
+ {
+ $history = new History(self::$historyFilePath, 5);
+ $history->updateLink(['id' => 1]);
+ $this->assertEquals(1, count($history->getHistory()));
+ $arr = $history->getHistory();
+ $arr[0]['datetime'] = (new DateTime('-1 hour'))->format(DateTime::ATOM);
+ FileUtils::writeFlatDB(self::$historyFilePath, $arr);
+
+ $history = new History(self::$historyFilePath, 60);
+ $this->assertEquals(1, count($history->getHistory()));
+ $this->assertEquals(1, $history->getHistory()[0]['id']);
+ $history->updateLink(['id' => 2]);
+ $this->assertEquals(1, count($history->getHistory()));
+ $this->assertEquals(2, $history->getHistory()[0]['id']);
+ }
+}
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 1f62a34..7bf98f9 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -101,7 +101,7 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
* Attempt to instantiate a LinkDB whereas the datastore is not writable
*
* @expectedException IOException
- * @expectedExceptionMessageRegExp /Error accessing\nnull/
+ * @expectedExceptionMessageRegExp /Error accessing "null"/
*/
public function testConstructDatastoreNotWriteable()
{
diff --git a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
index 5925a8e..f838f25 100644
--- a/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
+++ b/tests/NetscapeBookmarkUtils/BookmarkImportTest.php
@@ -33,6 +33,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/
protected static $testDatastore = 'sandbox/datastore.php';
+ /**
+ * @var string History file path
+ */
+ protected static $historyFilePath = 'sandbox/history.php';
+
/**
* @var LinkDB private LinkDB instance
*/
@@ -48,6 +53,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/
protected $conf;
+ /**
+ * @var History instance.
+ */
+ protected $history;
+
/**
* @var string Save the current timezone.
*/
@@ -73,6 +83,15 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->linkDb = new LinkDB(self::$testDatastore, true, false);
$this->conf = new ConfigManager('tests/utils/config/configJson');
$this->conf->set('resource.page_cache', $this->pagecache);
+ $this->history = new History(self::$historyFilePath);
+ }
+
+ /**
+ * Delete history file.
+ */
+ public function tearDown()
+ {
+ @unlink(self::$historyFilePath);
}
public static function tearDownAfterClass()
@@ -89,7 +108,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File empty.htm (0 bytes) has an unknown file format.'
.' Nothing was imported.',
- NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf)
+ NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history)
);
$this->assertEquals(0, count($this->linkDb));
}
@@ -102,7 +121,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$files = file2array('no_doctype.htm');
$this->assertEquals(
'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.',
- NetscapeBookmarkUtils::import(NULL, $files, NULL, $this->conf)
+ NetscapeBookmarkUtils::import(null, $files, null, $this->conf, $this->history)
);
$this->assertEquals(0, count($this->linkDb));
}
@@ -116,7 +135,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
.' 1 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(1, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -145,7 +164,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_nested.htm (1337 bytes) was successfully processed:'
.' 8 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(8, count($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb));
@@ -267,7 +286,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import([], $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
@@ -312,7 +331,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(1, count_private($this->linkDb));
@@ -356,7 +375,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -380,7 +399,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb));
@@ -406,7 +425,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb));
@@ -426,7 +445,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 2 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -452,7 +471,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -473,7 +492,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 2 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb));
@@ -497,7 +516,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -507,7 +526,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 0 links imported, 0 links overwritten, 2 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -526,7 +545,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -553,7 +572,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -578,7 +597,7 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->assertEquals(
'File same_date.htm (453 bytes) was successfully processed:'
.' 3 links imported, 0 links overwritten, 0 links skipped.',
- NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf)
+ NetscapeBookmarkUtils::import(array(), $files, $this->linkDb, $this->conf, $this->history)
);
$this->assertEquals(3, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb));
@@ -595,4 +614,32 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
$this->linkDb[2]['id']
);
}
+
+ public function testImportCreateUpdateHistory()
+ {
+ $post = [
+ 'privacy' => 'public',
+ 'overwrite' => 'true',
+ ];
+ $files = file2array('netscape_basic.htm');
+ $nbLinks = 2;
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
+ $history = $this->history->getHistory();
+ $this->assertEquals($nbLinks, count($history));
+ foreach ($history as $value) {
+ $this->assertEquals(History::CREATED, $value['event']);
+ $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $value['datetime']));
+ $this->assertTrue(is_int($value['id']));
+ }
+
+ // re-import as private, enable overwriting
+ NetscapeBookmarkUtils::import($post, $files, $this->linkDb, $this->conf, $this->history);
+ $history = $this->history->getHistory();
+ $this->assertEquals($nbLinks * 2, count($history));
+ for ($i = 0 ; $i < $nbLinks ; $i++) {
+ $this->assertEquals(History::UPDATED, $history[$i]['event']);
+ $this->assertTrue(new DateTime('-5 seconds') < DateTime::createFromFormat(DateTime::ATOM, $history[$i]['datetime']));
+ $this->assertTrue(is_int($history[$i]['id']));
+ }
+ }
}