]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Merge pull request #515 from ArthurHoaro/template-feeds
authorArthur <arthur@hoa.ro>
Fri, 25 Mar 2016 18:20:55 +0000 (19:20 +0100)
committerArthur <arthur@hoa.ro>
Fri, 25 Mar 2016 18:20:55 +0000 (19:20 +0100)
Refactor RSS feeds generation, and do it through templates

16 files changed:
application/FeedBuilder.php [new file with mode: 0644]
application/LinkDB.php
application/LinkFilter.php
application/Router.php
application/Updater.php
application/Utils.php
index.php
plugins/demo_plugin/demo_plugin.php
tests/FeedBuilderTest.php [new file with mode: 0644]
tests/LinkDBTest.php
tests/LinkFilterTest.php
tests/Updater/UpdaterTest.php
tests/utils/ReferenceLinkDB.php
tpl/configure.html
tpl/feed.atom.html [new file with mode: 0644]
tpl/feed.rss.html [new file with mode: 0644]

diff --git a/application/FeedBuilder.php b/application/FeedBuilder.php
new file mode 100644 (file)
index 0000000..ddefe6c
--- /dev/null
@@ -0,0 +1,279 @@
+<?php
+
+/**
+ * FeedBuilder class.
+ *
+ * Used to build ATOM and RSS feeds data.
+ */
+class FeedBuilder
+{
+    /**
+     * @var string Constant: RSS feed type.
+     */
+    public static $FEED_RSS = 'rss';
+
+    /**
+     * @var string Constant: ATOM feed type.
+     */
+    public static $FEED_ATOM = 'atom';
+
+    /**
+     * @var string Default language if the locale isn't set.
+     */
+    public static $DEFAULT_LANGUAGE = 'en-en';
+
+    /**
+     * @var int Number of links to display in a feed by default.
+     */
+    public static $DEFAULT_NB_LINKS = 50;
+
+    /**
+     * @var LinkDB instance.
+     */
+    protected $linkDB;
+
+    /**
+     * @var string RSS or ATOM feed.
+     */
+    protected $feedType;
+
+    /**
+     * @var array $_SERVER.
+     */
+    protected $serverInfo;
+
+    /**
+     * @var array $_GET.
+     */
+    protected $userInput;
+
+    /**
+     * @var boolean True if the user is currently logged in, false otherwise.
+     */
+    protected $isLoggedIn;
+
+    /**
+     * @var boolean Use permalinks instead of direct links if true.
+     */
+    protected $usePermalinks;
+
+    /**
+     * @var boolean true to hide dates in feeds.
+     */
+    protected $hideDates;
+
+    /**
+     * @var string PubSub hub URL.
+     */
+    protected $pubsubhubUrl;
+
+    /**
+     * @var string server locale.
+     */
+    protected $locale;
+
+    /**
+     * @var DateTime Latest item date.
+     */
+    protected $latestDate;
+
+    /**
+     * Feed constructor.
+     *
+     * @param LinkDB  $linkDB        LinkDB instance.
+     * @param string  $feedType      Type of feed.
+     * @param array   $serverInfo    $_SERVER.
+     * @param array   $userInput     $_GET.
+     * @param boolean $isLoggedIn True if the user is currently logged in, false otherwise.
+     */
+    public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLoggedIn)
+    {
+        $this->linkDB = $linkDB;
+        $this->feedType = $feedType;
+        $this->serverInfo = $serverInfo;
+        $this->userInput = $userInput;
+        $this->isLoggedIn = $isLoggedIn;
+    }
+
+    /**
+     * Build data for feed templates.
+     *
+     * @return array Formatted data for feeds templates.
+     */
+    public function buildData()
+    {
+        // Optionally filter the results:
+        $linksToDisplay = $this->linkDB->filterSearch($this->userInput);
+
+        $nblinksToDisplay = $this->getNbLinks(count($linksToDisplay));
+
+        // Can't use array_keys() because $link is a LinkDB instance and not a real array.
+        $keys = array();
+        foreach ($linksToDisplay as $key => $value) {
+            $keys[] = $key;
+        }
+
+        $pageaddr = escape(index_url($this->serverInfo));
+        $linkDisplayed = array();
+        for ($i = 0; $i < $nblinksToDisplay && $i < count($keys); $i++) {
+            $linkDisplayed[$keys[$i]] = $this->buildItem($linksToDisplay[$keys[$i]], $pageaddr);
+        }
+
+        $data['language'] = $this->getTypeLanguage();
+        $data['pubsubhub_url'] = $this->pubsubhubUrl;
+        $data['last_update'] = $this->getLatestDateFormatted();
+        $data['show_dates'] = !$this->hideDates || $this->isLoggedIn;
+        // Remove leading slash from REQUEST_URI.
+        $data['self_link'] = $pageaddr . escape(ltrim($this->serverInfo['REQUEST_URI'], '/'));
+        $data['index_url'] = $pageaddr;
+        $data['usepermalinks'] = $this->usePermalinks === true;
+        $data['links'] = $linkDisplayed;
+
+        return $data;
+    }
+
+    /**
+     * Build a feed item (one per shaare).
+     *
+     * @param array  $link     Single link array extracted from LinkDB.
+     * @param string $pageaddr Index URL.
+     *
+     * @return array Link array with feed attributes.
+     */
+    protected function buildItem($link, $pageaddr)
+    {
+        $link['guid'] = $pageaddr .'?'. smallHash($link['linkdate']);
+        // Check for both signs of a note: starting with ? and 7 chars long.
+        if ($link['url'][0] === '?' && strlen($link['url']) === 7) {
+            $link['url'] = $pageaddr . $link['url'];
+        }
+        if ($this->usePermalinks === true) {
+            $permalink = '<a href="'. $link['url'] .'" title="Direct link">Direct link</a>';
+        } else {
+            $permalink = '<a href="'. $link['guid'] .'" title="Permalink">Permalink</a>';
+        }
+        $link['description'] = format_description($link['description']) . PHP_EOL .'<br>&#8212; '. $permalink;
+
+        $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
+
+        if ($this->feedType == self::$FEED_RSS) {
+            $link['iso_date'] = $date->format(DateTime::RSS);
+        } else {
+            $link['iso_date'] = $date->format(DateTime::ATOM);
+        }
+
+        // Save the more recent item.
+        if (empty($this->latestDate) || $this->latestDate < $date) {
+            $this->latestDate = $date;
+        }
+
+        $taglist = array_filter(explode(' ', $link['tags']), 'strlen');
+        uasort($taglist, 'strcasecmp');
+        $link['taglist'] = $taglist;
+
+        return $link;
+    }
+
+    /**
+     * Assign PubSub hub URL.
+     *
+     * @param string $pubsubhubUrl PubSub hub url.
+     */
+    public function setPubsubhubUrl($pubsubhubUrl)
+    {
+        $this->pubsubhubUrl = $pubsubhubUrl;
+    }
+
+    /**
+     * Set this to true to use permalinks instead of direct links.
+     *
+     * @param boolean $usePermalinks true to force permalinks.
+     */
+    public function setUsePermalinks($usePermalinks)
+    {
+        $this->usePermalinks = $usePermalinks;
+    }
+
+    /**
+     * Set this to true to hide timestamps in feeds.
+     *
+     * @param boolean $hideDates true to enable.
+     */
+    public function setHideDates($hideDates)
+    {
+        $this->hideDates = $hideDates;
+    }
+
+    /**
+     * Set the locale. Used to show feed language.
+     *
+     * @param string $locale The locale (eg. 'fr_FR.UTF8').
+     */
+    public function setLocale($locale)
+    {
+        $this->locale = strtolower($locale);
+    }
+
+    /**
+     * Get the language according to the feed type, based on the locale:
+     *
+     *   - RSS format: en-us (default: 'en-en').
+     *   - ATOM format: fr (default: 'en').
+     *
+     * @return string The language.
+     */
+    public function getTypeLanguage()
+    {
+        // Use the locale do define the language, if available.
+        if (! empty($this->locale) && preg_match('/^\w{2}[_\-]\w{2}/', $this->locale)) {
+            $length = ($this->feedType == self::$FEED_RSS) ? 5 : 2;
+            return str_replace('_', '-', substr($this->locale, 0, $length));
+        }
+        return ($this->feedType == self::$FEED_RSS) ? 'en-en' : 'en';
+    }
+
+    /**
+     * Format the latest item date found according to the feed type.
+     *
+     * Return an empty string if invalid DateTime is passed.
+     *
+     * @return string Formatted date.
+     */
+    protected function getLatestDateFormatted()
+    {
+        if (empty($this->latestDate) || !$this->latestDate instanceof DateTime) {
+            return '';
+        }
+
+        $type = ($this->feedType == self::$FEED_RSS) ? DateTime::RSS : DateTime::ATOM;
+        return $this->latestDate->format($type);
+    }
+
+    /**
+     * Returns the number of link to display according to 'nb' user input parameter.
+     *
+     * If 'nb' not set or invalid, default value: $DEFAULT_NB_LINKS.
+     * If 'nb' is set to 'all', display all filtered links (max parameter).
+     *
+     * @param int $max maximum number of links to display.
+     *
+     * @return int number of links to display.
+     */
+    public function getNbLinks($max)
+    {
+        if (empty($this->userInput['nb'])) {
+            return self::$DEFAULT_NB_LINKS;
+        }
+
+        if ($this->userInput['nb'] == 'all') {
+            return $max;
+        }
+
+        $intNb = intval($this->userInput['nb']);
+        if (! is_int($intNb) || $intNb == 0) {
+            return self::$DEFAULT_NB_LINKS;
+        }
+
+        return $intNb;
+    }
+}
index 1b505620959c3a6a06061dabd4ea6f982b39f3fd..a62341fc638e2c078e401bdedb58e6aa635e990c 100644 (file)
@@ -341,17 +341,71 @@ You use the community supported version of the original Shaarli project, by Seba
     }
 
     /**
-     * Filter links.
+     * Returns the shaare corresponding to a smallHash.
      *
-     * @param string $type          Type of filter.
-     * @param mixed  $request       Search request, string or array.
+     * @param string $request QUERY_STRING server parameter.
+     *
+     * @return array $filtered array containing permalink data.
+     *
+     * @throws LinkNotFoundException if the smallhash is malformed or doesn't match any link.
+     */
+    public function filterHash($request)
+    {
+        $request = substr($request, 0, 6);
+        $linkFilter = new LinkFilter($this->_links);
+        return $linkFilter->filter(LinkFilter::$FILTER_HASH, $request);
+    }
+
+    /**
+     * Returns the list of articles for a given day.
+     *
+     * @param string $request day to filter. Format: YYYYMMDD.
+     *
+     * @return array list of shaare found.
+     */
+    public function filterDay($request) {
+        $linkFilter = new LinkFilter($this->_links);
+        return $linkFilter->filter(LinkFilter::$FILTER_DAY, $request);
+    }
+
+    /**
+     * Filter links according to search parameters.
+     *
+     * @param array  $filterRequest Search request content. Supported keys:
+     *                                - searchtags: list of tags
+     *                                - searchterm: term search
      * @param bool   $casesensitive Optional: Perform case sensitive filter
      * @param bool   $privateonly   Optional: Returns private links only if true.
      *
-     * @return array filtered links
+     * @return array filtered links, all links if no suitable filter was provided.
      */
