]> git.immae.eu Git - github/shaarli/Shaarli.git/commitdiff
Link filter refactoring
authorArthurHoaro <arthur@hoa.ro>
Sun, 27 Dec 2015 09:08:20 +0000 (10:08 +0100)
committerArthurHoaro <arthur@hoa.ro>
Wed, 6 Jan 2016 18:53:04 +0000 (19:53 +0100)
  * introduce class LinkFilter to handle link filter operation (and lighten LinkDB).
  * handle 'private only' in filtering.
  * update template to prefill search fields with current search terms.
  * coding style.
  * unit test (mostly move from LinkDB to LinkFilter).

PS: preparation for #358 #315 and 'AND' search.

application/LinkDB.php
application/LinkFilter.php [new file with mode: 0644]
application/Utils.php
index.php
tests/LinkDBTest.php
tests/LinkFilterTest.php [new file with mode: 0644]
tests/utils/ReferenceLinkDB.php
tpl/linklist.html

index 51fa926df328b19646053c3741b836db1eb3727e..be7d9016fc1eb1d0a00cfab2d41349adcca2d9d6 100644 (file)
@@ -62,6 +62,11 @@ class LinkDB implements Iterator, Countable, ArrayAccess
     // link redirector set in user settings.
     private $_redirector;
 
+    /**
+     * @var LinkFilter instance.
+     */
+    private $linkFilter;
+
     /**
      * Creates a new LinkDB
      *
@@ -80,6 +85,7 @@ class LinkDB implements Iterator, Countable, ArrayAccess
         $this->_redirector = $redirector;
         $this->_checkDB();
         $this->_readDB();
+        $this->linkFilter = new LinkFilter($this->_links);
     }
 
     /**
@@ -334,114 +340,18 @@ You use the community supported version of the original Shaarli project, by Seba
     }
 
     /**
-     * Returns the list of links corresponding to a full-text search
+     * Filter links.
      *
-     * Searches:
-     *  - in the URLs, title and description;
-     *  - are case-insensitive.
-     *
-     * Example:
-     *    print_r($mydb->filterFulltext('hollandais'));
-     *
-     * mb_convert_case($val, MB_CASE_LOWER, 'UTF-8')
-     *  - allows to perform searches on Unicode text
-     *  - see https://github.com/shaarli/Shaarli/issues/75 for examples
-     */
-    public function filterFulltext($searchterms)
-    {
-        // FIXME: explode(' ',$searchterms) and perform a AND search.
-        // FIXME: accept double-quotes to search for a string "as is"?
-        $filtered = array();
-        $search = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
-        $keys = array('title', 'description', 'url', 'tags');
-
-        foreach ($this->_links as $link) {
-            $found = false;
-
-            foreach ($keys as $key) {
-                if (strpos(mb_convert_case($link[$key], MB_CASE_LOWER, 'UTF-8'),
-                           $search) !== false) {
-                    $found = true;
-                }
-            }
-
-            if ($found) {
-                $filtered[$link['linkdate']] = $link;
-            }
-        }
-        krsort($filtered);
-        return $filtered;
-    }
-
-    /**
-     * Returns the list of links associated with a given list of tags
+     * @param string $type          Type of filter.
+     * @param mixed  $request       Search request, string or array.
+     * @param bool   $casesensitive Optional: Perform case sensitive filter
+     * @param bool   $privateonly   Optional: Returns private links only if true.
      *
-     * You can specify one or more tags, separated by space or a comma, e.g.
-     *  print_r($mydb->filterTags('linux programming'));
+     * @return array filtered links
      */
