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
This commit is contained in:
ArthurHoaro 2017-01-16 12:31:08 +01:00
parent b2306b0c78
commit 4306b184c4
6 changed files with 469 additions and 19 deletions

183
application/History.php Normal file
View file

@ -0,0 +1,183 @@
<?php
/**
* Class History
*
* Handle the history file tracing events in Shaarli.
* The history is stored as JSON in a file set by 'resource.history' setting.
*
* Available data:
* - event: event key
* - datetime: event date, in ISO8601 format.
* - id: event item identifier (currently only link IDs).
*
* Available event keys:
* - CREATED: new link
* - UPDATED: link updated
* - DELETED: link deleted
* - SETTINGS: the settings have been updated through the UI.
*
* Note: new events are put at the beginning of the file and history array.
*/
class History
{
/**
* @var string Action key: a new link has been created.
*/
const CREATED = 'CREATED';
/**
* @var string Action key: a link has been updated.
*/
const UPDATED = 'UPDATED';
/**
* @var string Action key: a link has been deleted.
*/
const DELETED = 'DELETED';
/**
* @var string Action key: settings have been updated.
*/
const SETTINGS = 'SETTINGS';
/**
* @var string History file path.
*/
protected $historyFilePath;
/**
* @var array History data.
*/
protected $history;
/**
* @var int History retention time in seconds (1 month).
*/
protected $retentionTime = 2678400;
/**
* History constructor.
*
* @param string $historyFilePath History file path.
* @param int $retentionTime History content rentention time in seconds.
*
* @throws Exception if something goes wrong.
*/
public function __construct($historyFilePath, $retentionTime = null)
{
$this->historyFilePath = $historyFilePath;
if ($retentionTime !== null) {
$this->retentionTime = $retentionTime;
}
$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)
{
$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()
{
return $this->history;
}
}

View file

@ -95,10 +95,11 @@ private static function importStatus(
* @param array $files Server $_FILES parameters * @param array $files Server $_FILES parameters
* @param LinkDB $linkDb Loaded LinkDB instance * @param LinkDB $linkDb Loaded LinkDB instance
* @param ConfigManager $conf instance * @param ConfigManager $conf instance
* @param History $history History instance
* *
* @return string Summary of the bookmark import status * @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']; $filename = $files['filetoupload']['name'];
$filesize = $files['filetoupload']['size']; $filesize = $files['filetoupload']['size'];
@ -182,6 +183,7 @@ public static function import($post, $files, $linkDb, $conf)
$linkDb[$existingLink['id']] = $newLink; $linkDb[$existingLink['id']] = $newLink;
$importCount++; $importCount++;
$overwriteCount++; $overwriteCount++;
$history->updateLink($newLink);
continue; continue;
} }
@ -193,6 +195,7 @@ public static function import($post, $files, $linkDb, $conf)
$newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']); $newLink['shorturl'] = link_small_hash($newLink['created'], $newLink['id']);
$linkDb[$newLink['id']] = $newLink; $linkDb[$newLink['id']] = $newLink;
$importCount++; $importCount++;
$history->addLink($newLink);
} }
$linkDb->save($conf->get('resource.page_cache')); $linkDb->save($conf->get('resource.page_cache'));

View file

@ -301,6 +301,7 @@ protected function setDefaultValues()
$this->setEmpty('resource.updates', 'data/updates.txt'); $this->setEmpty('resource.updates', 'data/updates.txt');
$this->setEmpty('resource.log', 'data/log.txt'); $this->setEmpty('resource.log', 'data/log.txt');
$this->setEmpty('resource.update_check', 'data/lastupdatecheck.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.raintpl_tpl', 'tpl/');
$this->setEmpty('resource.theme', 'default'); $this->setEmpty('resource.theme', 'default');
$this->setEmpty('resource.raintpl_tmp', 'tmp/'); $this->setEmpty('resource.raintpl_tmp', 'tmp/');

View file