-    public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
+    public function filterSearch($filterRequest = array(), $casesensitive = false, $privateonly = false)
     {
+        // Filter link database according to parameters.
+        $searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
+        $searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
+
+        // Search tags + fullsearch.
+        if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) {
+            $type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
+            $request = array($searchtags, $searchterm);
+        }
+        // Search by tags.
+        elseif (! empty($searchtags)) {
+            $type = LinkFilter::$FILTER_TAG;
+            $request = $searchtags;
+        }
+        // Fulltext search.
+        elseif (! empty($searchterm)) {
+            $type = LinkFilter::$FILTER_TEXT;
+            $request = $searchterm;
+        }
+        // Otherwise, display without filtering.
+        else {
+            $type = '';
+            $request = '';
+        }
+
         $linkFilter = new LinkFilter($this->_links);
         return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
     }
index 3fd803cb81f4d79c08538a8fc3a7079d8f0b9644..5e0d801594533174e7bd5aaef188d3eda81dfc4a 100644 (file)
@@ -44,7 +44,7 @@ class LinkFilter
      * Filter links according to parameters.
      *
      * @param string $type          Type of filter (eg. tags, permalink, etc.).
-     * @param string $request       Filter content.
+     * @param mixed  $request       Filter content.
      * @param bool   $casesensitive Optional: Perform case sensitive filter if true.
      * @param bool   $privateonly   Optional: Only returns private links if true.
      *