-    public function filterTags($tags, $casesensitive=false)
-    {
-        // Same as above, we use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
-        // FIXME: is $casesensitive ever true?
-        $t = str_replace(
-            ',', ' ',
-            ($casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8'))
-        );
-
-        $searchtags = explode(' ', $t);
-        $filtered = array();
-
-        foreach ($this->_links as $l) {
-            $linktags = explode(
-                ' ',
-                ($casesensitive ? $l['tags']:mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8'))
-            );
-
-            if (count(array_intersect($linktags, $searchtags)) == count($searchtags)) {
-                $filtered[$l['linkdate']] = $l;
-            }
-        }
-        krsort($filtered);
-        return $filtered;
-    }
-
-
-    /**
-     * Returns the list of articles for a given day, chronologically sorted
-     *
-     * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
-     *  print_r($mydb->filterDay('20120125'));
-     */
-    public function filterDay($day)
-    {
-        if (! checkDateFormat('Ymd', $day)) {
-            throw new Exception('Invalid date format');
-        }
-
-        $filtered = array();
-        foreach ($this->_links as $l) {
-            if (startsWith($l['linkdate'], $day)) {
-                $filtered[$l['linkdate']] = $l;
-            }
-        }
-        ksort($filtered);
-        return $filtered;
-    }
-
-    /**
-     * Returns the article corresponding to a smallHash
-     */
-    public function filterSmallHash($smallHash)
-    {
-        $filtered = array();
-        foreach ($this->_links as $l) {
-            if ($smallHash == smallHash($l['linkdate'])) {
-                // Yes, this is ugly and slow
-                $filtered[$l['linkdate']] = $l;
-                return $filtered;
-            }
-        }
-        return $filtered;
+    public function filter($type, $request, $casesensitive = false, $privateonly = false) {
+        $requestFilter = is_array($request) ? implode(' ', $request) : $request;
+        return $this->linkFilter->filter($type, $requestFilter, $casesensitive, $privateonly);
     }
 
     /**
diff --git a/application/LinkFilter.php b/application/LinkFilter.php
new file mode 100644 (file)
index 0000000..cf64737
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+
+/**
+ * Class LinkFilter.
+ *
+ * Perform search and filter operation on link data list.
+ */
+class LinkFilter
+{
+    /**
+     * @var string permalinks.
+     */
+    public static $FILTER_HASH   = 'permalink';
+
+    /**
+     * @var string text search.
+     */
+    public static $FILTER_TEXT   = 'fulltext';
+
+    /**
+     * @var string tag filter.
+     */
+    public static $FILTER_TAG    = 'tags';
+
+    /**
+     * @var string filter by day.
+     */
+    public static $FILTER_DAY    = 'FILTER_DAY';
+
+    /**
+     * @var array all available links.
+     */
+    private $links;
+
+    /**
+     * @param array $links initialization.
+     */
+    public function __construct($links)
+    {
+        $this->links = $links;
+    }
+
+    /**
+     * Filter links according to parameters.
+     *
+     * @param string $type          Type of filter (eg. tags, permalink, etc.).
+     * @param string $request       Filter content.
+     * @param bool   $casesensitive Optional: Perform case sensitive filter if true.
+     * @param bool   $privateonly   Optional: Only returns private links if true.
+     *
+     * @return array filtered link list.
+     */
+    public function filter($type, $request, $casesensitive = false, $privateonly = false)
+    {
+        switch($type) {
+            case self::$FILTER_HASH:
+                return $this->filterSmallHash($request);
+                break;
+            case self::$FILTER_TEXT:
+                return $this->filterFulltext($request, $privateonly);
+                break;
+            case self::$FILTER_TAG:
+                return $this->filterTags($request, $casesensitive, $privateonly);
+                break;
+            case self::$FILTER_DAY:
+                return $this->filterDay($request);
+                break;
+            default:
+                return $this->noFilter($privateonly);
+        }
+    }
+
+    /**
+     * Unknown filter, but handle private only.
+     *
+     * @param bool $privateonly returns private link only if true.
+     *
+     * @return array filtered links.
+     */
+    private function noFilter($privateonly = false)
+    {
+        if (! $privateonly) {
+            krsort($this->links);
+            return $this->links;
+        }
+
+        $out = array();
+        foreach ($this->links as $value) {
+            if ($value['private']) {
+                $out[$value['linkdate']] = $value;
+            }
+        }
+
+        krsort($out);
+        return $out;
+    }
+
+    /**
+     * Returns the shaare corresponding to a smallHash.
+     *
+     * @param string $smallHash permalink hash.
+     *
+     * @return array $filtered array containing permalink data.
+     */
+    private function filterSmallHash($smallHash)
+    {
+        $filtered = array();
+        foreach ($this->links as $l) {
+            if ($smallHash == smallHash($l['linkdate'])) {
+                // Yes, this is ugly and slow
+                $filtered[$l['linkdate']] = $l;
+                return $filtered;
+            }
+        }
+        return $filtered;
+    }
+
+    /**
+     * Returns the list of links corresponding to a full-text search
+     *
+     * Searches:
+     *  - in the URLs, title and description;
+     *  - are case-insensitive.
+     *
+     * Example:
+     *    print_r($mydb->filterFulltext('hollandais'));
+     *
+     * mb_convert_case($val, MB_CASE_LOWER, 'UTF-8')
+     *  - allows to perform searches on Unicode text
+     *  - see https://github.com/shaarli/Shaarli/issues/75 for examples
+     *
+     * @param string $searchterms search query.
+     * @param bool   $privateonly return only private links if true.
+     *
+     * @return array search results.
+     */
+    private function filterFulltext($searchterms, $privateonly = false)
+    {
+        // FIXME: explode(' ',$searchterms) and perform a AND search.
+        // FIXME: accept double-quotes to search for a string "as is"?
+        $filtered = array();
+        $search = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
+        $explodedSearch = explode(' ', trim($search));
+        $keys = array('title', 'description', 'url', 'tags');
+
+        // Iterate over every stored link.
+        foreach ($this->links as $link) {
+            $found = false;
+
+            // ignore non private links when 'privatonly' is on.
+            if (! $link['private'] && $privateonly === true) {
+                continue;
+            }
+
+            // Iterate over searchable link fields.
+            foreach ($keys as $key) {
+                // Search full expression.
+                if (strpos(
+                    mb_convert_case($link[$key], MB_CASE_LOWER, 'UTF-8'),
+                    $search
+                ) !== false) {
+                    $found = true;
+                }
+
+                if ($found) {
+                    break;
+                }
+            }
+
+            if ($found) {
+                $filtered[$link['linkdate']] = $link;
+            }
+        }
+
+        krsort($filtered);
+        return $filtered;
+    }
+
+    /**
+     * Returns the list of links associated with a given list of tags
+     *
+     * You can specify one or more tags, separated by space or a comma, e.g.
+     *  print_r($mydb->filterTags('linux programming'));
+     *
+     * @param string $tags          list of tags separated by commas or blank spaces.
+     * @param bool   $casesensitive ignore case if false.
+     * @param bool   $privateonly   returns private links only.
+     *
+     * @return array filtered links.
+     */
+    public function filterTags($tags, $casesensitive = false, $privateonly = false)
+    {
+        $searchtags = $this->tagsStrToArray($tags, $casesensitive);
+        $filtered = array();
+
+        foreach ($this->links as $l) {
+            // ignore non private links when 'privatonly' is on.
+            if (! $l['private'] && $privateonly === true) {
+                continue;
+            }
+
+            $linktags = $this->tagsStrToArray($l['tags'], $casesensitive);
+
+            if (count(array_intersect($linktags, $searchtags)) == count($searchtags)) {
+                $filtered[$l['linkdate']] = $l;
+            }
+        }
+        krsort($filtered);
+        return $filtered;
+    }
+
+    /**
+     * Returns the list of articles for a given day, chronologically sorted
+     *
+     * Day must be in the form 'YYYYMMDD' (e.g. '20120125'), e.g.
+     *  print_r($mydb->filterDay('20120125'));
+     *
+     * @param string $day day to filter.
+     *
+     * @return array all link matching given day.
+     *
+     * @throws Exception if date format is invalid.
+     */
+    public function filterDay($day)
+    {
+        if (! checkDateFormat('Ymd', $day)) {
+            throw new Exception('Invalid date format');
+        }
+
+        $filtered = array();
+        foreach ($this->links as $l) {
+            if (startsWith($l['linkdate'], $day)) {
+                $filtered[$l['linkdate']] = $l;
+            }
+        }
+        ksort($filtered);
+        return $filtered;
+    }
+
+    /**
+     * Convert a list of tags (str) to an array. Also
+     * - handle case sensitivity.
+     * - accepts spaces commas as separator.
+     * - remove private tags for loggedout users.
+     *
+     * @param string $tags          string containing a list of tags.
+     * @param bool   $casesensitive will convert everything to lowercase if false.
+     *
+     * @return array filtered tags string.
+    */
+    public function tagsStrToArray($tags, $casesensitive)
+    {
+        // We use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
+        $tagsOut = $casesensitive ? $tags : mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8');
+        $tagsOut = str_replace(',', ' ', $tagsOut);
+
+        return explode(' ', trim($tagsOut));
+    }
+}
index f84f70e44a7f8662f136bd19909b35f338796cbd..aeaef9ff143a84e67f16bb99365fcdbd70680608 100644 (file)
@@ -72,12 +72,14 @@ function sanitizeLink(&$link)
 
 /**
  * Checks if a string represents a valid date
+
+ * @param string $format The expected DateTime format of the string
+ * @param string $string A string-formatted date
+ *
+ * @return bool whether the string is a valid date
  *
- * @param string        a string-formatted date
- * @param format        the expected DateTime format of the string
- * @return              whether the string is a valid date
- * @see                 http://php.net/manual/en/class.datetime.php
- * @see                 http://php.net/manual/en/datetime.createfromformat.php
+ * @see http://php.net/manual/en/class.datetime.php
+ * @see http://php.net/manual/en/datetime.createfromformat.php
  */
 function checkDateFormat($format, $string)
 {
index 40a6fbe5a1a5c6e0c2f1d3b06782617df2da9c54..1664c01b6ec192d24a2a6155f651f5db3e12d1b2 100644 (file)
--- a/index.php
+++ b/index.php
@@ -151,6 +151,7 @@ require_once 'application/CachedPage.php';
 require_once 'application/FileUtils.php';
 require_once 'application/HttpUtils.php';
 require_once 'application/LinkDB.php';
+require_once 'application/LinkFilter.php';
 require_once 'application/TimeZone.php';
 require_once 'application/Url.php';
 require_once 'application/Utils.php';
@@ -730,18 +731,23 @@ function showRSS()
     // Read links from database (and filter private links if user it not logged in).
 
     // Optionally filter the results:
-    $linksToDisplay=array();
-    if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
-    else if (!empty($_GET['searchtags']))   $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
-    else $linksToDisplay = $LINKSDB;
+    if (!empty($_GET['searchterm'])) {
+        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
+    }
+    elseif (!empty($_GET['searchtags'])) {
+        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
+    }
+    else {
+        $linksToDisplay = $LINKSDB;
+    }
 
     $nblinksToDisplay = 50;  // Number of links to display.
-    if (!empty($_GET['nb']))  // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    {
-        $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
+    // 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));
+    $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";
@@ -821,15 +827,20 @@ function showATOM()
     );
 
     // Optionally filter the results:
-    $linksToDisplay=array();
-    if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
-    else if (!empty($_GET['searchtags']))   $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
-    else $linksToDisplay = $LINKSDB;
+    if (!empty($_GET['searchterm'])) {
+        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
+    }
+    else if (!empty($_GET['searchtags'])) {
+        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
+    }
+    else {
+        $linksToDisplay = $LINKSDB;
+    }
 
     $nblinksToDisplay = 50;  // Number of links to display.
-    if (!empty($_GET['nb']))  // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    {
-        $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
+    // 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));
@@ -1024,7 +1035,7 @@ function showDaily($pageBuilder)
     }
 
     try {
-        $linksToDisplay = $LINKSDB->filterDay($day);
+        $linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_DAY, $day);
     } catch (Exception $exc) {
         error_log($exc);
         $linksToDisplay = array();
@@ -1149,13 +1160,17 @@ function renderPage()
     if ($targetPage == Router::$PAGE_PICWALL)
     {
         // Optionally filter the results:
-        $links=array();
-        if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']);
-        elseif (!empty($_GET['searchtags']))   $links = $LINKSDB->filterTags(trim($_GET['searchtags']));
-        else $links = $LINKSDB;
+        if (!empty($_GET['searchterm'])) {
+            $links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
+        }
+        elseif (! empty($_GET['searchtags'])) {
+            $links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
+        }
+        else {
+            $links = $LINKSDB;
+        }
 
-        $body='';
-        $linksToDisplay=array();
+        $linksToDisplay = array();
 
         // Get only links which have a thumbnail.
         foreach($links as $link)
@@ -1282,7 +1297,7 @@ function renderPage()
         }
 
         if (isset($params['searchtags'])) {
-            $tags = explode(' ',$params['searchtags']);
+            $tags = explode(' ', $params['searchtags']);
             $tags=array_diff($tags, array($_GET['removetag'])); // Remove value from array $tags.
             if (count($tags)==0) {
                 unset($params['searchtags']);
@@ -1467,7 +1482,8 @@ function renderPage()
         if (!empty($_POST['deletetag']) && !empty($_POST['fromtag']))
         {
             $needle=trim($_POST['fromtag']);
-            $linksToAlter = $LINKSDB->filterTags($needle,true); // True for case-sensitive tag search.
+            // True for case-sensitive tag search.
+            $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
             foreach($linksToAlter as $key=>$value)
             {
                 $tags = explode(' ',trim($value['tags']));
@@ -1484,7 +1500,8 @@ function renderPage()
         if (!empty($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag']))
         {
             $needle=trim($_POST['fromtag']);
-            $linksToAlter = $LINKSDB->filterTags($needle,true); // true for case-sensitive tag search.
+            // True for case-sensitive tag search.
+            $linksToAlter = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $needle, true);
             foreach($linksToAlter as $key=>$value)
             {
                 $tags = explode(' ',trim($value['tags']));
@@ -1865,81 +1882,78 @@ function importFile()
 function buildLinkList($PAGE,$LINKSDB)
 {
     // ---- Filter link database according to parameters
-    $linksToDisplay=array();
-    $search_type='';
-    $search_crits='';
-    if (isset($_GET['searchterm'])) // Fulltext search
-    {
-        $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm']));
-        $search_crits=escape(trim($_GET['searchterm']));
-        $search_type='fulltext';
-    }
-    elseif (isset($_GET['searchtags'])) // Search by tag
-    {
-        $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
-        $search_crits=explode(' ',escape(trim($_GET['searchtags'])));
-        $search_type='tags';
-    }
-    elseif (isset($_SERVER['QUERY_STRING']) && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/',$_SERVER['QUERY_STRING'])) // Detect smallHashes in URL
-    {
-        $linksToDisplay = $LINKSDB->filterSmallHash(substr(trim($_SERVER["QUERY_STRING"], '/'),0,6));
-        if (count($linksToDisplay)==0)
-        {
-            header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
-            echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.';
+    $search_type = '';
+    $search_crits = '';
+    $privateonly = !empty($_SESSION['privateonly']) ? true : false;
+
+    // Fulltext search
+    if (isset($_GET['searchterm'])) {
+        $search_crits = escape(trim($_GET['searchterm']));
+        $search_type = LinkFilter::$FILTER_TEXT;
+        $linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly);
+    }
+    // Search by tag
+    elseif (isset($_GET['searchtags'])) {
+        $search_crits = explode(' ', escape(trim($_GET['searchtags'])));
+        $search_type = LinkFilter::$FILTER_TAG;
+        $linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly);
+    }
+    // Detect smallHashes in URL.
+    elseif (isset($_SERVER['QUERY_STRING'])
+        && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])) {
+        $search_type = LinkFilter::$FILTER_HASH;
+        $search_crits = substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6);
+        $linksToDisplay = $LINKSDB->filter($search_type, $search_crits);
+
+        if (count($linksToDisplay) == 0) {
+            header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
+            echo '<h1>404 Not found.</h1>Oh crap.
+                  The link you are trying to reach does not exist or has been deleted.';
             echo '<br>Would you mind <a href="?">clicking here</a>?';
             exit;
         }
-        $search_type='permalink';
     }
-    else
-        $linksToDisplay = $LINKSDB;  // Otherwise, display without filtering.
-
-
-    // Option: Show only private links
-    if (!empty($_SESSION['privateonly']))
-    {
-        $tmp = array();
-        foreach($linksToDisplay as $linkdate=>$link)
-        {
-            if ($link['private']!=0) $tmp[$linkdate]=$link;
-        }
-        $linksToDisplay=$tmp;
+    // Otherwise, display without filtering.
+    else {
+        $linksToDisplay = $LINKSDB->filter('', '', false, $privateonly);
     }
 
     // ---- Handle paging.
-    /* Can someone explain to me why you get the following error when using array_keys() on an object which implements the interface ArrayAccess???
-       "Warning: array_keys() expects parameter 1 to be array, object given in ... "
-       If my class implements ArrayAccess, why won't array_keys() accept it ?  ( $keys=array_keys($linksToDisplay); )
-    */
-    $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // Stupid and ugly. Thanks PHP.
+    $keys = array();
+    foreach ($linksToDisplay as $key => $value) {
+        $keys[] = $key;
+    }
 
     // If there is only a single link, we change on-the-fly the title of the page.
-    if (count($linksToDisplay)==1) $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title'];
+    if (count($linksToDisplay) == 1) {
+        $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title'];
+    }
 
     // Select articles according to paging.
-    $pagecount = ceil(count($keys)/$_SESSION['LINKS_PER_PAGE']);
-    $pagecount = ($pagecount==0 ? 1 : $pagecount);
-    $page=( empty($_GET['page']) ? 1 : intval($_GET['page']));
-    $page = ( $page<1 ? 1 : $page );
-    $page = ( $page>$pagecount ? $pagecount : $page );
-    $i = ($page-1)*$_SESSION['LINKS_PER_PAGE']; // Start index.
-    $end = $i+$_SESSION['LINKS_PER_PAGE'];
-    $linkDisp=array(); // Links to display
+    $pagecount = ceil(count($keys) / $_SESSION['LINKS_PER_PAGE']);
+    $pagecount = $pagecount == 0 ? 1 : $pagecount;
+    $page= empty($_GET['page']) ? 1 : intval($_GET['page']);
+    $page = $page < 1 ? 1 : $page;
+    $page = $page > $pagecount ? $pagecount : $page;
+    // Start index.
+    $i = ($page-1) * $_SESSION['LINKS_PER_PAGE'];
+    $end = $i + $_SESSION['LINKS_PER_PAGE'];
+    $linkDisp = array();
     while ($i<$end && $i<count($keys))
     {
         $link = $linksToDisplay[$keys[$i]];
         $link['description'] = format_description($link['description'], $GLOBALS['redirector']);
-        $classLi =  $i%2!=0 ? '' : 'publicLinkHightLight';
-        $link['class'] = ($link['private']==0 ? $classLi : 'private');
-        $link['timestamp']=linkdate2timestamp($link['linkdate']);
-        $taglist = explode(' ',$link['tags']);
+        $classLi =  ($i % 2) != 0 ? '' : 'publicLinkHightLight';
+        $link['class'] = $link['private'] == 0 ? $classLi : 'private';
+        $link['timestamp'] = linkdate2timestamp($link['linkdate']);
+        $taglist = explode(' ', $link['tags']);
         uasort($taglist, 'strcasecmp');
-        $link['taglist']=$taglist;
+        $link['taglist'] = $taglist;
         $link['shorturl'] = smallHash($link['linkdate']);
-        if ($link["url"][0] === '?' && // Check for both signs of a note: starting with ? and 7 chars long. I doubt that you'll post any links that look like this.
-            strlen($link["url"]) === 7) {
-            $link["url"] = index_url($_SERVER) . $link["url"];
+        // Check for both signs of a note: starting with ? and 7 chars long.
+        if ($link['url'][0] === '?' &&
+            strlen($link['url']) === 7) {
+            $link['url'] = index_url($_SERVER) . $link['url'];
         }
 
         $linkDisp[$keys[$i]] = $link;
@@ -1947,13 +1961,21 @@ function buildLinkList($PAGE,$LINKSDB)
     }
 
     // Compute paging navigation
-    $searchterm= ( empty($_GET['searchterm']) ? '' : '&searchterm='.$_GET['searchterm'] );
-    $searchtags= ( empty($_GET['searchtags']) ? '' : '&searchtags='.$_GET['searchtags'] );
-    $paging='';
-    $previous_page_url=''; if ($i!=count($keys)) $previous_page_url='?page='.($page+1).$searchterm.$searchtags;
-    $next_page_url='';if ($page>1) $next_page_url='?page='.($page-1).$searchterm.$searchtags;
+    $searchterm = empty($_GET['searchterm']) ? '' : '&searchterm=' . $_GET['searchterm'];
+    $searchtags = empty($_GET['searchtags']) ? '' : '&searchtags=' . $_GET['searchtags'];
+    $previous_page_url = '';
+    if ($i != count($keys)) {
+        $previous_page_url = '?page=' . ($page+1) . $searchterm . $searchtags;
+    }
+    $next_page_url='';
+    if ($page>1) {
+        $next_page_url = '?page=' . ($page-1) . $searchterm . $searchtags;
+    }
 
-    $token = ''; if (isLoggedIn()) $token=getToken();
+    $token = '';
+    if (isLoggedIn()) {
+        $token = getToken();
+    }
 
     // Fill all template fields.
     $data = array(
index 7b22b2704f8ba84f3e26995f36c84d071b71161d..3b1a20572a75d428724c8dcf37f50a0f1edb4ff7 100644 (file)
@@ -302,236 +302,49 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
     }
 
     /**
-     * Filter links using a tag
-     */
-    public function testFilterOneTag()
-    {
-        $this->assertEquals(
-            3,
-            sizeof(self::$publicLinkDB->filterTags('web', false))
-        );
-
-        $this->assertEquals(
-            4,
-            sizeof(self::$privateLinkDB->filterTags('web', false))
-        );
-    }
-
-    /**
-     * Filter links using a tag - case-sensitive
-     */
-    public function testFilterCaseSensitiveTag()
-    {
-        $this->assertEquals(
-            0,
-            sizeof(self::$privateLinkDB->filterTags('mercurial', true))
-        );
-
-        $this->assertEquals(
-            1,
-            sizeof(self::$privateLinkDB->filterTags('Mercurial', true))
-        );
-    }
-
-    /**
-     * Filter links using a tag combination
-     */
-    public function testFilterMultipleTags()
-    {
-        $this->assertEquals(
-            1,
-            sizeof(self::$publicLinkDB->filterTags('dev cartoon', false))
-        );
-
-        $this->assertEquals(
-            2,
-            sizeof(self::$privateLinkDB->filterTags('dev cartoon', false))
-        );
-    }
-
-    /**
-     * Filter links using a non-existent tag
-     */
-    public function testFilterUnknownTag()
-    {
-        $this->assertEquals(
-            0,
-            sizeof(self::$publicLinkDB->filterTags('null', false))
-        );
-    }
-
-    /**
-     * Return links for a given day
-     */
-    public function testFilterDay()
-    {
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterDay('20121206'))
-        );
-
-        $this->assertEquals(
-            3,
-            sizeof(self::$privateLinkDB->filterDay('20121206'))
-        );
-    }
-
-    /**
-     * 404 - day not found
-     */
-    public function testFilterUnknownDay()
-    {
-        $this->assertEquals(
-            0,
-            sizeof(self::$publicLinkDB->filterDay('19700101'))
-        );
-
-        $this->assertEquals(
-            0,
-            sizeof(self::$privateLinkDB->filterDay('19700101'))
-        );
-    }
-
-    /**
-     * Use an invalid date format
-     * @expectedException              Exception
-     * @expectedExceptionMessageRegExp /Invalid date format/
-     */
-    public function testFilterInvalidDayWithChars()
-    {
-        self::$privateLinkDB->filterDay('Rainy day, dream away');
-    }
-
-    /**
-     * Use an invalid date format
-     * @expectedException              Exception
-     * @expectedExceptionMessageRegExp /Invalid date format/
-     */
-    public function testFilterInvalidDayDigits()
-    {
-        self::$privateLinkDB->filterDay('20');
-    }
-
-    /**
-     * Retrieve a link entry with its hash
-     */
-    public function testFilterSmallHash()
-    {
-        $links = self::$privateLinkDB->filterSmallHash('IuWvgA');
-
-        $this->assertEquals(
-            1,
-            sizeof($links)
-        );
-
-        $this->assertEquals(
-            'MediaGoblin',
-            $links['20130614_184135']['title']
-        );
-        
-    }
-
-    /**
-     * No link for this hash
-     */
-    public function testFilterUnknownSmallHash()
-    {
-        $this->assertEquals(
-            0,
-            sizeof(self::$privateLinkDB->filterSmallHash('Iblaah'))
-        );
-    }
-
-    /**
-     * Full-text search - result from a link's URL
-     */
-    public function testFilterFullTextURL()
-    {
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterFullText('ars.userfriendly.org'))
-        );
-    }
-
-    /**
-     * Full-text search - result from a link's title only
+     * Test real_url without redirector.
      */
-    public function testFilterFullTextTitle()
+    public function testLinkRealUrlWithoutRedirector()
     {
-        // use miscellaneous cases
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterFullText('userfriendly -'))
-        );
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterFullText('UserFriendly -'))
-        );
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterFullText('uSeRFrIendlY -'))
-        );
-
-        // use miscellaneous case and offset
-        $this->assertEquals(
-            2,
-            sizeof(self::$publicLinkDB->filterFullText('RFrIendL'))
-        );
+        $db = new LinkDB(self::$testDatastore, false, false);
+        foreach($db as $link) {
+            $this->assertEquals($link['url'], $link['real_url']);
+        }
     }
 
     /**
-     * Full-text search - result from the link's description only
+     * Test real_url with redirector.
      */
