Allow crossed search between terms and tags
* Partial fix of #449 * Current use case: search term + click on tag. * LinkFilter now returns all links if no filter is given. * Unit tests.
This commit is contained in:
parent
6c3d6a31f4
commit
c51fae92dc
6 changed files with 178 additions and 65 deletions
|
@ -353,8 +353,7 @@ You use the community supported version of the original Shaarli project, by Seba
|
|||
public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
|
||||
{
|
||||
$linkFilter = new LinkFilter($this->_links);
|
||||
$requestFilter = is_array($request) ? implode(' ', $request) : $request;
|
||||
return $linkFilter->filter($type, trim($requestFilter), $casesensitive, $privateonly);
|
||||
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,16 +55,25 @@ class LinkFilter
|
|||
switch($type) {
|
||||
case self::$FILTER_HASH:
|
||||
return $this->filterSmallHash($request);
|
||||
break;
|
||||
case self::$FILTER_TAG | self::$FILTER_TEXT:
|
||||
if (!empty($request)) {
|
||||
$filtered = $this->links;
|
||||
if (isset($request[0])) {
|
||||
$filtered = $this->filterTags($request[0], $casesensitive, $privateonly);
|
||||
}
|
||||
if (isset($request[1])) {
|
||||
$lf = new LinkFilter($filtered);
|
||||
$filtered = $lf->filterFulltext($request[1], $privateonly);
|
||||
}
|
||||
return $filtered;
|
||||
}
|
||||
return $this->noFilter($privateonly);
|
||||
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);
|
||||
}
|
||||
|
@ -138,6 +147,10 @@ class LinkFilter
|
|||
*/
|
||||
private function filterFulltext($searchterms, $privateonly = false)
|
||||
{
|
||||
if (empty($searchterms)) {
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
$filtered = array();
|
||||
$search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8');
|
||||
$exactRegex = '/"([^"]+)"/';
|
||||
|
@ -219,6 +232,12 @@ class LinkFilter
|
|||
*/
|
||||
public function filterTags($tags, $casesensitive = false, $privateonly = false)
|
||||
{
|
||||
// Implode if array for clean up.
|
||||
$tags = is_array($tags) ? trim(implode(' ', $tags)) : $tags;
|
||||
if (empty($tags)) {
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
$searchtags = self::tagsStrToArray($tags, $casesensitive);
|
||||
$filtered = array();
|
||||
if (empty($searchtags)) {
|
||||
|
|
|
@ -33,6 +33,10 @@ h1 {
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.bigbutton {
|
||||
background-color: #c0c0c0;
|
||||
|
|
120
index.php
120
index.php
|
@ -623,7 +623,7 @@ class pageBuilder
|
|||
if (!empty($_GET['searchtags'])) {
|
||||
$searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']);
|
||||
}
|
||||
elseif (!empty($_GET['searchterm'])) {
|
||||
if (!empty($_GET['searchterm'])) {
|
||||
$searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']);
|
||||
}
|
||||
$this->tpl->assign('searchcrits', $searchcrits);
|
||||
|
@ -709,11 +709,19 @@ function showRSS()
|
|||
// Read links from database (and filter private links if user it not logged in).
|
||||
|
||||
// Optionally filter the results:
|
||||
if (!empty($_GET['searchterm'])) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
elseif (!empty($_GET['searchtags'])) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
||||
elseif ($searchtags) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$linksToDisplay = $LINKSDB;
|
||||
|
@ -807,11 +815,19 @@ function showATOM()
|
|||
);
|
||||
|
||||
// Optionally filter the results:
|
||||
if (!empty($_GET['searchterm'])) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
else if (!empty($_GET['searchtags'])) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
||||
elseif ($searchtags) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$linksToDisplay = $LINKSDB;
|
||||
|
@ -1165,11 +1181,19 @@ function renderPage()
|
|||
if ($targetPage == Router::$PAGE_PICWALL)
|
||||
{
|
||||
// Optionally filter the results:
|
||||
if (!empty($_GET['searchterm'])) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape($_GET['searchterm']) : '';
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$links = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm)
|
||||
);
|
||||
}
|
||||
elseif (! empty($_GET['searchtags'])) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
||||
elseif ($searchtags) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||
}
|
||||
elseif ($searchterm) {
|
||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||
}
|
||||
else {
|
||||
$links = $LINKSDB;
|
||||
|
@ -1963,29 +1987,46 @@ function importFile()
|
|||
// This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
|
||||
function buildLinkList($PAGE,$LINKSDB)
|
||||
{
|
||||
// ---- Filter link database according to parameters
|
||||
$search_type = '';
|
||||
$search_crits = '';
|
||||
// Filter link database according to parameters.
|
||||
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||
$searchterm = !empty($_GET['searchterm']) ? escape(trim($_GET['searchterm'])) : '';
|
||||
$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 tags + fullsearch.
|
||||
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($searchtags, $searchterm),
|
||||
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);
|
||||
// Search by tags.
|
||||
elseif (! empty($searchtags)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TAG,
|
||||
$searchtags,
|
||||
false,
|
||||
$privateonly
|
||||
);
|
||||
}
|
||||
// Fulltext search.
|
||||
elseif (! empty($searchterm)) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_TEXT,
|
||||
$searchterm,
|
||||
false,
|
||||
$privateonly
|
||||
);
|
||||
}
|
||||
// Detect smallHashes in URL.
|
||||
elseif (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);
|
||||
elseif (! empty($_SERVER['QUERY_STRING'])
|
||||
&& preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_SERVER['QUERY_STRING'])
|
||||
) {
|
||||
$linksToDisplay = $LINKSDB->filter(
|
||||
LinkFilter::$FILTER_HASH,
|
||||
substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6)
|
||||
);
|
||||
|
||||
if (count($linksToDisplay) == 0) {
|
||||
$PAGE->render404('The link you are trying to reach does not exist or has been deleted.');
|
||||
|
@ -2041,21 +2082,18 @@ function buildLinkList($PAGE,$LINKSDB)
|
|||
}
|
||||
|
||||
// Compute paging navigation
|
||||
$searchterm = empty($_GET['searchterm']) ? '' : '&searchterm=' . $_GET['searchterm'];
|
||||
$searchtags = empty($_GET['searchtags']) ? '' : '&searchtags=' . $_GET['searchtags'];
|
||||
$searchtagsUrl = empty($searchtags) ? '' : '&searchtags=' . urlencode($searchtags);
|
||||
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
|
||||
$previous_page_url = '';
|
||||
if ($i != count($keys)) {
|
||||
$previous_page_url = '?page=' . ($page+1) . $searchterm . $searchtags;
|
||||
$previous_page_url = '?page=' . ($page+1) . $searchtermUrl . $searchtagsUrl;
|
||||
}
|
||||
$next_page_url='';
|
||||
if ($page>1) {
|
||||
$next_page_url = '?page=' . ($page-1) . $searchterm . $searchtags;
|
||||
$next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl;
|
||||
}
|
||||
|
||||
$token = '';
|
||||
if (isLoggedIn()) {
|
||||
$token = getToken();
|
||||
}
|
||||
$token = isLoggedIn() ? getToken() : '';
|
||||
|
||||
// Fill all template fields.
|
||||
$data = array(
|
||||
|
@ -2065,8 +2103,8 @@ function buildLinkList($PAGE,$LINKSDB)
|
|||
'page_current' => $page,
|
||||
'page_max' => $pagecount,
|
||||
'result_count' => count($linksToDisplay),
|
||||
'search_type' => $search_type,
|
||||
'search_crits' => $search_crits,
|
||||
'search_term' => $searchterm,
|
||||
'search_tags' => $searchtags,
|
||||
'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL.
|
||||
'token' => $token,
|
||||
'links' => $linkDisp,
|
||||
|
|
|
@ -12,6 +12,8 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
*/
|
||||
protected static $linkFilter;
|
||||
|
||||
protected static $NB_LINKS_REFDB = 7;
|
||||
|
||||
/**
|
||||
* Instanciate linkFilter with ReferenceLinkDB data.
|
||||
*/
|
||||
|
@ -27,7 +29,7 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
public function testFilter()
|
||||
{
|
||||
$this->assertEquals(
|
||||
7,
|
||||
self::$NB_LINKS_REFDB,
|
||||
count(self::$linkFilter->filter('', ''))
|
||||
);
|
||||
|
||||
|
@ -36,6 +38,16 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
2,
|
||||
count(self::$linkFilter->filter('', '', false, true))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, ''))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, ''))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -341,4 +353,41 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
|||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test crossed search (terms + tags).
|
||||
*/
|
||||
public function testFilterCrossedSearch()
|
||||
{
|
||||
$terms = '"Free Software " stallman "read this" @website stuff';
|
||||
$tags = 'free';
|
||||
$this->assertEquals(
|
||||
1,
|
||||
count(self::$linkFilter->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($tags, $terms)
|
||||
))
|
||||
);
|
||||
$this->assertEquals(
|
||||
2,
|
||||
count(self::$linkFilter->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array('', $terms)
|
||||
))
|
||||
);
|
||||
$this->assertEquals(
|
||||
1,
|
||||
count(self::$linkFilter->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
array($tags, '')
|
||||
))
|
||||
);
|
||||
$this->assertEquals(
|
||||
self::$NB_LINKS_REFDB,
|
||||
count(self::$linkFilter->filter(
|
||||
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||
''
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,16 +11,16 @@
|
|||
<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"
|
||||
{if="!empty($search_crits) && $search_type=='fulltext'"}
|
||||
value="{$search_crits}"
|
||||
{if="!empty($search_term)"}
|
||||
value="{$search_term}"
|
||||
{/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"
|
||||
{if="!empty($search_crits) && $search_type=='tags'"}
|
||||
value="{function="implode(' ', $search_crits)"}"
|
||||
{if="!empty($search_tags)"}
|
||||
value="{$search_tags}"
|
||||
{/if}
|
||||
autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
|
||||
data-list="{loop="$tags"}{$key}, {/loop}"
|
||||
|
@ -44,19 +44,23 @@
|
|||
</div>
|
||||
|
||||
{if="count($links)==0"}
|
||||
<div id="searchcriteria">Nothing found.</i></div>
|
||||
{else}
|
||||
{if="$search_type=='fulltext'"}
|
||||
<div id="searchcriteria">{$result_count} results for <i>{$search_crits}</i></div>
|
||||
{/if}
|
||||
{if="$search_type=='tags'"}
|
||||
<div id="searchcriteria">{$result_count} results for tags <i>
|
||||
{loop="search_crits"}
|
||||
<span class="linktag" title="Remove tag">
|
||||
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
|
||||
</span>
|
||||
{/loop}</i></div>
|
||||
{/if}
|
||||
<div id="searchcriteria">Nothing found.</div>
|
||||
{elseif="!empty($search_term) or !empty($search_tags)"}
|
||||
<div id="searchcriteria">
|
||||
{$result_count} results
|
||||
{if="!empty($search_term)"}
|
||||
for <em>{$search_term}</em>
|
||||
{/if}
|
||||
{if="!empty($search_tags)"}
|
||||
{$exploded_tags=explode(' ', $search_tags)}
|
||||
tagged
|
||||
{loop="$exploded_tags"}
|
||||
<span class="linktag" title="Remove tag">
|
||||
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
|
||||
</span>
|
||||
{/loop}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<ul>
|
||||
{loop="links"}
|
||||
|
|
Loading…
Reference in a new issue