@ -65,6 +65,7 @@
require_once 'application/config/ConfigPlugin.php'; require_once 'application/config/ConfigPlugin.php';
require_once 'application/FeedBuilder.php'; require_once 'application/FeedBuilder.php';
require_once 'application/FileUtils.php'; require_once 'application/FileUtils.php';
require_once 'application/History.php';
require_once 'application/HttpUtils.php'; require_once 'application/HttpUtils.php';
require_once 'application/Languages.php'; require_once 'application/Languages.php';
require_once 'application/LinkDB.php'; require_once 'application/LinkDB.php';
@ -754,6 +755,12 @@ function renderPage($conf, $pluginManager, $LINKSDB)
die($e->getMessage()); die($e->getMessage());
} }
try {
$history = new History($conf->get('resource.history'));
} catch(Exception $e) {
die($e->getMessage());
}
$PAGE = new PageBuilder($conf); $PAGE = new PageBuilder($conf);
$PAGE->assign('linkcount', count($LINKSDB)); $PAGE->assign('linkcount', count($LINKSDB));
$PAGE->assign('privateLinkcount', count_private($LINKSDB)); $PAGE->assign('privateLinkcount', count_private($LINKSDB));
@ -1146,6 +1153,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$conf->set('api.secret', escape($_POST['apiSecret'])); $conf->set('api.secret', escape($_POST['apiSecret']));
try { try {
$conf->write(isLoggedIn()); $conf->write(isLoggedIn());
$history->updateSettings();
invalidateCaches($conf->get('resource.page_cache')); invalidateCaches($conf->get('resource.page_cache'));
} }
catch(Exception $e) { catch(Exception $e) {
@ -1177,6 +1185,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false)); $PAGE->assign('hide_public_links', $conf->get('privacy.hide_public_links', false));
$PAGE->assign('api_enabled', $conf->get('api.enabled', true)); $PAGE->assign('api_enabled', $conf->get('api.enabled', true));
$PAGE->assign('api_secret', $conf->get('api.secret')); $PAGE->assign('api_secret', $conf->get('api.secret'));
$history->updateSettings();
$PAGE->renderPage('configure'); $PAGE->renderPage('configure');
exit; exit;
} }
@ -1206,6 +1215,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
unset($tags[array_search($needle,$tags)]); // Remove tag. unset($tags[array_search($needle,$tags)]); // Remove tag.
$value['tags']=trim(implode(' ',$tags)); $value['tags']=trim(implode(' ',$tags));
$LINKSDB[$key]=$value; $LINKSDB[$key]=$value;
$history->updateLink($LINKSDB[$key]);
} }
$LINKSDB->save($conf->get('resource.page_cache')); $LINKSDB->save($conf->get('resource.page_cache'));
echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>'; echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?do=changetag\';</script>';
@ -1223,6 +1233,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$tags[array_search($needle, $tags)] = trim($_POST['totag']); $tags[array_search($needle, $tags)] = trim($_POST['totag']);
$value['tags'] = implode(' ', array_unique($tags)); $value['tags'] = implode(' ', array_unique($tags));
$LINKSDB[$key] = $value; $LINKSDB[$key] = $value;
$history->updateLink($LINKSDB[$key]);
} }
$LINKSDB->save($conf->get('resource.page_cache')); // Save to disk. $LINKSDB->save($conf->get('resource.page_cache')); // Save to disk.
echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode(escape($_POST['totag'])).'\';</script>'; 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)
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = new DateTime(); $updated = new DateTime();
$shortUrl = $LINKSDB[$id]['shorturl']; $shortUrl = $LINKSDB[$id]['shorturl'];
$new = false;
} else { } else {
// New link // New link
$created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate); $created = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $linkdate);
$updated = null; $updated = null;
$shortUrl = link_small_hash($created, $id); $shortUrl = link_small_hash($created, $id);
$new = true;
} }
// Remove multiple spaces. // Remove multiple spaces.
@ -1300,6 +1313,11 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$LINKSDB[$id] = $link; $LINKSDB[$id] = $link;
$LINKSDB->save($conf->get('resource.page_cache')); $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 we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) {
@ -1346,6 +1364,7 @@ function renderPage($conf, $pluginManager, $LINKSDB)
$pluginManager->executeHooks('delete_link', $link); $pluginManager->executeHooks('delete_link', $link);
unset($LINKSDB[$id]); unset($LINKSDB[$id]);
$LINKSDB->save($conf->get('resource.page_cache')); // save to disk $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 we are called from the bookmarklet, we must close the popup:
if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; } 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)
$_POST, $_POST,
$_FILES, $_FILES,
$LINKSDB, $LINKSDB,
$conf $conf,
$history
); );
echo '<script>alert("'.$status.'");document.location=\'?do=' echo '<script>alert("'.$status.'");document.location=\'?do='
.Router::$PAGE_IMPORT .'\';</script>'; .Router::$PAGE_IMPORT .'\';</script>';
@ -1557,6 +1577,7 @@ function($a, $b) { return $a['order'] - $b['order']; }
// Plugin administration form action // Plugin administration form action
if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) { if ($targetPage == Router::$PAGE_SAVE_PLUGINSADMIN) {
$history->updateSettings();
try { try {
if (isset($_POST['parameters_form'])) { if (isset($_POST['parameters_form'])) {
unset($_POST['parameters_form']); unset($_POST['parameters_form']);

195
tests/HistoryTest.php Normal file
View file

@ -0,0 +1,195 @@
<?php
require_once 'application/History.php';
class HistoryTest extends PHPUnit_Framework_TestCase
{
/**
* @var string History file path
*/
protected static $historyFilePath = 'sandbox/history.php';
/**
* Delete history file.
*/
public function tearDown()
{
@unlink(self::$historyFilePath);
}
/**
* Test that the history file is created if it doesn't exist.
*/
public function testConstructFileCreated()
{
new History(self::$historyFilePath);
$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);
new History(self::$historyFilePath);
}
/**
* 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');
// gzinflate generates a warning
@new History(self::$historyFilePath);
}
/**
* 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']);
}
}

View file

@ -33,6 +33,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/ */
protected static $testDatastore = 'sandbox/datastore.php'; protected static $testDatastore = 'sandbox/datastore.php';
/**
* @var string History file path
*/
protected static $historyFilePath = 'sandbox/history.php';
/** /**
* @var LinkDB private LinkDB instance * @var LinkDB private LinkDB instance
*/ */
@ -48,6 +53,11 @@ class BookmarkImportTest extends PHPUnit_Framework_TestCase
*/ */
protected $conf; protected $conf;
/**
* @var History instance.
*/
protected $history;
/** /**
* @var string Save the current timezone. * @var string Save the current timezone.
*/ */
@ -73,6 +83,15 @@ protected function setUp()
$this->linkDb = new LinkDB(self::$testDatastore, true, false); $this->linkDb = new LinkDB(self::$testDatastore, true, false);
$this->conf = new ConfigManager('tests/utils/config/configJson'); $this->conf = new ConfigManager('tests/utils/config/configJson');
$this->conf->set('resource.page_cache', $this->pagecache); $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() public static function tearDownAfterClass()
@ -89,7 +108,7 @@ public function testImportEmptyData()
$this->assertEquals( $this->assertEquals(
'File empty.htm (0 bytes) has an unknown file format.' 'File empty.htm (0 bytes) has an unknown file format.'
.' Nothing was imported.', .' 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)); $this->assertEquals(0, count($this->linkDb));
} }
@ -102,7 +121,7 @@ public function testImportNoDoctype()
$files = file2array('no_doctype.htm'); $files = file2array('no_doctype.htm');
$this->assertEquals( $this->assertEquals(
'File no_doctype.htm (350 bytes) has an unknown file format. Nothing was imported.', '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)); $this->assertEquals(0, count($this->linkDb));
} }
@ -116,7 +135,7 @@ public function testImportInternetExplorerEncoding()
$this->assertEquals( $this->assertEquals(
'File internet_explorer_encoding.htm (356 bytes) was successfully processed:' 'File internet_explorer_encoding.htm (356 bytes) was successfully processed:'
.' 1 links imported, 0 links overwritten, 0 links skipped.', .' 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(1, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -145,7 +164,7 @@ public function testImportNested()
$this->assertEquals( $this->assertEquals(
'File netscape_nested.htm (1337 bytes) was successfully processed:' 'File netscape_nested.htm (1337 bytes) was successfully processed:'
.' 8 links imported, 0 links overwritten, 0 links skipped.', .' 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(8, count($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
@ -267,7 +286,7 @@ public function testImportDefaultPrivacyNoPost()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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)); $this->assertEquals(2, count($this->linkDb));
@ -312,7 +331,7 @@ public function testImportKeepPrivacy()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(1, count_private($this->linkDb)); $this->assertEquals(1, count_private($this->linkDb));
@ -356,7 +375,7 @@ public function testImportAsPublic()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -380,7 +399,7 @@ public function testImportAsPrivate()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
@ -406,7 +425,7 @@ public function testOverwriteAsPublic()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
@ -426,7 +445,7 @@ public function testOverwriteAsPublic()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 2 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -452,7 +471,7 @@ public function testOverwriteAsPrivate()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -473,7 +492,7 @@ public function testOverwriteAsPrivate()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 2 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(2, count_private($this->linkDb)); $this->assertEquals(2, count_private($this->linkDb));
@ -497,7 +516,7 @@ public function testSkipOverwrite()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -507,7 +526,7 @@ public function testSkipOverwrite()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 0 links imported, 0 links overwritten, 2 links skipped.', .' 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(2, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -526,7 +545,7 @@ public function testSetDefaultTags()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -553,7 +572,7 @@ public function testSanitizeDefaultTags()
$this->assertEquals( $this->assertEquals(
'File netscape_basic.htm (482 bytes) was successfully processed:' 'File netscape_basic.htm (482 bytes) was successfully processed:'
.' 2 links imported, 0 links overwritten, 0 links skipped.', .' 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($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -578,7 +597,7 @@ public function testImportSameDate()
$this->assertEquals( $this->assertEquals(
'File same_date.htm (453 bytes) was successfully processed:' 'File same_date.htm (453 bytes) was successfully processed:'
.' 3 links imported, 0 links overwritten, 0 links skipped.', .' 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(3, count($this->linkDb));
$this->assertEquals(0, count_private($this->linkDb)); $this->assertEquals(0, count_private($this->linkDb));
@ -595,4 +614,32 @@ public function testImportSameDate()
$this->linkDb[2]['id'] $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']));
}
}
} }