From 822bffced8212e7f34bcb2ad063b31a78bd57bdb Mon Sep 17 00:00:00 2001 From: ArthurHoaro Date: Sun, 27 Dec 2015 10:08:20 +0100 Subject: [PATCH] Link filter refactoring * 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 | 120 ++------------- application/LinkFilter.php | 259 ++++++++++++++++++++++++++++++++ application/Utils.php | 12 +- index.php | 200 +++++++++++++----------- tests/LinkDBTest.php | 235 +++-------------------------- tests/LinkFilterTest.php | 242 +++++++++++++++++++++++++++++ tests/utils/ReferenceLinkDB.php | 5 + tpl/linklist.html | 17 ++- 8 files changed, 676 insertions(+), 414 deletions(-) create mode 100644 application/LinkFilter.php create mode 100644 tests/LinkFilterTest.php diff --git a/application/LinkDB.php b/application/LinkDB.php index 51fa926..be7d901 100644 --- a/application/LinkDB.php +++ b/application/LinkDB.php @@ -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. + * @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. * - * 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 + * @return array filtered links */ - 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 - * - * You can specify one or more tags, separated by space or a comma, e.g. - * print_r($mydb->filterTags('linux programming')); - */ - 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 index 0000000..cf64737 --- /dev/null +++ b/application/LinkFilter.php @@ -0,0 +1,259 @@ +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)); + } +} diff --git a/application/Utils.php b/application/Utils.php index f84f70e..aeaef9f 100644 --- a/application/Utils.php +++ b/application/Utils.php @@ -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 * - * @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 + * @return bool 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 */ function checkDateFormat($format, $string) { diff --git a/index.php b/index.php index 40a6fbe..1664c01 100644 --- 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; - - $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) ; + 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; } - $pageaddr=escape(index_url($_SERVER)); + $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 ''; echo ''.$GLOBALS['title'].''.$pageaddr.''; echo 'Shared linksen-en'.$pageaddr.''."\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'; + $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); } - elseif (isset($_GET['searchtags'])) // Search by tag - { - $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); - $search_crits=explode(' ',escape(trim($_GET['searchtags']))); - $search_type='tags'; + // 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); } - 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 '

404 Not found.

Oh crap. The link you are trying to reach does not exist or has been deleted.'; + // 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 '

404 Not found.

Oh crap. + The link you are trying to reach does not exist or has been deleted.'; echo '
Would you mind clicking here?'; 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 && $i1) $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( diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php index 7b22b27..3b1a205 100644 --- a/tests/LinkDBTest.php +++ b/tests/LinkDBTest.php @@ -301,217 +301,6 @@ 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 - */ - public function testFilterFullTextTitle() - { - // 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')) - ); - } - - /** - * Full-text search - result from the link's description only - */ - public function testFilterFullTextDescription() - { - $this->assertEquals( - 1, - sizeof(self::$publicLinkDB->filterFullText('media publishing')) - ); - } - - /** - * Full-text search - result from the link's tags only - */ - public function testFilterFullTextTags() - { - $this->assertEquals( - 2, - sizeof(self::$publicLinkDB->filterFullText('gnu')) - ); - } - - /** - * Full-text search - result set from mixed sources - */ - public function testFilterFullTextMixed() - { - $this->assertEquals( - 2, - sizeof(self::$publicLinkDB->filterFullText('free software')) - ); - } - /** * Test real_url without redirector. */ @@ -534,4 +323,28 @@ class LinkDBTest extends PHPUnit_Framework_TestCase $this->assertStringStartsWith($redirector, $link['real_url']); } } + + /** + * Test filter with string. + */ + public function testFilterString() + { + $tags = 'dev cartoon'; + $this->assertEquals( + 2, + count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) + ); + } + + /** + * Test filter with string. + */ + public function testFilterArray() + { + $tags = array('dev', 'cartoon'); + $this->assertEquals( + 2, + count(self::$privateLinkDB->filter(LinkFilter::$FILTER_TAG, $tags, true, false)) + ); + } } diff --git a/tests/LinkFilterTest.php b/tests/LinkFilterTest.php new file mode 100644 index 0000000..5107ab7 --- /dev/null +++ b/tests/LinkFilterTest.php @@ -0,0 +1,242 @@ +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')) + ); + } +} diff --git a/tests/utils/ReferenceLinkDB.php b/tests/utils/ReferenceLinkDB.php index 47b5182..011317e 100644 --- a/tests/utils/ReferenceLinkDB.php +++ b/tests/utils/ReferenceLinkDB.php @@ -124,4 +124,9 @@ class ReferenceLinkDB { return $this->_privateCount; } + + public function getLinks() + { + return $this->_links; + } } diff --git a/tpl/linklist.html b/tpl/linklist.html index 666748a..09860ba 100644 --- a/tpl/linklist.html +++ b/tpl/linklist.html @@ -7,15 +7,24 @@