@@ -110,6 +110,8 @@ class LinkFilter
      * @param string $smallHash permalink hash.
      *
      * @return array $filtered array containing permalink data.
+     *
+     * @throws LinkNotFoundException if the smallhash doesn't match any link.
      */
     private function filterSmallHash($smallHash)
     {
@@ -121,6 +123,11 @@ class LinkFilter
                 return $filtered;
             }
         }
+
+        if (empty($filtered)) {
+            throw new LinkNotFoundException();
+        }
+
         return $filtered;
     }
 
@@ -318,3 +325,8 @@ class LinkFilter
         return array_filter(explode(' ', trim($tagsOut)), 'strlen');
     }
 }
+
+class LinkNotFoundException extends Exception
+{
+    protected $message = 'The link you are trying to reach does not exist or has been deleted.';
+}
index 6185f08e932c742806135767792d6f3c2cf8e7bc..a1e594a0846b529eedb06dd5ee02c1701b2dc095 100644 (file)
@@ -15,6 +15,10 @@ class Router
 
     public static $PAGE_DAILY = 'daily';
 
+    public static $PAGE_FEED_ATOM = 'atom';
+
+    public static $PAGE_FEED_RSS = 'rss';
+
     public static $PAGE_TOOLS = 'tools';
 
     public static $PAGE_CHANGEPASSWORD = 'changepasswd';
@@ -49,7 +53,7 @@ class Router
      * @param array  $get      $_SERVER['GET'].
      * @param bool   $loggedIn true if authenticated user.
      *
-     * @return self::page found.
+     * @return string page found.
      */
     public static function findPage($query, $get, $loggedIn)
     {
@@ -79,6 +83,14 @@ class Router
             return self::$PAGE_DAILY;
         }
 
