]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Refactor Netscape bookmark exporting 538/head
authorVirtualTam <virtualtam@flibidi.net>
Sun, 10 Apr 2016 15:34:07 +0000 (17:34 +0200)
committerVirtualTam <virtualtam@flibidi.net>
Sun, 10 Apr 2016 19:28:04 +0000 (21:28 +0200)
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>
application/NetscapeBookmarkUtils.php [new file with mode: 0644]
index.php
tests/NetscapeBookmarkUtilsTest.php [new file with mode: 0644]
tpl/export.bookmarks.html [new file with mode: 0644]
tpl/export.html

diff --git a/application/NetscapeBookmarkUtils.php b/application/NetscapeBookmarkUtils.php
new file mode 100644 (file)
index 0000000..8a29670
--- /dev/null
@@ -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;
+    }
+}
index d3369a2dcd58335772dc4dbe4c21f6735451c0c6..456d93c24ccd920ee284cac6d988da174703ec82 100644 (file)
--- a/index.php
+++ b/index.php
@@ -161,6 +161,7 @@ require_once 'application/HttpUtils.php';
 require_once 'application/LinkDB.php';
 require_once 'application/LinkFilter.php';
 require_once 'application/LinkUtils.php';
+require_once 'application/NetscapeBookmarkUtils.php';
 require_once 'application/TimeZone.php';
 require_once 'application/Url.php';
 require_once 'application/Utils.php';
@@ -1584,44 +1585,36 @@ function renderPage()
     }
 
     // -------- Export as Netscape Bookmarks HTML file.
-    if ($targetPage == Router::$PAGE_EXPORT)
-    {
-        if (empty($_GET['what']))
-        {
+    if ($targetPage == Router::$PAGE_EXPORT) {
+        if (empty($_GET['selection'])) {
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->renderPage('export');
             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');
-        header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html');
-        $currentdate=date('Y/m/d H:i:s');
-        echo <<<HTML
-<!DOCTYPE NETSCAPE-Bookmark-file-1>
-<!-- This is an automatically generated file.
-     It will be read and overwritten.
-     DO NOT EDIT! -->
-<!-- Shaarli {$exportWhat} bookmarks export on {$currentdate} -->
-<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
-<TITLE>Bookmarks</TITLE>
-<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";
-            }
+        // export as bookmarks_(all|private|public)_YYYYmmdd_HHMMSS.html
+        $selection = $_GET['selection'];
+        try {
+            $PAGE->assign(
+                'links',
+                NetscapeBookmarkUtils::filterAndFormat($LINKSDB, $selection)
+            );
+        } catch (Exception $exc) {
+            header('Content-Type: text/plain; charset=utf-8');
+            echo $exc->getMessage();
+            exit;
         }
-                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
diff --git a/tests/NetscapeBookmarkUtilsTest.php b/tests/NetscapeBookmarkUtilsTest.php
new file mode 100644 (file)
index 0000000..b7472d9
--- /dev/null
@@ -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']
+            );
+        }
+    }
+}
diff --git a/tpl/export.bookmarks.html b/tpl/export.bookmarks.html
new file mode 100644 (file)
index 0000000..da73325
--- /dev/null
@@ -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>
index 9d101db43092f57c877d0be554cd0a6b3923771d..9582627a2e4d617c6b9e7ccba117186ecaad64ed 100644 (file)
@@ -2,15 +2,21 @@
 <html>
 <head>{include="includes"}</head>
 <body>
-<div id="pageheader">
-       {include="page.header"}
-       <div id="toolsdiv">
-           <a href="?do=export&amp;what=all"><b>Export all</b> <span>: Export all links</span></a><br><br>
-           <a href="?do=export&amp;what=public"><b>Export public</b> <span>: Export public links only</span></a><br><br>
-           <a href="?do=export&amp;what=private"><b>Export private</b> <span>: Export private links only</span></a>
-           <div class="clear"></div>
-       </div>
-</div>
-{include="page.footer"}
+  <div id="pageheader">
+    {include="page.header"}
+    <div id="toolsdiv">
+      <a href="?do=export&amp;selection=all">
+        <b>Export all</b><span>: Export all links</span>
+      </a><br>
+      <a href="?do=export&amp;selection=public">
+        <b>Export public</b><span>: Only export public links</span>
+      </a><br>
+      <a href="?do=export&amp;selection=private">
+        <b>Export private</b><span>: Only export private links</span>
+      </a>
+      <div class="clear"></div>
+    </div>
+  </div>
+  {include="page.footer"}
 </body>
 </html>