Merge pull request #424 from ArthurHoaro/search

Link filter refactoring
This commit is contained in:
Arthur 2016-01-06 19:57:42 +01:00
commit 88c15abb2a
8 changed files with 683 additions and 419 deletions

View 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.
* @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, trim($requestFilter), $casesensitive, $privateonly);
}
/**

259
application/LinkFilter.php Normal file
View File

@ -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));
}
}

View 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
*
* @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)
{

210
index.php
View File

@ -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 '<?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,13 +1297,15 @@ function renderPage()
}
if (isset($params['searchtags'])) {
$tags = explode(' ',$params['searchtags']);
$tags=array_diff($tags, array($_GET['removetag'])); // Remove value from array $tags.
if (count($tags)==0) {
$tags = explode(' ', $params['searchtags']);
// Remove value from array $tags.
$tags = array_diff($tags, array($_GET['removetag']));
$params['searchtags'] = implode(' ',$tags);
if (empty($params['searchtags'])) {
unset($params['searchtags']);
} else {
$params['searchtags'] = implode(' ',$tags);
}
unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different)
}
header('Location: ?'.http_build_query($params));
@ -1468,7 +1485,8 @@ function renderPage()
// Delete a tag:
if (isset($_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 +1502,8 @@ function renderPage()
// Rename a tag:
if (isset($_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 +1884,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 '<h1>404 Not found.</h1>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 '<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 +1963,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(

View File

@ -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))
);
}
}

242
tests/LinkFilterTest.php Normal file
View File

@ -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'))
);
}
}

View File

@ -124,4 +124,9 @@ class ReferenceLinkDB
{
return $this->_privateCount;
}
public function getLinks()
{
return $this->_links;
}
}

View 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"}
@ -44,7 +53,7 @@
<div id="searchcriteria">{$result_count} results for tags <i>
{loop="search_crits"}
<span class="linktag" title="Remove tag">
<a href="?removetag={$value}">{$value} <span class="remove">x</span></a>
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
</span>
{/loop}</i></div>
{/if}