Empty tag search will look for not tagged links

Fixes #784

From now, searching for tags with an empty value will return only not tagged links,
with the search bar showing `x results [not tagged]`.

Note that using the api, the searchtags request parameter must be set to `false` to get the same result.

  - [ ] Update API doc
This commit is contained in:
ArthurHoaro 2017-04-01 12:17:37 +02:00
parent b64d83cd2b
commit 7d86f40bdb
12 changed files with 115 additions and 36 deletions

View file

@ -97,6 +97,11 @@ public function __construct($linkDB, $feedType, $serverInfo, $userInput, $isLogg
*/
public function buildData()
{
// Search for untagged links
if (isset($this->userInput['searchtags']) && empty($this->userInput['searchtags'])) {
$this->userInput['searchtags'] = false;
}
// Optionally filter the results:
$linksToDisplay = $this->linkDB->filterSearch($this->userInput);

View file

@ -450,29 +450,12 @@ public function filterDay($request) {
public function filterSearch($filterRequest = array(), $casesensitive = false, $visibility = 'all')
{
// Filter link database according to parameters.
$searchtags = !empty($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
$searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
$searchtags = isset($filterRequest['searchtags']) ? escape($filterRequest['searchtags']) : '';
$searchterm = isset($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
// Search tags + fullsearch.
if (! empty($searchtags) && ! empty($searchterm)) {
// Search tags + fullsearch - blank string parameter will return all links.
$type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
$request = array($searchtags, $searchterm);
}
// Search by tags.
elseif (! empty($searchtags)) {
$type = LinkFilter::$FILTER_TAG;
$request = $searchtags;
}
// Fulltext search.
elseif (! empty($searchterm)) {
$type = LinkFilter::$FILTER_TEXT;
$request = $searchterm;
}
// Otherwise, display without filtering.
else {
$type = '';
$request = '';
}
$request = [$searchtags, $searchterm];
$linkFilter = new LinkFilter($this);
return $linkFilter->filter($type, $request, $casesensitive, $visibility);

View file

@ -253,6 +253,9 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
{
// Implode if array for clean up.
$tags = is_array($tags) ? trim(implode(' ', $tags)) : $tags;
if ($tags === false) {
return $this->filterUntagged($visibility);
}
if (empty($tags)) {
return $this->noFilter($visibility);
}
@ -295,6 +298,33 @@ public function filterTags($tags, $casesensitive = false, $visibility = 'all')
return $filtered;
}
/**
* Return only links without any tag.
*
* @param string $visibility return only all/private/public links.
*
* @return array filtered links.
*/
public function filterUntagged($visibility)
{
$filtered = [];
foreach ($this->links as $key => $link) {
if ($visibility !== 'all') {
if (! $link['private'] && $visibility === 'private') {
continue;
} else if ($link['private'] && $visibility === 'public') {
continue;
}
}
if (empty(trim($link['tags']))) {
$filtered[$key] = $link;
}
}
return $filtered;
}
/**
* Returns the list of articles for a given day, chronologically sorted
*

View file

@ -91,6 +91,10 @@ function endsWith($haystack, $needle, $case = true)
*/
function escape($input)
{
if (is_bool($input)) {
return $input;
}
if (is_array($input)) {
$out = array();
foreach($input as $key => $value) {

View file

@ -1609,7 +1609,15 @@ function($a, $b) { return $a['order'] - $b['order']; }
function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
{
// Used in templates
$searchtags = !empty($_GET['searchtags']) ? escape(normalize_spaces($_GET['searchtags'])) : '';
if (isset($_GET['searchtags'])) {
if (! empty($_GET['searchtags'])) {
$searchtags = escape(normalize_spaces($_GET['searchtags']));
} else {
$searchtags = false;
}
} else {
$searchtags = '';
}
$searchterm = !empty($_GET['searchterm']) ? escape(normalize_spaces($_GET['searchterm'])) : '';
// Smallhash filter
@ -1624,7 +1632,11 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
} else {
// Filter links according search parameters.
$visibility = ! empty($_SESSION['privateonly']) ? 'private' : 'all';
$linksToDisplay = $LINKSDB->filterSearch($_GET, false, $visibility);
$request = [
'searchtags' => $searchtags,
'searchterm' => $searchterm,
];
$linksToDisplay = $LINKSDB->filterSearch($request, false, $visibility);
}
// ---- Handle paging.
@ -1671,7 +1683,7 @@ function buildLinkList($PAGE,$LINKSDB, $conf, $pluginManager)
}
// Compute paging navigation
$searchtagsUrl = empty($searchtags) ? '' : '&searchtags=' . urlencode($searchtags);
$searchtagsUrl = $searchtags === '' ? '' : '&searchtags=' . urlencode($searchtags);
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
$previous_page_url = '';
if ($i != count($keys)) {

View file

@ -448,7 +448,7 @@ public function testFilterHashInValid()
public function testReorderLinksDesc()
{
self::$privateLinkDB->reorder('ASC');
$linkIds = array(42, 4, 1, 0, 7, 6, 8, 41);
$linkIds = array(42, 4, 9, 1, 0, 7, 6, 8, 41);
$cpt = 0;
foreach (self::$privateLinkDB as $key => $value) {
$this->assertEquals($linkIds[$cpt++], $key);

View file

@ -63,6 +63,12 @@ public function testFilter()
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, ''))
);
// Untagged only
$this->assertEquals(
self::$refDB->countUntaggedLinks(),
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, false))
);
$this->assertEquals(
ReferenceLinkDB::$NB_LINKS_TOTAL,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, ''))
@ -146,7 +152,7 @@ public function testFilterUnknownTag()
public function testFilterDay()
{
$this->assertEquals(
3,
4,
count(self::$linkFilter->filter(LinkFilter::$FILTER_DAY, '20121206'))
);
}
@ -339,7 +345,7 @@ public function testExcludeSearch()
);
$this->assertEquals(
7,
ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TEXT, '-revolution'))
);
}
@ -399,7 +405,7 @@ public function testTagFilterWithExclusion()
);
$this->assertEquals(
7,
ReferenceLinkDB::$NB_LINKS_TOTAL - 1,
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
);
}
@ -425,6 +431,13 @@ public function testFilterCrossedSearch()
array('', $terms)
))
);
$this->assertEquals(
1,
count(self::$linkFilter->filter(
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
array(false, 'PSR-2')
))
);
$this->assertEquals(
1,
count(self::$linkFilter->filter(

View file

@ -94,7 +94,7 @@ public function testGetLinks()
$this->assertEquals($this->refDB->countLinks(), count($data));
// Check order
$order = [41, 8, 6, 7, 0, 1, 4, 42];
$order = [41, 8, 6, 7, 0, 1, 9, 4, 42];
$cpt = 0;
foreach ($data as $link) {
$this->assertEquals(self::NB_FIELDS_LINK, count($link));
@ -163,7 +163,7 @@ public function testGetLinksLimitAll()
$data = json_decode((string) $response->getBody(), true);
$this->assertEquals($this->refDB->countLinks(), count($data));
// Check order
$order = [41, 8, 6, 7, 0, 1, 4, 42];
$order = [41, 8, 6, 7, 0, 1, 9, 4, 42];
$cpt = 0;
foreach ($data as $link) {
$this->assertEquals(self::NB_FIELDS_LINK, count($link));

View file

@ -80,7 +80,7 @@ public function testGetInfo()
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode((string) $response->getBody(), true);
$this->assertEquals(8, $data['global_counter']);
$this->assertEquals(\ReferenceLinkDB::$NB_LINKS_TOTAL, $data['global_counter']);
$this->assertEquals(2, $data['private_counter']);
$this->assertEquals('Shaarli', $data['settings']['title']);
$this->assertEquals('?', $data['settings']['header_link']);
@ -103,7 +103,7 @@ public function testGetInfo()
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode((string) $response->getBody(), true);
$this->assertEquals(8, $data['global_counter']);
$this->assertEquals(\ReferenceLinkDB::$NB_LINKS_TOTAL, $data['global_counter']);
$this->assertEquals(2, $data['private_counter']);
$this->assertEquals($title, $data['settings']['title']);
$this->assertEquals($headerLink, $data['settings']['header_link']);

View file

@ -4,7 +4,7 @@
*/
class ReferenceLinkDB
{
public static $NB_LINKS_TOTAL = 8;
public static $NB_LINKS_TOTAL = 9;
private $_links = array();
private $_publicCount = 0;
@ -37,6 +37,16 @@ public function __construct()
'ut'
);
$this->addLink(
9,
'PSR-2: Coding Style Guide',
'http://www.php-fig.org/psr/psr-2/',
'This guide extends and expands on PSR-1, the basic coding standard.',
0,
DateTime::createFromFormat(LinkDB::LINK_DATE_FORMAT, '20121206_152312'),
''
);
$this->addLink(
8,
'Free as in Freedom 2.0 @website',
@ -161,6 +171,20 @@ public function countPrivateLinks()
return $this->_privateCount;
}
/**
* Returns the number of links without tag
*/
public function countUntaggedLinks()
{
$cpt = 0;
foreach ($this->_links as $link) {
if (empty($link['tags'])) {
++$cpt;
}
}
return $cpt;
}
public function getLinks()
{
return $this->_links;

View file

@ -89,7 +89,7 @@
<div id="searchcriteria">{'Nothing found.'|t}</div>
</div>
</div>
{elseif="!empty($search_term) or !empty($search_tags) or !empty($visibility)"}
{elseif="!empty($search_term) or $search_tags !== '' or !empty($visibility)"}
<div class="pure-g pure-alert pure-alert-success search-result">
<div class="pure-u-2-24"></div>
<div class="pure-u-20-24">
@ -105,6 +105,10 @@
<a href="?removetag={function="urlencode($value)"}">{$value}<span class="remove"><i class="fa fa-times"></i></span></a>
</span>
{/loop}
{elseif="$search_tags === false"}
<span class="label label-tag" title="{'Remove tag'|t}">
<a href="?">{'untagged'|t}<span class="remove"><i class="fa fa-times"></i></span></a>
</span>
{/if}
{if="!empty($visibility)"}
{'with status'|t}

View file

@ -55,7 +55,7 @@
{if="count($links)==0"}
<div id="searchcriteria">Nothing found.</div>
{elseif="!empty($search_term) or !empty($search_tags)"}
{elseif="!empty($search_term) or $search_tags !== ''"}
<div id="searchcriteria">
{$result_count} results
{if="!empty($search_term)"}
@ -69,6 +69,10 @@
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
</span>
{/loop}
{elseif="$search_tags === false"}
<span class="linktag" title="Remove tag">
<a href="?">untagged <span class="remove">x</span></a>
</span>
{/if}
</div>
{/if}