-    public function testFilterFullTextDescription()
+    public function testLinkRealUrlWithRedirector()
     {
-        $this->assertEquals(
-            1,
-            sizeof(self::$publicLinkDB->filterFullText('media publishing'))
-        );
+        $redirector = 'http://redirector.to?';
+        $db = new LinkDB(self::$testDatastore, false, false, $redirector);
+        foreach($db as $link) {
+            $this->assertStringStartsWith($redirector, $link['real_url']);
+        }
     }
 
     /**
-     * Full-text search - result from the link's tags only
+     * Test filter with string.
      */
-    public function testFilterFullTextTags()
+    public function testFilterString()
     {
+        $tags = 'dev cartoon';
         $this->assertEquals(
             2,
-            sizeof(self::$publicLinkDB->filterFullText('gnu'))
+            count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
         );
     }
 
     /**
-     * Full-text search - result set from mixed sources
+     * Test filter with string.
      */
-    public function testFilterFullTextMixed()
+    public function testFilterArray()
     {
+        $tags = array('dev', 'cartoon');
         $this->assertEquals(
             2,
-            sizeof(self::$publicLinkDB->filterFullText('free software'))
+            count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false))
         );
     }
-
-    /**
-     * Test real_url without redirector.
-     */
-    public function testLinkRealUrlWithoutRedirector()
-    {
-        $db = new LinkDB(self::$testDatastore, false, false);
-        foreach($db as $link) {
-            $this->assertEquals($link['url'], $link['real_url']);
-        }
-    }
-
-    /**
-     * Test real_url with redirector.
-     */
-    public function testLinkRealUrlWithRedirector()
-    {
-        $redirector = 'http://redirector.to?';
-        $db = new LinkDB(self::$testDatastore, false, false, $redirector);
-        foreach($db as $link) {
-            $this->assertStringStartsWith($redirector, $link['real_url']);
-        }
-    }
 }
diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php
new file mode 100644 (file)
index 0000000..5107ab7
--- /dev/null
@@ -0,0 +1,242 @@
+<?php
+
+require_once 'application/LinkFilter.php';
+
+/**
+ * Class LinkFilterTest.
+ */
+class LinkFilterTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @var LinkFilter instance.
+     */
+    protected static $linkFilter;
+
+    /**
+     * Instanciate linkFilter with ReferenceLinkDB data.
+     */
+    public static function setUpBeforeClass()
+    {
+        $refDB = new ReferenceLinkDB();
+        self::$linkFilter = new LinkFilter($refDB->getLinks());
+    }
+
+    /**
+     * Blank filter.
+     */
+    public function testFilter()
+    {
+        $this->assertEquals(
+            6,
+            count(self::$linkFilter->filter('', ''))
+        );
+
+        // Private only.
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter('', '', false, true))
+        );
+    }
+
+    /**
+     * Filter links using a tag
+     */
+    public function testFilterOneTag()
+    {
+        $this->assertEquals(
+            4,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false))
+        );
+
+        // Private only.
+        $this->assertEquals(
+            1,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'web', false, true))
+        );
+    }
+
+    /**
+     * Filter links using a tag - case-sensitive
+     */
+    public function testFilterCaseSensitiveTag()
+    {
+        $this->assertEquals(
+            0,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'mercurial', true))
+        );
+
+        $this->assertEquals(
+            1,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'Mercurial', true))
+        );
+    }
+
+    /**
+     * Filter links using a tag combination
+     */
+    public function testFilterMultipleTags()
+    {
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'dev cartoon', false))
+        );
+    }
+
+    /**
+     * Filter links using a non-existent tag
+     */
+    public function testFilterUnknownTag()
+    {
+        $this->assertEquals(
+            0,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, 'null', false))
+        );
+    }
+
+    /**
+     * Return links for a given day
+     */
+    public function testFilterDay()
+    {
+        $this->assertEquals(
+            3,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20121206'))
+        );
+    }
+
+    /**
+     * 404 - day not found
+     */
+    public function testFilterUnknownDay()
+    {
+        $this->assertEquals(
+            0,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '19700101'))
+        );
+    }
+
+    /**
+     * Use an invalid date format
+     * @expectedException              Exception
+     * @expectedExceptionMessageRegExp /Invalid date format/
+     */
+    public function testFilterInvalidDayWithChars()
+    {
+        self::$linkFilter->filter(LinkFilter::$FILTER_DAY, 'Rainy day, dream away');
+    }
+
+    /**
+     * Use an invalid date format
+     * @expectedException              Exception
+     * @expectedExceptionMessageRegExp /Invalid date format/
+     */
+    public function testFilterInvalidDayDigits()
+    {
+        self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20');
+    }
+
+    /**
+     * Retrieve a link entry with its hash
+     */
+    public function testFilterSmallHash()
+    {
+        $links = self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'IuWvgA');
+
+        $this->assertEquals(
+            1,
+            count($links)
+        );
+
+        $this->assertEquals(
+            'MediaGoblin',
+            $links['20130614_184135']['title']
+        );
+    }
+
+    /**
+     * No link for this hash
+     */
+    public function testFilterUnknownSmallHash()
+    {
+        $this->assertEquals(
+            0,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_HASH, 'Iblaah'))
+        );
+    }
+
+    /**
+     * Full-text search - result from a link's URL
+     */
+    public function testFilterFullTextURL()
+    {
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'ars.userfriendly.org'))
+        );
+    }
+
+    /**
+     * Full-text search - result from a link's title only
+     */
+    public function testFilterFullTextTitle()
+    {
+        // use miscellaneous cases
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'userfriendly -'))
+        );
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'UserFriendly -'))
+        );
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'uSeRFrIendlY -'))
+        );
+
+        // use miscellaneous case and offset
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'RFrIendL'))
+        );
+    }
+
+    /**
+     * Full-text search - result from the link's description only
+     */
+    public function testFilterFullTextDescription()
+    {
+        $this->assertEquals(
+            1,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'media publishing'))
+        );
+    }
+
+    /**
+     * Full-text search - result from the link's tags only
+     */
+    public function testFilterFullTextTags()
+    {
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'gnu'))
+        );
+
+        // Private only.
+        $this->assertEquals(
+            1,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'web', false, true))
+        );
+    }
+
+    /**
+     * Full-text search - result set from mixed sources
+     */
+    public function testFilterFullTextMixed()
+    {
+        $this->assertEquals(
+            2,
+            count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, 'free software'))
+        );
+    }
+}
index 47b51829989018906824c0cdc34b2f154764b0bc..011317ef970eb3ea9834ebc8b3834a04e0a00435 100644 (file)
@@ -124,4 +124,9 @@ class ReferenceLinkDB
     {
         return $this->_privateCount;
     }
