diff options
author | VirtualTam <virtualtam@flibidi.net> | 2016-04-10 17:34:07 +0200 |
---|---|---|
committer | VirtualTam <virtualtam@flibidi.net> | 2016-04-10 21:28:04 +0200 |
commit | cd5327bee83f3e9467d786752bbd447963b941f7 (patch) | |
tree | 7abb6d242fc551b33b1d5b9f066fdabf4ef64c4d | |
parent | 745304c842e6e1234aac41a3f1c496c4522f32c5 (diff) | |
download | Shaarli-cd5327bee83f3e9467d786752bbd447963b941f7.tar.gz Shaarli-cd5327bee83f3e9467d786752bbd447963b941f7.tar.zst Shaarli-cd5327bee83f3e9467d786752bbd447963b941f7.zip |
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>
-rw-r--r-- | application/NetscapeBookmarkUtils.php | 47 | ||||
-rw-r--r-- | index.php | 57 | ||||
-rw-r--r-- | tests/NetscapeBookmarkUtilsTest.php | 104 | ||||
-rw-r--r-- | tpl/export.bookmarks.html | 10 | ||||
-rw-r--r-- | tpl/export.html | 26 |
5 files changed, 202 insertions, 42 deletions
diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php new file mode 100644 index 00000000..8a296705 --- /dev/null +++ b/application/NetscapeBookmarkUtils.php | |||
@@ -0,0 +1,47 @@ | |||
1 | <?php | ||
2 | |||
3 | /** | ||
4 | * Utilities to import and export bookmarks using the Netscape format | ||
5 | */ | ||
6 | class NetscapeBookmarkUtils | ||
7 | { | ||
8 | |||
9 | /** | ||
10 | * Filters links and adds Netscape-formatted fields | ||
11 | * | ||
12 | * Added fields: | ||
13 | * - timestamp link addition date, using the Unix epoch format | ||
14 | * - taglist comma-separated tag list | ||
15 | * | ||
16 | * @param LinkDB $linkDb The link datastore | ||
17 | * @param string $selection Which links to export: (all|private|public) | ||
18 | * | ||
19 | * @throws Exception Invalid export selection | ||
20 | * | ||
21 | * @return array The links to be exported, with additional fields | ||
22 | */ | ||
23 | public static function filterAndFormat($linkDb, $selection) | ||
24 | { | ||
25 | // see tpl/export.html for possible values | ||
26 | if (! in_array($selection, array('all','public','private'))) { | ||
27 | throw new Exception('Invalid export selection: "'.$selection.'"'); | ||
28 | } | ||
29 | |||
30 | $bookmarkLinks = array(); | ||
31 | |||
32 | foreach ($linkDb as $link) { | ||
33 | if ($link['private'] != 0 && $selection == 'public') { | ||
34 | continue; | ||
35 | } | ||
36 | if ($link['private'] == 0 && $selection == 'private') { | ||
37 | continue; | ||
38 | } | ||
39 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
40 | $link['timestamp'] = $date->getTimestamp(); | ||
41 | $link['taglist'] = str_replace(' ', ',', $link['tags']); | ||
42 | $bookmarkLinks[] = $link; | ||
43 | } | ||
44 | |||
45 | return $bookmarkLinks; | ||
46 | } | ||
47 | } | ||
@@ -161,6 +161,7 @@ require_once 'application/HttpUtils.php'; | |||
161 | require_once 'application/LinkDB.php'; | 161 | require_once 'application/LinkDB.php'; |
162 | require_once 'application/LinkFilter.php'; | 162 | require_once 'application/LinkFilter.php'; |
163 | require_once 'application/LinkUtils.php'; | 163 | require_once 'application/LinkUtils.php'; |
164 | require_once 'application/NetscapeBookmarkUtils.php'; | ||
164 | require_once 'application/TimeZone.php'; | 165 | require_once 'application/TimeZone.php'; |
165 | require_once 'application/Url.php'; | 166 | require_once 'application/Url.php'; |
166 | require_once 'application/Utils.php'; | 167 | require_once 'application/Utils.php'; |
@@ -1584,44 +1585,36 @@ function renderPage() | |||
1584 | } | 1585 | } |
1585 | 1586 | ||
1586 | // -------- Export as Netscape Bookmarks HTML file. | 1587 | // -------- Export as Netscape Bookmarks HTML file. |
1587 | if ($targetPage == Router::$PAGE_EXPORT) | 1588 | if ($targetPage == Router::$PAGE_EXPORT) { |
1588 | { | 1589 | if (empty($_GET['selection'])) { |
1589 | if (empty($_GET['what'])) | ||
1590 | { | ||
1591 | $PAGE->assign('linkcount',count($LINKSDB)); | 1590 | $PAGE->assign('linkcount',count($LINKSDB)); |
1592 | $PAGE->renderPage('export'); | 1591 | $PAGE->renderPage('export'); |
1593 | exit; | 1592 | exit; |
1594 | } | 1593 | } |
1595 | $exportWhat=$_GET['what']; | ||
1596 | if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export???'); | ||
1597 | 1594 | ||
1598 | header('Content-Type: text/html; charset=utf-8'); | 1595 | // export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html |
1599 | header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html'); | 1596 | $selection = $_GET['selection']; |
1600 | $currentdate=date('Y/m/d H:i:s'); | 1597 | try { |
1601 | echo <<<HTML | 1598 | $PAGE->assign( |
1602 | <!DOCTYPE NETSCAPE-Bookmark-file-1> | 1599 | 'links', |
1603 | <!-- This is an automatically generated file. | 1600 | NetscapeBookmarkUtils::filterAndFormat($LINKSDB, $selection) |
1604 | It will be read and overwritten. | 1601 | ); |
1605 | DO NOT EDIT! --> | 1602 | } catch (Exception $exc) { |
1606 | <!-- Shaarli {$exportWhat} bookmarks export on {$currentdate} --> | 1603 | header('Content-Type: text/plain; charset=utf-8'); |
1607 | <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> | 1604 | echo $exc->getMessage(); |
1608 | <TITLE>Bookmarks</TITLE> | 1605 | exit; |
1609 | <H1>Bookmarks</H1> | ||
1610 | HTML; | ||
1611 | foreach($LINKSDB as $link) | ||
1612 | { | ||
1613 | if ($exportWhat=='all' || | ||
1614 | ($exportWhat=='private' && $link['private']!=0) || | ||
1615 | ($exportWhat=='public' && $link['private']==0)) | ||
1616 | { | ||
1617 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
1618 | echo '<DT><A HREF="'.$link['url'].'" ADD_DATE="'.$date->getTimestamp().'" PRIVATE="'.$link['private'].'"'; | ||
1619 | if ($link['tags']!='') echo ' TAGS="'.str_replace(' ',',',$link['tags']).'"'; | ||
1620 | echo '>'.$link['title']."</A>\n"; | ||
1621 | if ($link['description']!='') echo '<DD>'.$link['description']."\n"; | ||
1622 | } | ||
1623 | } | 1606 | } |
1624 | exit; | 1607 | $now = new DateTime(); |
1608 | header('Content-Type: text/html; charset=utf-8'); | ||
1609 | header( | ||
1610 | 'Content-disposition: attachment; filename=bookmarks_' | ||
1611 | .$selection.'_'.$now->format(LinkDB::LINK_DATE_FORMAT).'.html' | ||
1612 | ); | ||
1613 | $PAGE->assign('date', $now->format(DateTime::RFC822)); | ||
1614 | $PAGE->assign('eol', PHP_EOL); | ||
1615 | $PAGE->assign('selection', $selection); | ||
1616 | $PAGE->renderPage('export.bookmarks'); | ||
1617 | exit; | ||
1625 | } | 1618 | } |
1626 | 1619 | ||
1627 | // -------- User is uploading a file for import | 1620 | // -------- User is uploading a file for import |
diff --git a/tests/NetscapeBookmarkUtilsTest.php b/tests/NetscapeBookmarkUtilsTest.php new file mode 100644 index 00000000..b7472d92 --- /dev/null +++ b/tests/NetscapeBookmarkUtilsTest.php | |||
@@ -0,0 +1,104 @@ | |||
1 | <?php | ||
2 | |||
3 | require_once 'application/NetscapeBookmarkUtils.php'; | ||
4 | |||
5 | /** | ||
6 | * Netscape bookmark import and export | ||
7 | */ | ||
8 | class NetscapeBookmarkUtilsTest extends PHPUnit_Framework_TestCase | ||
9 | { | ||
10 | /** | ||
11 | * @var string datastore to test write operations | ||
12 | */ | ||
13 | protected static $testDatastore = 'sandbox/datastore.php'; | ||
14 | |||
15 | /** | ||
16 | * @var ReferenceLinkDB instance. | ||
17 | */ | ||
18 | protected static $refDb = null; | ||
19 | |||
20 | /** | ||
21 | * @var LinkDB private LinkDB instance. | ||
22 | */ | ||
23 | protected static $linkDb = null; | ||
24 | |||
25 | /** | ||
26 | * Instantiate reference data | ||
27 | */ | ||
28 | public static function setUpBeforeClass() | ||
29 | { | ||
30 | self::$refDb = new ReferenceLinkDB(); | ||
31 | self::$refDb->write(self::$testDatastore); | ||
32 | self::$linkDb = new LinkDB(self::$testDatastore, true, false); | ||
33 | } | ||
34 | |||
35 | /** | ||
36 | * Attempt to export an invalid link selection | ||
37 | * @expectedException Exception | ||
38 | * @expectedExceptionMessageRegExp /Invalid export selection/ | ||
39 | */ | ||
40 | public function testFilterAndFormatInvalid() | ||
41 | { | ||
42 | NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'derp'); | ||
43 | } | ||
44 | |||
45 | /** | ||
46 | * Prepare all links for export | ||
47 | */ | ||
48 | public function testFilterAndFormatAll() | ||
49 | { | ||
50 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'all'); | ||
51 | $this->assertEquals(self::$refDb->countLinks(), sizeof($links)); | ||
52 | foreach ($links as $link) { | ||
53 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
54 | $this->assertEquals( | ||
55 | $date->getTimestamp(), | ||
56 | $link['timestamp'] | ||
57 | ); | ||
58 | $this->assertEquals( | ||
59 | str_replace(' ', ',', $link['tags']), | ||
60 | $link['taglist'] | ||
61 | ); | ||
62 | } | ||
63 | } | ||
64 | |||
65 | /** | ||
66 | * Prepare private links for export | ||
67 | */ | ||
68 | public function testFilterAndFormatPrivate() | ||
69 | { | ||
70 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'private'); | ||
71 | $this->assertEquals(self::$refDb->countPrivateLinks(), sizeof($links)); | ||
72 | foreach ($links as $link) { | ||
73 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
74 | $this->assertEquals( | ||
75 | $date->getTimestamp(), | ||
76 | $link['timestamp'] | ||
77 | ); | ||
78 | $this->assertEquals( | ||
79 | str_replace(' ', ',', $link['tags']), | ||
80 | $link['taglist'] | ||
81 | ); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * Prepare public links for export | ||
87 | */ | ||
88 | public function testFilterAndFormatPublic() | ||
89 | { | ||
90 | $links = NetscapeBookmarkUtils::filterAndFormat(self::$linkDb, 'public'); | ||
91 | $this->assertEquals(self::$refDb->countPublicLinks(), sizeof($links)); | ||
92 | foreach ($links as $link) { | ||
93 | $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']); | ||
94 | $this->assertEquals( | ||
95 | $date->getTimestamp(), | ||
96 | $link['timestamp'] | ||
97 | ); | ||
98 | $this->assertEquals( | ||
99 | str_replace(' ', ',', $link['tags']), | ||
100 | $link['taglist'] | ||
101 | ); | ||
102 | } | ||
103 | } | ||
104 | } | ||
diff --git a/tpl/export.bookmarks.html b/tpl/export.bookmarks.html new file mode 100644 index 00000000..da733257 --- /dev/null +++ b/tpl/export.bookmarks.html | |||
@@ -0,0 +1,10 @@ | |||
1 | <!DOCTYPE NETSCAPE-Bookmark-file-1> | ||
2 | <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8"> | ||
3 | <!-- This is an automatically generated file. | ||
4 | It will be read and overwritten. | ||
5 | Do Not Edit! -->{ignore}The RainTPL loop is formatted to avoid generating extra newlines{/ignore} | ||
6 | <TITLE>{$pagetitle}</TITLE> | ||
7 | <H1>Shaarli export of {$selection} bookmarks on {$date}</H1> | ||
8 | <DL><p>{loop="links"} | ||
9 | <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} | ||
10 | </DL><p> | ||
diff --git a/tpl/export.html b/tpl/export.html index 9d101db4..9582627a 100644 --- a/tpl/export.html +++ b/tpl/export.html | |||
@@ -2,15 +2,21 @@ | |||
2 | <html> | 2 | <html> |
3 | <head>{include="includes"}</head> | 3 | <head>{include="includes"}</head> |
4 | <body> | 4 | <body> |
5 | <div id="pageheader"> | 5 | <div id="pageheader"> |
6 | {include="page.header"} | 6 | {include="page.header"} |
7 | <div id="toolsdiv"> | 7 | <div id="toolsdiv"> |
8 | <a href="?do=export&what=all"><b>Export all</b> <span>: Export all links</span></a><br><br> | 8 | <a href="?do=export&selection=all"> |
9 | <a href="?do=export&what=public"><b>Export public</b> <span>: Export public links only</span></a><br><br> | 9 | <b>Export all</b><span>: Export all links</span> |
10 | <a href="?do=export&what=private"><b>Export private</b> <span>: Export private links only</span></a> | 10 | </a><br> |
11 | <div class="clear"></div> | 11 | <a href="?do=export&selection=public"> |
12 | </div> | 12 | <b>Export public</b><span>: Only export public links</span> |
13 | </div> | 13 | </a><br> |
14 | {include="page.footer"} | 14 | <a href="?do=export&selection=private"> |
15 | <b>Export private</b><span>: Only export private links</span> | ||
16 | </a> | ||
17 | <div class="clear"></div> | ||
18 | </div> | ||
19 | </div> | ||
20 | {include="page.footer"} | ||
15 | </body> | 21 | </body> |
16 | </html> | 22 | </html> |