+        if (startsWith($query, 'do='. self::$PAGE_FEED_ATOM)) {
+            return self::$PAGE_FEED_ATOM;
+        }
+
+        if (startsWith($query, 'do='. self::$PAGE_FEED_RSS)) {
+            return self::$PAGE_FEED_RSS;
+        }
+
         // At this point, only loggedin pages.
         if (!$loggedIn) {
             return self::$PAGE_LINKLIST;
index 773a1ffa1d8187e016e8ccbfb9200a1c5824bc01..58c13c07796ca44ada886b061b0b163e2321e302 100644 (file)
@@ -137,7 +137,7 @@ class Updater
      */
     public function updateMethodRenameDashTags()
     {
-        $linklist = $this->linkDB->filter();
+        $linklist = $this->linkDB->filterSearch();
         foreach ($linklist as $link) {
             $link['tags'] = preg_replace('/(^| )\-/', '$1', $link['tags']);
             $link['tags'] = implode(' ', array_unique(LinkFilter::tagsStrToArray($link['tags'], true)));
index 3d819716316e78f1a884cb662723668a1529560d..5b8ca508576a94b0bafd0a6ee957a99fd3a7588e 100644 (file)
@@ -63,14 +63,22 @@ function endsWith($haystack, $needle, $case=true)
 
 /**
  * Htmlspecialchars wrapper
+ * Support multidimensional array of strings.
  *
- * @param string $str the string to escape.
+ * @param mixed $input Data to escape: a single string or an array of strings.
  *
  * @return string escaped.
  */
-function escape($str)
+function escape($input)
 {
-    return htmlspecialchars($str, ENT_COMPAT, 'UTF-8', false);
+    if (is_array($input)) {
+        $out = array();
+        foreach($input as $key => $value) {
+            $out[$key] = escape($value);
+        }
+        return $out;
+    }
+    return htmlspecialchars($input, ENT_COMPAT, 'UTF-8', false);
 }
 
 /**
@@ -226,7 +234,7 @@ function space2nbsp($text)
  *
  * @return string formatted description.
  */
-function format_description($description, $redirector) {
+function format_description($description, $redirector = false) {
     return nl2br(space2nbsp(text2clickable($description, $redirector)));
 }
 
index 27db10ba147bfbd4f79daa39176a46ff431b974e..74091f37703a63d90c2102da7804b6ab4e524763 100644 (file)
--- a/index.php
+++ b/index.php
@@ -154,6 +154,7 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
 require_once 'application/ApplicationUtils.php';
 require_once 'application/Cache.php';
 require_once 'application/CachedPage.php';
+require_once 'application/FeedBuilder.php';
 require_once 'application/FileUtils.php';
 require_once 'application/HttpUtils.php';
 require_once 'application/LinkDB.php';
@@ -637,6 +638,29 @@ class pageBuilder
         $this->tpl->assign($what,$where);
     }
 
+    /**
+     * Assign an array of data to the template builder.
+     *
+     * @param array $data Data to assign.
+     *
+     * @return false if invalid data.
+     */
+    public function assignAll($data)
+    {
+        // Lazy initialization
+        if ($this->tpl === false) {
+            $this->initialize();
+        }
+
+        if (empty($data) || !is_array($data)){
+            return false;
+        }
+
+        foreach ($data as $key => $value) {
+            $this->assign($key, $value);
+        }
+    }
+
     // Render a specific page (using a template).
     // e.g. pb.renderPage('picwall')
     public function renderPage($page)
@@ -658,232 +682,6 @@ class pageBuilder
     }
 }
 
-// ------------------------------------------------------------------------------------------
-// Output the last N links in RSS 2.0 format.
-function showRSS()
-{
-    header('Content-Type: application/rss+xml; charset=utf-8');
-
-    // $usepermalink : If true, use permalink instead of final link.
-    // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks
-    // Also enabled through a config option
-    $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
-
-    // Cache system
-    $query = $_SERVER["QUERY_STRING"];
-    $cache = new CachedPage(
-        $GLOBALS['config']['PAGECACHE'],
-        page_url($_SERVER),
-        startsWith($query,'do=rss') && !isLoggedIn()
-    );
-    $cached = $cache->cachedVersion();
-    if (! empty($cached)) {
-        echo $cached;
-        exit;
-    }
-
-    // If cached was not found (or not usable), then read the database and build the response:
-    $LINKSDB = new LinkDB(
-        $GLOBALS['config']['DATASTORE'],
-        isLoggedIn(),
-        $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
-        $GLOBALS['redirector']
-    );
-    // Read links from database (and filter private links if user it not logged in).
-
-    // Optionally filter the results:
-    $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
-    $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
-    if (! empty($searchtags) && ! empty($searchterm)) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
-            array($searchtags, $searchterm)
-        );
-    }
-    elseif ($searchtags) {
-        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
-    }
-    elseif ($searchterm) {
-        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
-    }
-    else {
-        $linksToDisplay = $LINKSDB;
-    }
-
-    $nblinksToDisplay = 50;  // Number of links to display.
-    // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    if (!empty($_GET['nb'])) {
-        $nblinksToDisplay = $_GET['nb'] == 'all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1);
-    }
-
-    $pageaddr = escape(index_url($_SERVER));
-    echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">';
-    echo '<channel><title>'.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>';
-    echo '<description>Shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n\n";
-    if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
-    {
-        echo '<!-- PubSubHubbub Discovery -->';
-        echo '<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" xmlns="http://www.w3.org/2005/Atom" />';
-        echo '<link rel="self" href="'.$pageaddr.'?do=rss" xmlns="http://www.w3.org/2005/Atom" />';
-        echo '<!-- End Of PubSubHubbub Discovery -->';
-    }
-    $i=0;
-    $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; }  // No, I can't use array_keys().
-    while ($i<$nblinksToDisplay && $i<count($keys))
-    {
-        $link = $linksToDisplay[$keys[$i]];
-        $guid = $pageaddr.'?'.smallHash($link['linkdate']);
-        $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
-        $absurl = $link['url'];
-        if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl;  // make permalink URL absolute
-        if ($usepermalinks===true)
-            echo '<item><title>'.$link['title'].'</title><guid isPermaLink="true">'.$guid.'</guid><link>'.$guid.'</link>';
-        else
-            echo '<item><title>'.$link['title'].'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>';
-        if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) {
-            echo '<pubDate>'.escape($date->format(DateTime::RSS))."</pubDate>\n";
-        }
-        if ($link['tags']!='') // Adding tags to each RSS entry (as mentioned in RSS specification)
-        {
-            foreach(explode(' ',$link['tags']) as $tag) { echo '<category domain="'.$pageaddr.'">'.$tag.'</category>'."\n"; }
-        }
-
-        // Add permalink in description
-        $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
-        // If user wants permalinks first, put the final link in description
-        if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
-        if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
-        echo '<description><![CDATA['.
-            format_description($link['description'], $GLOBALS['redirector']) .
-            $descriptionlink . ']]></description>' . "\n</item>\n";
-        $i++;
-    }
-    echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
-
-    $cache->cache(ob_get_contents());
-    ob_end_flush();
-    exit;
-}
-
-// ------------------------------------------------------------------------------------------
-// Output the last N links in ATOM format.
-function showATOM()
-{
-    header('Content-Type: application/atom+xml; charset=utf-8');
-
-    // $usepermalink : If true, use permalink instead of final link.
-    // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks
-    $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
-
-    // Cache system
-    $query = $_SERVER["QUERY_STRING"];
-    $cache = new CachedPage(
-        $GLOBALS['config']['PAGECACHE'],
-        page_url($_SERVER),
-        startsWith($query,'do=atom') && !isLoggedIn()
-    );
-    $cached = $cache->cachedVersion();
-    if (!empty($cached)) {
-        echo $cached;
-        exit;
-    }
-
-    // If cached was not found (or not usable), then read the database and build the response:
-    // Read links from database (and filter private links if used it not logged in).
-    $LINKSDB = new LinkDB(
-        $GLOBALS['config']['DATASTORE'],
-        isLoggedIn(),
-        $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
-        $GLOBALS['redirector']
-    );
-
-    // Optionally filter the results:
-    $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
-    $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
-    if (! empty($searchtags) && ! empty($searchterm)) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
-            array($searchtags, $searchterm)
-        );
-    }
-    elseif ($searchtags) {
-        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
-    }
-    elseif ($searchterm) {
-        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
-    }
-    else {
-        $linksToDisplay = $LINKSDB;
-    }
-
-    $nblinksToDisplay = 50;  // Number of links to display.
-    // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    if (!empty($_GET['nb'])) {
-        $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max(intval($_GET['nb']), 1);
-    }
-
-    $pageaddr=escape(index_url($_SERVER));
-    $latestDate = '';
-    $entries='';
-    $i=0;
-    $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; }  // No, I can't use array_keys().
-    while ($i<$nblinksToDisplay && $i<count($keys))
-    {
-        $link = $linksToDisplay[$keys[$i]];
-        $guid = $pageaddr.'?'.smallHash($link['linkdate']);
-        $date = DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, $link['linkdate']);
-        $iso8601date = $date->format(DateTime::ISO8601);
-        $latestDate = max($latestDate, $iso8601date);
-        $absurl = $link['url'];
-        if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl;  // make permalink URL absolute
-        $entries.='<entry><title>'.$link['title'].'</title>';
-        if ($usepermalinks===true)
-            $entries.='<link href="'.$guid.'" /><id>'.$guid.'</id>';
-        else
-            $entries.='<link href="'.$absurl.'" /><id>'.$guid.'</id>';
-
-        if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) {
-            $entries.='<updated>'.escape($iso8601date).'</updated>';
-        }
-
-        // Add permalink in description
-        $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
-        // If user wants permalinks first, put the final link in description
-        if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
-        if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
-
-        $entries .= '<content type="html"><![CDATA['.
-            format_description($link['description'], $GLOBALS['redirector']) .
-            $descriptionlink . "]]></content>\n";
-        if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification)
-        {
-            foreach(explode(' ',$link['tags']) as $tag)
-                { $entries.='<category scheme="'.$pageaddr.'" term="'.$tag.'" />'."\n"; }
-        }
-        $entries.="</entry>\n";
-        $i++;
-    }
-    $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
-    $feed.='<title>'.$GLOBALS['title'].'</title>';
-    if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.escape($latestDate).'</updated>';
-    $feed.='<link rel="self" href="'.escape(server_url($_SERVER).$_SERVER["REQUEST_URI"]).'" />';
-    if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
-    {
-        $feed.='<!-- PubSubHubbub Discovery -->';
-        $feed.='<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" />';
-        $feed.='<!-- End Of PubSubHubbub Discovery -->';
-    }
-    $feed.='<author><name>'.$pageaddr.'</name><uri>'.$pageaddr.'</uri></author>';
-    $feed.='<id>'.$pageaddr.'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
-    $feed.=$entries;
-    $feed.='</feed><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
-    echo $feed;
-
-    $cache->cache(ob_get_contents());
-    ob_end_flush();
-    exit;
-}
-
 // ------------------------------------------------------------------------------------------
 // Daily RSS feed: 1 RSS entry per day giving all the links on that day.
 // Gives the last 7 days (which have links).
@@ -1018,7 +816,7 @@ function showDaily($pageBuilder)
     }
 
     try {
-        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_DAY, $day);
+        $linksToDisplay = $LINKSDB->filterDay($day);
     } catch (Exception $exc) {
         error_log($exc);
         $linksToDisplay = array();
@@ -1164,24 +962,7 @@ function renderPage()
     if ($targetPage == Router::$PAGE_PICWALL)
     {
         // Optionally filter the results:
-        $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
-        $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
-        if (! empty($searchtags) && ! empty($searchterm)) {
-            $links = $LINKSDB->filter(
-                LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
-                array($searchtags, $searchterm)
-            );
-        }
-        elseif ($searchtags) {
-            $links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
-        }
-        elseif ($searchterm) {
-            $links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
-        }
-        else {
-            $links = $LINKSDB;
-        }
-
+        $links = $LINKSDB->filterSearch($_GET);
         $linksToDisplay = array();
 
         // Get only links which have a thumbnail.
@@ -1260,6 +1041,49 @@ function renderPage()
         showDaily($PAGE);
     }
 
+    // ATOM and RSS feed.
+    if ($targetPage == Router::$PAGE_FEED_ATOM || $targetPage == Router::$PAGE_FEED_RSS) {
+        $feedType = $targetPage == Router::$PAGE_FEED_RSS ? FeedBuilder::$FEED_RSS : FeedBuilder::$FEED_ATOM;
+        header('Content-Type: application/'. $feedType .'+xml; charset=utf-8');
+
+        // Cache system
+        $query = $_SERVER['QUERY_STRING'];
+        $cache = new CachedPage(
+            $GLOBALS['config']['PAGECACHE'],
+            page_url($_SERVER),
+            startsWith($query,'do='. $targetPage) && !isLoggedIn()
+        );
+        $cached = $cache->cachedVersion();
+        if (false && !empty($cached)) {
+            echo $cached;
+            exit;
+        }
+
+        // Generate data.
+        $feedGenerator = new FeedBuilder($LINKSDB, $feedType, $_SERVER, $_GET, isLoggedIn());
+        $feedGenerator->setLocale(strtolower(setlocale(LC_COLLATE, 0)));
+        $feedGenerator->setHideDates($GLOBALS['config']['HIDE_TIMESTAMPS'] && !isLoggedIn());
+        $feedGenerator->setUsePermalinks(isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS']);
+        if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) {
+            $feedGenerator->setPubsubhubUrl($GLOBALS['config']['PUBSUBHUB_URL']);
+        }
+        $data = $feedGenerator->buildData();
+
+        // Process plugin hook.
+        $pluginManager = PluginManager::getInstance();
+        $pluginManager->executeHooks('render_feed', $data, array(
+            'loggedin' => isLoggedIn(),
+            'target' => $targetPage,
+        ));
+
+        // Render the template.
+        $PAGE->assignAll($data);
+        $PAGE->renderPage('feed.'. $feedType);
+        $cache->cache(ob_get_contents());
+        ob_end_flush();
+        exit;
+    }
+
     // Display openseach plugin (XML)
     if ($targetPage == Router::$PAGE_OPENSEARCH) {
         header('Content-Type: application/xml; charset=utf-8');
@@ -1511,9 +1335,9 @@ function renderPage()
 
         // Delete a tag:
         if (isset($_POST['deletetag']) && !empty($_POST['fromtag'])) {
-            $needle=trim($_POST['fromtag']);
+            $needle = trim($_POST['fromtag']);
             // True for case-sensitive tag search.
-            $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
+            $linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true);
             foreach($linksToAlter as $key=>$value)
             {
                 $tags = explode(' ',trim($value['tags']));
@@ -1528,9 +1352,9 @@ function renderPage()
 
         // Rename a tag:
         if (isset($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) {
-            $needle=trim($_POST['fromtag']);
+            $needle = trim($_POST['fromtag']);
             // True for case-sensitive tag search.
-            $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
+            $linksToAlter = $LINKSDB->filterSearch(array('searchtags' => $needle), true);
             foreach($linksToAlter as $key=>$value)
             {
                 $tags = explode(' ',trim($value['tags']));
@@ -1966,60 +1790,32 @@ function importFile()
     }
 }
 
-// -----------------------------------------------------------------------------------------------
-// Template for the list of links (<div id="linklist">)
-// This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
+/**
+ * Template for the list of links (<div id="linklist">)
+ * This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
+ *
+ * @param pageBuilder $PAGE    pageBuilder instance.
+ * @param LinkDB      $LINKSDB LinkDB instance.
+ */
 function buildLinkList($PAGE,$LINKSDB)
 {
-    // Filter link database according to parameters.
+    // Used in templates
     $searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
-    $searchterm = !empty($_GET['searchterm']) ? escape(trim($_GET['searchterm'])) : '';
-    $privateonly = !empty($_SESSION['privateonly']) ? true : false;
-
-    // Search tags + fullsearch.
-    if (! empty($searchtags) && ! empty($searchterm)) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
-            array($searchtags, $searchterm),
-            false,
-            $privateonly
-        );
-    }
-    // Search by tags.
-    elseif (! empty($searchtags)) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_TAG,
-            $searchtags,
-            false,
-            $privateonly
-        );
-    }
-    // Fulltext search.
-    elseif (! empty($searchterm)) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_TEXT,
-            $searchterm,
-            false,
-            $privateonly
-        );
-    }
-    // Detect smallHashes in URL.
-    elseif (! empty($_SERVER['QUERY_STRING'])
-        && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])
-    ) {
-        $linksToDisplay = $LINKSDB->filter(
-            LinkFilter::$FILTER_HASH,
-            substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6)
-        );
+    $searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
 
-        if (count($linksToDisplay) == 0) {
-            $PAGE->render404('The link you are trying to reach does not exist or has been deleted.');
+    // Smallhash filter
+    if (! empty($_SERVER['QUERY_STRING'])
+        && preg_match('/^[a-zA-Z0-9-_@]{6}($|&|#)/', $_SERVER['QUERY_STRING'])) {
+        try {
+            $linksToDisplay = $LINKSDB->filterHash($_SERVER['QUERY_STRING']);
+        } catch (LinkNotFoundException $e) {
+            $PAGE->render404($e->getMessage());
             exit;
         }
-    }
-    // Otherwise, display without filtering.
-    else {
-        $linksToDisplay = $LINKSDB->filter('', '', false, $privateonly);
+    } else {
+        // Filter links according search parameters.
+        $privateonly = !empty($_SESSION['privateonly']);
+        $linksToDisplay = $LINKSDB->filterSearch($_GET, false, $privateonly);
     }
 
     // ---- Handle paging.
@@ -2584,8 +2380,6 @@ function resizeImage($filepath)
 }
 
 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; }  // Thumbnail generation/cache does not need the link database.
-if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
-if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
 if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=dailyrss')) { showDailyRSS(); exit; }
 if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE'];
 renderPage();
index f5f028e023c68a583ff12c5e7c0837006655492b..18834e5331d91cc9d217c18a284056097bcc23db 100644 (file)
@@ -322,4 +322,29 @@ function hook_demo_plugin_delete_link($data)
     if (strpos($data['url'], 'youtube.com') !== false) {
         exit('You can not delete a YouTube link. Don\'t ask.');
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Execute render_feed hook.
+ * Called with ATOM and RSS feed.
+ *
+ * Special data keys:
+ *   - _PAGE_: current page
+ *   - _LOGGEDIN_: true/false
+ *
+ * @param array $data data passed to plugin
+ *
+ * @return array altered $data.
+ */
+function hook_demo_plugin_render_feed($data)
+{
+    foreach ($data['links'] as &$link) {
+        if ($data['_PAGE_'] == Router::$PAGE_FEED_ATOM) {
+            $link['description'] .= ' - ATOM Feed' ;
+        }
+        elseif ($data['_PAGE_'] == Router::$PAGE_FEED_RSS) {
+            $link['description'] .= ' - RSS Feed';
+        }
+    }
+    return $data;
+}
diff --git a/tests/FeedBuilderTest.php b/tests/FeedBuilderTest.php
new file mode 100644 (file)
index 0000000..069b158
--- /dev/null
@@ -0,0 +1,212 @@
+<?php
+
+require_once 'application/FeedBuilder.php';
+require_once 'application/LinkDB.php';
+
+/**
+ * FeedBuilderTest class.
+ *
+ * Unit tests for FeedBuilder.
+ */
+class FeedBuilderTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var string locale Basque (Spain).
+     */
+    public static $LOCALE = 'eu_ES';
+
+    /**
+     * @var string language in RSS format.
+     */
+    public static $RSS_LANGUAGE = 'eu-es';
+
+    /**
+     * @var string language in ATOM format.
+     */
+    public static $ATOM_LANGUAGUE = 'eu';
+
+    protected static $testDatastore = 'sandbox/datastore.php';
+
+    public static $linkDB;
+
+    public static $serverInfo;
+
+    /**
+     * Called before every test method.
+     */
+    public static function setUpBeforeClass()
+    {
+        $refLinkDB = new ReferenceLinkDB();
+        $refLinkDB->write(self::$testDatastore);
+        self::$linkDB = new LinkDB(self::$testDatastore, true, false);
+        self::$serverInfo = array(
+            'HTTPS' => 'Off',
+            'SERVER_NAME' => 'host.tld',
+            'SERVER_PORT' => '80',
+            'SCRIPT_NAME' => '/index.php',
+            'REQUEST_URI' => '/index.php?do=feed',
+        );
+    }
+
+    /**
+     * Test GetTypeLanguage().
+     */
+    public function testGetTypeLanguage()
+    {
+        $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $this->assertEquals(self::$ATOM_LANGUAGUE, $feedBuilder->getTypeLanguage());
+        $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $this->assertEquals(self::$RSS_LANGUAGE, $feedBuilder->getTypeLanguage());
+        $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_ATOM, null, null, false);
+        $this->assertEquals('en', $feedBuilder->getTypeLanguage());
+        $feedBuilder = new FeedBuilder(null, FeedBuilder::$FEED_RSS, null, null, false);
+        $this->assertEquals('en-en', $feedBuilder->getTypeLanguage());
+    }
+
+    /**
+     * Test buildData with RSS feed.
+     */
+    public function testRSSBuildData()
+    {
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_RSS, self::$serverInfo, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $data = $feedBuilder->buildData();
+        // Test headers (RSS)
+        $this->assertEquals(self::$RSS_LANGUAGE, $data['language']);
+        $this->assertEmpty($data['pubsubhub_url']);
+        $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $data['last_update']);
+        $this->assertEquals(true, $data['show_dates']);
+        $this->assertEquals('http://host.tld/index.php?do=feed', $data['self_link']);
+        $this->assertEquals('http://host.tld/', $data['index_url']);
+        $this->assertFalse($data['usepermalinks']);
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+
+        // Test first link (note link)
+        $link = array_shift($data['links']);
+        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
+        $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
+        $this->assertEquals('Tue, 10 Mar 2015 11:46:51 +0100', $link['iso_date']);
+        $this->assertContains('Stallman has a beard', $link['description']);
+        $this->assertContains('Permalink', $link['description']);
+        $this->assertContains('http://host.tld/?WDWyig', $link['description']);
+        $this->assertEquals(1, count($link['taglist']));
+        $this->assertEquals('stuff', $link['taglist'][0]);
+
+        // Test URL with external link.
+        $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $data['links']['20150310_114633']['url']);
+
+        // Test multitags.
+        $this->assertEquals(5, count($data['links']['20141125_084734']['taglist']));
+        $this->assertEquals('css', $data['links']['20141125_084734']['taglist'][0]);
+    }
+
+    /**
+     * Test buildData with ATOM feed (test only specific to ATOM).
+     */
+    public function testAtomBuildData()
+    {
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+        $link = array_shift($data['links']);
+        $this->assertEquals('2015-03-10T11:46:51+01:00', $link['iso_date']);
+    }
+
+    /**
+     * Test buildData with search criteria.
+     */
+    public function testBuildDataFiltered()
+    {
+        $criteria = array(
+            'searchtags' => 'stuff',
+            'searchterm' => 'beard',
+        );
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(1, count($data['links']));
+        $link = array_shift($data['links']);
+        $this->assertEquals('20150310_114651', $link['linkdate']);
+    }
+
+    /**
+     * Test buildData with nb limit.
+     */
+    public function testBuildDataCount()
+    {
+        $criteria = array(
+            'nb' => '1',
+        );
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, $criteria, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(1, count($data['links']));
+        $link = array_shift($data['links']);
+        $this->assertEquals('20150310_114651', $link['linkdate']);
+    }
+
+    /**
+     * Test buildData with permalinks on.
+     */
+    public function testBuildDataPermalinks()
+    {
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $feedBuilder->setUsePermalinks(true);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+        $this->assertTrue($data['usepermalinks']);
+        // First link is a permalink
+        $link = array_shift($data['links']);
+        $this->assertEquals('20150310_114651', $link['linkdate']);
+        $this->assertEquals('http://host.tld/?WDWyig', $link['guid']);
+        $this->assertEquals('http://host.tld/?WDWyig', $link['url']);
+        $this->assertContains('Direct link', $link['description']);
+        $this->assertContains('http://host.tld/?WDWyig', $link['description']);
+        // Second link is a direct link
+        $link = array_shift($data['links']);
+        $this->assertEquals('20150310_114633', $link['linkdate']);
+        $this->assertEquals('http://host.tld/?kLHmZg', $link['guid']);
+        $this->assertEquals('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['url']);
+        $this->assertContains('Direct link', $link['description']);
+        $this->assertContains('https://static.fsf.org/nosvn/faif-2.0.pdf', $link['description']);
+    }
+
+    /**
+     * Test buildData with hide dates settings.
+     */
+    public function testBuildDataHideDates()
+    {
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $feedBuilder->setHideDates(true);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+        $this->assertFalse($data['show_dates']);
+
+        // Show dates while logged in
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, true);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $feedBuilder->setHideDates(true);
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+        $this->assertTrue($data['show_dates']);
+    }
+
+    /**
+     * Test buildData with hide dates settings.
+     */
+    public function testBuildDataPubsubhub()
+    {
+        $feedBuilder = new FeedBuilder(self::$linkDB, FeedBuilder::$FEED_ATOM, self::$serverInfo, null, false);
+        $feedBuilder->setLocale(self::$LOCALE);
+        $feedBuilder->setPubsubhubUrl('http://pubsubhub.io');
+        $data = $feedBuilder->buildData();
+        $this->assertEquals(ReferenceLinkDB::$NB_LINKS_TOTAL, count($data['links']));
+        $this->assertEquals('http://pubsubhub.io', $data['pubsubhub_url']);
+    }
+}
index b6a273b3338ed30477664f6ea0b5880e782c618a..52d31400b5a0716ebce5c2ab51ec3ab008af4c38 100644 (file)
@@ -17,8 +17,20 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
 {
     // datastore to test write operations
     protected static $testDatastore = 'sandbox/datastore.php';
+
+    /**
+     * @var ReferenceLinkDB instance.
+     */
     protected static $refDB = null;
+
+    /**
+     * @var LinkDB public LinkDB instance.
+     */
     protected static $publicLinkDB = null;
+
+    /**
+     * @var LinkDB private LinkDB instance.
+     */
     protected static $privateLinkDB = null;
 
     /**
@@ -335,9 +347,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     public function testFilterString()
     {
         $tags = 'dev cartoon';
+        $request = array('searchtags' => $tags);
         $this->assertEquals(
             2,
-            count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
+            count(self::$privateLinkDB->filterSearch($request, true, false))
         );
     }
 
@@ -347,9 +360,10 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     public function testFilterArray()
     {
         $tags = array('dev', 'cartoon');
+        $request = array('searchtags' => $tags);
         $this->assertEquals(
             2,
-            count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
+            count(self::$privateLinkDB->filterSearch($request, true, false))
         );
     }
 
@@ -360,14 +374,48 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     public function testHiddenTags()
     {
         $tags = '.hidden';
+        $request = array('searchtags' => $tags);
         $this->assertEquals(
             1,
-            count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
+            count(self::$privateLinkDB->filterSearch($request, true, false))
         );
 
         $this->assertEquals(
             0,
-            count(self::$publicLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
+            count(self::$publicLinkDB->filterSearch($request, true, false))
         );
     }
+
+    /**
+     * Test filterHash() with a valid smallhash.
+     */
+    public function testFilterHashValid()
+    {
+        $request = smallHash('20150310_114651');
+        $this->assertEquals(
+            1,
+            count(self::$publicLinkDB->filterHash($request))
+        );
+    }
+
+    /**
+     * Test filterHash() with an invalid smallhash.
+     *
+     * @expectedException LinkNotFoundException
+     */
+    public function testFilterHashInValid1()
+    {
+        $request = 'blabla';
+        self::$publicLinkDB->filterHash($request);
+    }
+
+    /**
+     * Test filterHash() with an empty smallhash.
+     *
+     * @expectedException LinkNotFoundException
+     */
+    public function testFilterHashInValid()
+    {
+        self::$publicLinkDB->filterHash('');
+    }
 }
index ef1cc10a9789c78ea80dc85ee375eeaca17a5f0f..1620bb78e91ee945b31deffb3fde1dd32bb5b033 100644 (file)
@@ -12,8 +12,6 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
      */
     protected static $linkFilter;
 
-    protected static $NB_LINKS_REFDB = 7;
-
     /**
      * Instanciate linkFilter with ReferenceLinkDB data.
      */
@@ -29,7 +27,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
     public function testFilter()
     {
         $this->assertEquals(
-            self::$NB_LINKS_REFDB,
+            ReferenceLinkDB::$NB_LINKS_TOTAL,
             count(self::$linkFilter->filter('', ''))
         );
 
@@ -40,12 +38,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
         );
 
         $this->assertEquals(
-            self::$NB_LINKS_REFDB,
+            ReferenceLinkDB::$NB_LINKS_TOTAL,
             count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, ''))
         );
 
         $this->assertEquals(
-            self::$NB_LINKS_REFDB,
+            ReferenceLinkDB::$NB_LINKS_TOTAL,
             count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, ''))
         );
     }
@@ -167,13 +165,12 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
 
     /**
      * No link for this hash
+     *
+     * @expectedException LinkNotFoundException
      */
     public function testFilterUnknownSmallHash()
     {
-        $this->assertEquals(
-            0,
-            count(self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah'))
-        );
+        self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah');
     }
 
     /**
@@ -383,7 +380,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
             ))
         );
         $this->assertEquals(
-            self::$NB_LINKS_REFDB,
+            ReferenceLinkDB::$NB_LINKS_TOTAL,
             count(self::$linkFilter->filter(
                 LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
                 ''
index d865066b99f9aa923bafbee30f9ce90b264de9d8..a29d90677dc0d7b611fd078b43e03e841f41bed2 100644 (file)
@@ -236,9 +236,9 @@ class UpdaterTest extends PHPUnit_Framework_TestCase
         $refDB = new ReferenceLinkDB();
         $refDB->write(self::$testDatastore);
         $linkDB = new LinkDB(self::$testDatastore, true, false);
-        $this->assertEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude'));
+        $this->assertEmpty($linkDB->filterSearch(array('searchtags' => 'exclude')));
         $updater = new Updater(array(), self::$configFields, $linkDB, true);
         $updater->updateMethodRenameDashTags();
-        $this->assertNotEmpty($linkDB->filter(LinkFilter::$FILTER_TAG, 'exclude'));
+        $this->assertNotEmpty($linkDB->filterSearch(array('searchtags' =>  'exclude')));
     }
 }
index 61faef0575dcdd75faea3e003c01617e7f01bf03..dc4f5dfa98ccb7e1fbd28decd67417ca93d46dfa 100644 (file)
@@ -4,6 +4,8 @@
  */
 class ReferenceLinkDB
 {
+    public static $NB_LINKS_TOTAL = 7;
+
     private $_links = array();
     private $_publicCount = 0;
     private $_privateCount = 0;
@@ -13,6 +15,15 @@ class ReferenceLinkDB
      */
     function __construct()
     {
+        $this->addLink(
+            'Link title: @website',
+            '?WDWyig',
+            'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
+            0,
+            '20150310_114651',
+            'stuff'
+        );
+
         $this->addLink(
             'Free as in Freedom 2.0 @website',
             'https://static.fsf.org/nosvn/faif-2.0.pdf',
@@ -22,15 +33,6 @@ class ReferenceLinkDB
             'free gnu software stallman -exclude stuff'
         );
 
-        $this->addLink(
-            'Link title: @website',
-            'local',
-            'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
-            0,
-            '20150310_114651',
-            'stuff'
-        );
-
         $this->addLink(
             'MediaGoblin',
             'http://mediagoblin.org/',
index 9c725a5136657bfa5ee9b5a990f0142a4e35057c..77c8b7d9d41c3b558624e18d8fd09b451cbaf07d 100644 (file)
                <input type="checkbox" name="privateLinkByDefault" id="privateLinkByDefault" {if="!empty($GLOBALS['privateLinkByDefault'])"}checked{/if}/><label for="privateLinkByDefault">&nbsp;All new links are private by default</label></td>
         </tr>
         <tr>
-               <td valign="top"><b>Enable RSS Permalinks</b></td>
+               <td valign="top"><b>RSS direct links</b></td>
                <td>
-                       <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/><label for="enableRssPermalinks">&nbsp;Switches the RSS feed URLs between full URLs and shortlinks. Enabling it will show a permalink in the description, and the feed item will be linked to the absolute URL. Disabling it swaps this behaviour around (permalink in title and link in description). RSS Permalinks are currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}</b></label>
+                       <input type="checkbox" name="enableRssPermalinks" id="enableRssPermalinks" {if="!empty($GLOBALS['config']['ENABLE_RSS_PERMALINKS'])"}checked{/if}/>
+              <label for="enableRssPermalinks">
+                  &nbsp;Disable it to use permalinks in RSS feed instead of direct links to your shaared links. Currently <b>{if="$GLOBALS['config']['ENABLE_RSS_PERMALINKS']"}enabled{else}disabled{/if}.</b>
+              </label>
                </td>
         </tr>
         <tr>
diff --git a/tpl/feed.atom.html b/tpl/feed.atom.html
new file mode 100644 (file)
index 0000000..2ebb162
--- /dev/null
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title>{$pagetitle}</title>
+  <subtitle>Shaared links</subtitle>
+  {if="$show_dates"}
+    <updated>{$last_update}</updated>
+  {/if}
+  <link rel="self" href="{$self_link}#" />
+  {if="!empty($pubsubhub_url)"}
+    <!-- PubSubHubbub Discovery -->
+    <link rel="hub" href="{$pubsubhub_url}#" />
+    <!-- End Of PubSubHubbub Discovery -->
+  {/if}
+  <author>
+    <name>{$index_url}</name>
+    <uri>{$index_url}</uri>
+  </author>
+  <id>{$index_url}</id>
+  <generator>Shaarli</generator>
+  {loop="links"}
+    <entry>
+      <title>{$value.title}</title>
+      {if="$usepermalinks"}
+        <link href="{$value.guid}#" />
+      {else}
+        <link href="{$value.url}#" />
+      {/if}
+      <id>{$value.guid}</id>
+      {if="$show_dates"}
+        <updated>{$value.iso_date}</updated>
+      {/if}
+      <content type="html" xml:lang="{$language}">
+        <![CDATA[{$value.description}]]>
+      </content>
+      {loop="$value.taglist"}
+        <category scheme="{$index_url}?searchtags=" term="{$value|strtolower}" label="{$value}" />
+      {/loop}
+    </entry>
+  {/loop}
+</feed>
diff --git a/tpl/feed.rss.html b/tpl/feed.rss.html
new file mode 100644 (file)
index 0000000..26de7f1
--- /dev/null
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
+  <channel>
+    <title>{$pagetitle}</title>
+    <link>{$index_url}</link>
+    <description>Shaared links</description>
+    <language>{$language}</language>
+    <copyright>{$index_url}</copyright>
+    <generator>Shaarli</generator>
+    <atom:link rel="self" href="{$self_link}"  />
+    {if="!empty($pubsubhub_url)"}
+      <!-- PubSubHubbub Discovery -->
+      <atom:link rel="hub" href="{$pubsubhub_url}" />
+    {/if}
+    {loop="links"}
+      <item>
+        <title>{$value.title}</title>
+        <guid isPermaLink="{if="$usepermalinks"}true{else}false{/if}">{$value.guid}</guid>
+        {if="$usepermalinks"}
+          <link>{$value.guid}</link>
+        {else}
+          <link>{$value.url}</link>
+        {/if}
+        {if="$show_dates"}
+          <pubDate>{$value.iso_date}</pubDate>
+        {/if}
+        <description><![CDATA[{$value.description}]]></description>
+        {loop="$value.taglist"}
+          <category domain="{$index_url}?searchtags=">{$value}</category>
+        {/loop}
+      </item>
+    {/loop}
+  </channel>
+</rss>