+
+    public function getLinks()
+    {
+        return $this->_links;
+    }
 }
index 666748a7b3f2d4bf660ee116ed016af9b2dc32a3..09860bafbec0b2ca3c3ba7b8d8f05d5888da7390 100644 (file)
@@ -7,15 +7,24 @@
 <body>
 <div id="pageheader">
     {include="page.header"}
+
     <div id="headerform" class="search">
         <form method="GET" class="searchform" name="searchform">
-            <input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="Search text" value="">
+            <input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="Search text"
+               {if="!empty($search_crits) && $search_type=='fulltext'"}
+                    value="{$search_crits}"
+               {/if}
+            >
             <input type="submit" value="Search" class="bigbutton">
         </form>
         <form method="GET" class="tagfilter" name="tagfilter">
-            <input type="text" tabindex="2" name="searchtags" id="tagfilter_value" placeholder="Filter by tag" value=""
-                   autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
-                   data-list="{loop="$tags"}{$key}, {/loop}">
+            <input type="text" tabindex="2" name="searchtags" id="tagfilter_value" placeholder="Filter by tag"
+                {if="!empty($search_crits) && $search_type=='tags'"}
+                    value="{function="implode(' ', $search_crits)"}"
+                {/if}
+                autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
+                data-list="{loop="$tags"}{$key}, {/loop}">
+            >
             <input type="submit" value="Search" class="bigbutton">
         </form>
         {loop="$plugins_header.fields_toolbar"}