Refactor Netscape bookmark exporting
Relates to https://github.com/shaarli/netscape-bookmark-parser/issues/5 Fixes: - respect the Netscape bookmark format "specification" Modifications: - [application] introduce the NetscapeBookmarkUtils class - [template] export - improve formatting, rename export selection parameter - [template] export.bookmarks - template for Netscape exports - [tests] bookmark filtering, additional field generation Signed-off-by: VirtualTam <virtualtam@flibidi.net>
This commit is contained in:
parent
745304c842
commit
cd5327bee8
5 changed files with 202 additions and 42 deletions
47
application/NetscapeBookmarkUtils.php
Normal file
47
application/NetscapeBookmarkUtils.php
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities to import and export bookmarks using the Netscape format
|
||||||
|
*/
|
||||||
|
class NetscapeBookmarkUtils
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters links and adds Netscape-formatted fields
|
||||||
|
*
|
||||||
|
* Added fields:
|
||||||
|
* - timestamp link addition date, using the Unix epoch format
|
||||||
|
* - taglist comma-separated tag list
|
||||||
|
*
|
||||||
|
* @param LinkDB $linkDb The link datastore
|
||||||
|
* @param string $selection Which links to export: (all|private|public)
|
||||||
|
*
|
||||||
|
* @throws Exception Invalid export selection
|
||||||
|
*
|
||||||
|
* @return array The links to be exported, with additional fields
|
||||||
|
*/
|
||||||
|
public static function filterAndFormat($linkDb, $selection)
|
||||||
|
{
|
||||||
|
// see tpl/export.html for possible values
|
||||||
|
if (! in_array($selection, array('all','public','private'))) {
|
||||||
|
throw new Exception('Invalid export selection: "'.$selection.'"');
|
||||||
|
}
|
||||||
|
|
||||||
|
$bookmarkLinks = array();
|
||||||
|
|
||||||
|
foreach ($linkDb as $link) {
|
||||||
|
if ($link['private'] != 0 && $selection == 'public') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($link['private'] == 0 && $selection == 'private') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||||
|
$link['timestamp'] = $date->getTimestamp();
|
||||||
|
$link['taglist'] = str_replace(' ', ',', $link['tags']);
|
||||||
|
$bookmarkLinks[] = $link;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bookmarkLinks;
|
||||||
|
}
|
||||||
|
}
|
57
index.php
57
index.php
|
@ -161,6 +161,7 @@ require_once 'application/HttpUtils.php';
|
||||||
require_once 'application/LinkDB.php';
|
require_once 'application/LinkDB.php';
|
||||||
require_once 'application/LinkFilter.php';
|
require_once 'application/LinkFilter.php';
|
||||||
require_once 'application/LinkUtils.php';
|
require_once 'application/LinkUtils.php';
|
||||||
|
require_once 'application/NetscapeBookmarkUtils.php';
|
||||||
require_once 'application/TimeZone.php';
|
require_once 'application/TimeZone.php';
|
||||||
require_once 'application/Url.php';
|
require_once 'application/Url.php';
|
||||||
require_once 'application/Utils.php';
|
require_once 'application/Utils.php';
|
||||||
|
@ -1584,44 +1585,36 @@ function renderPage()
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- Export as Netscape Bookmarks HTML file.
|
// -------- Export as Netscape Bookmarks HTML file.
|
||||||
if ($targetPage == Router::$PAGE_EXPORT)
|
if ($targetPage == Router::$PAGE_EXPORT) {
|
||||||
{
|
if (empty($_GET['selection'])) {
|
||||||
if (empty($_GET['what']))
|
|
||||||
{
|
|
||||||
$PAGE->assign('linkcount',count($LINKSDB));
|
$PAGE->assign('linkcount',count($LINKSDB));
|
||||||
$PAGE->renderPage('export');
|
$PAGE->renderPage('export');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
$exportWhat=$_GET['what'];
|
|
||||||
if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export???');
|
|
||||||
|
|
||||||
header('Content-Type: text/html; charset=utf-8');
|
// export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html
|
||||||
header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html');
|
$selection = $_GET['selection'];
|
||||||
$currentdate=date('Y/m/d H:i:s');
|
try {
|
||||||
echo <<<HTML
|
$PAGE->assign(
|
||||||
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
'links',
|
||||||
<!-- This is an automatically generated file.
|
NetscapeBookmarkUtils::filterAndFormat($LINKSDB, $selection)
|
||||||
It will be read and overwritten.
|
);
|
||||||
DO NOT EDIT! -->
|
} catch (Exception $exc) {
|
||||||
<!-- Shaarli {$exportWhat} bookmarks export on {$currentdate} -->
|
header('Content-Type: text/plain; charset=utf-8');
|
||||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
echo $exc->getMessage();
|
||||||
<TITLE>Bookmarks</TITLE>
|
exit;
|
||||||
<H1>Bookmarks</H1>
|
|
||||||
HTML;
|
|
||||||
foreach($LINKSDB as $link)
|
|
||||||
{
|
|
||||||
if ($exportWhat=='all' ||
|
|
||||||
($exportWhat=='private' && $link['private']!=0) ||
|
|
||||||
($exportWhat=='public' && $link['private']==0))
|
|
||||||
{
|
|
||||||
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
|
||||||
echo '<DT><A HREF="'.$link['url'].'" ADD_DATE="'.$date->getTimestamp().'" PRIVATE="'.$link['private'].'"';
|
|
||||||
if ($link['tags']!='') echo ' TAGS="'.str_replace(' ',',',$link['tags']).'"';
|
|
||||||
echo '>'.$link['title']."</A>\n";
|
|
||||||
if ($link['description']!='') echo '<DD>'.$link['description']."\n";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
exit;
|
$now = new DateTime();
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
header(
|
||||||
|
'Content-disposition: attachment; filename=bookmarks_'
|
||||||
|
.$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html'
|
||||||
|
);
|
||||||
|
$PAGE->assign('date', $now->format(DateTime::RFC822));
|
||||||
|
$PAGE->assign('eol', PHP_EOL);
|
||||||
|
$PAGE->assign('selection', $selection);
|
||||||
|
$PAGE->renderPage('export.bookmarks');
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- User is uploading a file for import
|
// -------- User is uploading a file for import
|
||||||
|
|
104
tests/NetscapeBookmarkUtilsTest.php
Normal file
104
tests/NetscapeBookmarkUtilsTest.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
require_once 'application/NetscapeBookmarkUtils.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Netscape bookmark import and export
|
||||||
|
*/
|
||||||
|
class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string datastore to test write operations
|
||||||
|
*/
|
||||||
|
protected static $testDatastore = 'sandbox/datastore.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ReferenceLinkDB instance.
|
||||||
|
*/
|
||||||
|
protected static $refDb = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LinkDB private LinkDB instance.
|
||||||
|
*/
|
||||||
|
protected static $linkDb = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiate reference data
|
||||||
|
*/
|
||||||
|
public static function setUpBeforeClass()
|
||||||
|
{
|
||||||
|
self::$refDb = new ReferenceLinkDB();
|
||||||
|
self::$refDb->write(self::$testDatastore);
|
||||||
|
self::$linkDb = new LinkDB(self::$testDatastore, true, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to export an invalid link selection
|
||||||
|
* @expectedException Exception
|
||||||
|
* @expectedExceptionMessageRegExp /Invalid export selection/
|
||||||
|
*/
|
||||||
|
public function testFilterAndFormatInvalid()
|
||||||
|
{
|
||||||
|
NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'derp');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare all links for export
|
||||||
|
*/
|
||||||
|
public function testFilterAndFormatAll()
|
||||||
|
{
|
||||||
|
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all');
|
||||||
|
$this->assertEquals(self::$refDb->countLinks(), sizeof($links));
|
||||||
|
foreach ($links as $link) {
|
||||||
|
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||||
|
$this->assertEquals(
|
||||||
|
$date->getTimestamp(),
|
||||||
|
$link['timestamp']
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
str_replace(' ', ',', $link['tags']),
|
||||||
|
$link['taglist']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare private links for export
|
||||||
|
*/
|
||||||
|
public function testFilterAndFormatPrivate()
|
||||||
|
{
|
||||||
|
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private');
|
||||||
|
$this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links));
|
||||||
|
foreach ($links as $link) {
|
||||||
|
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||||
|
$this->assertEquals(
|
||||||
|
$date->getTimestamp(),
|
||||||
|
$link['timestamp']
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
str_replace(' ', ',', $link['tags']),
|
||||||
|
$link['taglist']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare public links for export
|
||||||
|
*/
|
||||||
|
public function testFilterAndFormatPublic()
|
||||||
|
{
|
||||||
|
$links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public');
|
||||||
|
$this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links));
|
||||||
|
foreach ($links as $link) {
|
||||||
|
$date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
|
||||||
|
$this->assertEquals(
|
||||||
|
$date->getTimestamp(),
|
||||||
|
$link['timestamp']
|
||||||
|
);
|
||||||
|
$this->assertEquals(
|
||||||
|
str_replace(' ', ',', $link['tags']),
|
||||||
|
$link['taglist']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
tpl/export.bookmarks.html
Normal file
10
tpl/export.bookmarks.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||||
|
<!-- This is an automatically generated file.
|
||||||
|
It will be read and overwritten.
|
||||||
|
Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore}
|
||||||
|
<TITLE>{$pagetitle}</TITLE>
|
||||||
|
<H1>Shaarli export of {$selection} bookmarks on {$date}</H1>
|
||||||
|
<DL><p>{loop="links"}
|
||||||
|
<DT><A HREF="{$value.url}" ADD_DATE="{$value.timestamp}" PRIVATE="{$value.private}" TAGS="{$value.taglist}">{$value.title}</A>{if="$value.description"}{$eol}<DD>{$value.description}{/if}{/loop}
|
||||||
|
</DL><p>
|
|
@ -2,15 +2,21 @@
|
||||||
<html>
|
<html>
|
||||||
<head>{include="includes"}</head>
|
<head>{include="includes"}</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="pageheader">
|
<div id="pageheader">
|
||||||
{include="page.header"}
|
{include="page.header"}
|
||||||
<div id="toolsdiv">
|
<div id="toolsdiv">
|
||||||
<a href="?do=export&what=all"><b>Export all</b> <span>: Export all links</span></a><br><br>
|
<a href="?do=export&selection=all">
|
||||||
<a href="?do=export&what=public"><b>Export public</b> <span>: Export public links only</span></a><br><br>
|
<b>Export all</b><span>: Export all links</span>
|
||||||
<a href="?do=export&what=private"><b>Export private</b> <span>: Export private links only</span></a>
|
</a><br>
|
||||||
<div class="clear"></div>
|
<a href="?do=export&selection=public">
|
||||||
</div>
|
<b>Export public</b><span>: Only export public links</span>
|
||||||
</div>
|
</a><br>
|
||||||
{include="page.footer"}
|
<a href="?do=export&selection=private">
|
||||||
|
<b>Export private</b><span>: Only export private links</span>
|
||||||
|
</a>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{include="page.footer"}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue