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 @@ public function getLinkFromUrl($url)
|
||||||
public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
|
public function filter($type = '', $request = '', $casesensitive = false, $privateonly = false)
|
||||||
{
|
{
|
||||||
$linkFilter = new LinkFilter($this->_links);
|
$linkFilter = new LinkFilter($this->_links);
|
||||||
$requestFilter = is_array($request) ? implode(' ', $request) : $request;
|
return $linkFilter->filter($type, $request, $casesensitive, $privateonly);
|
||||||
return $linkFilter->filter($type, trim($requestFilter), $casesensitive, $privateonly);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -55,16 +55,25 @@ public function filter($type, $request, $casesensitive = false, $privateonly = f
|
||||||
switch($type) {
|
switch($type) {
|
||||||
case self::$FILTER_HASH:
|
case self::$FILTER_HASH:
|
||||||
return $this->filterSmallHash($request);
|
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:
|
case self::$FILTER_TEXT:
|
||||||
return $this->filterFulltext($request, $privateonly);
|
return $this->filterFulltext($request, $privateonly);
|
||||||
break;
|
|
||||||
case self::$FILTER_TAG:
|
case self::$FILTER_TAG:
|
||||||
return $this->filterTags($request, $casesensitive, $privateonly);
|
return $this->filterTags($request, $casesensitive, $privateonly);
|
||||||
break;
|
|
||||||
case self::$FILTER_DAY:
|
case self::$FILTER_DAY:
|
||||||
return $this->filterDay($request);
|
return $this->filterDay($request);
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
return $this->noFilter($privateonly);
|
return $this->noFilter($privateonly);
|
||||||
}
|
}
|
||||||
|
@ -138,6 +147,10 @@ private function filterSmallHash($smallHash)
|
||||||
*/
|
*/
|
||||||
private function filterFulltext($searchterms, $privateonly = false)
|
private function filterFulltext($searchterms, $privateonly = false)
|
||||||
{
|
{
|
||||||
|
if (empty($searchterms)) {
|
||||||
|
return $this->links;
|
||||||
|
}
|
||||||
|
|
||||||
$filtered = array();
|
$filtered = array();
|
||||||
$search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8');
|
$search = mb_convert_case(html_entity_decode($searchterms), MB_CASE_LOWER, 'UTF-8');
|
||||||
$exactRegex = '/"([^"]+)"/';
|
$exactRegex = '/"([^"]+)"/';
|
||||||
|
@ -219,6 +232,12 @@ private function filterFulltext($searchterms, $privateonly = false)
|
||||||
*/
|
*/
|
||||||
public function filterTags($tags, $casesensitive = false, $privateonly = false)
|
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);
|
$searchtags = self::tagsStrToArray($tags, $casesensitive);
|
||||||
$filtered = array();
|
$filtered = array();
|
||||||
if (empty($searchtags)) {
|
if (empty($searchtags)) {
|
||||||
|
|
|
@ -33,6 +33,10 @@ h1 {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
.bigbutton {
|
.bigbutton {
|
||||||
background-color: #c0c0c0;
|
background-color: #c0c0c0;
|
||||||
|
|
120
index.php
120
index.php
|
@ -623,7 +623,7 @@ private function initialize()
|
||||||
if (!empty($_GET['searchtags'])) {
|
if (!empty($_GET['searchtags'])) {
|
||||||
$searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']);
|
$searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']);
|
||||||
}
|
}
|
||||||
elseif (!empty($_GET['searchterm'])) {
|
if (!empty($_GET['searchterm'])) {
|
||||||
$searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']);
|
$searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']);
|
||||||
}
|
}
|
||||||
$this->tpl->assign('searchcrits', $searchcrits);
|
$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).
|
// Read links from database (and filter private links if user it not logged in).
|
||||||
|
|
||||||
// Optionally filter the results:
|
// Optionally filter the results:
|
||||||
if (!empty($_GET['searchterm'])) {
|
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
$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'])) {
|
elseif ($searchtags) {
|
||||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||||
|
}
|
||||||
|
elseif ($searchterm) {
|
||||||
|
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$linksToDisplay = $LINKSDB;
|
$linksToDisplay = $LINKSDB;
|
||||||
|
@ -807,11 +815,19 @@ function showATOM()
|
||||||
);
|
);
|
||||||
|
|
||||||
// Optionally filter the results:
|
// Optionally filter the results:
|
||||||
if (!empty($_GET['searchterm'])) {
|
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
$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'])) {
|
elseif ($searchtags) {
|
||||||
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||||
|
}
|
||||||
|
elseif ($searchterm) {
|
||||||
|
$linksToDisplay = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$linksToDisplay = $LINKSDB;
|
$linksToDisplay = $LINKSDB;
|
||||||
|
@ -1165,11 +1181,19 @@ function renderPage()
|
||||||
if ($targetPage == Router::$PAGE_PICWALL)
|
if ($targetPage == Router::$PAGE_PICWALL)
|
||||||
{
|
{
|
||||||
// Optionally filter the results:
|
// Optionally filter the results:
|
||||||
if (!empty($_GET['searchterm'])) {
|
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $_GET['searchterm']);
|
$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'])) {
|
elseif ($searchtags) {
|
||||||
$links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, trim($_GET['searchtags']));
|
$links = $LINKSDB->filter(LinkFilter::$FILTER_TAG, $searchtags);
|
||||||
|
}
|
||||||
|
elseif ($searchterm) {
|
||||||
|
$links = $LINKSDB->filter(LinkFilter::$FILTER_TEXT, $searchterm);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$links = $LINKSDB;
|
$links = $LINKSDB;
|
||||||
|
@ -1963,29 +1987,46 @@ function importFile()
|
||||||
// This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
|
// This function fills all the necessary fields in the $PAGE for the template 'linklist.html'
|
||||||
function buildLinkList($PAGE,$LINKSDB)
|
function buildLinkList($PAGE,$LINKSDB)
|
||||||
{
|
{
|
||||||
// ---- Filter link database according to parameters
|
// Filter link database according to parameters.
|
||||||
$search_type = '';
|
$searchtags = !empty($_GET['searchtags']) ? escape($_GET['searchtags']) : '';
|
||||||
$search_crits = '';
|
$searchterm = !empty($_GET['searchterm']) ? escape(trim($_GET['searchterm'])) : '';
|
||||||
$privateonly = !empty($_SESSION['privateonly']) ? true : false;
|
$privateonly = !empty($_SESSION['privateonly']) ? true : false;
|
||||||
|
|
||||||
// Fulltext search
|
// Search tags + fullsearch.
|
||||||
if (isset($_GET['searchterm'])) {
|
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||||
$search_crits = escape(trim($_GET['searchterm']));
|
$linksToDisplay = $LINKSDB->filter(
|
||||||
$search_type = LinkFilter::$FILTER_TEXT;
|
LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT,
|
||||||
$linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly);
|
array($searchtags, $searchterm),
|
||||||
|
false,
|
||||||
|
$privateonly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Search by tag
|
// Search by tags.
|
||||||
elseif (isset($_GET['searchtags'])) {
|
elseif (! empty($searchtags)) {
|
||||||
$search_crits = explode(' ', escape(trim($_GET['searchtags'])));
|
$linksToDisplay = $LINKSDB->filter(
|
||||||
$search_type = LinkFilter::$FILTER_TAG;
|
LinkFilter::$FILTER_TAG,
|
||||||
$linksToDisplay = $LINKSDB->filter($search_type, $search_crits, false, $privateonly);
|
$searchtags,
|
||||||
|
false,
|
||||||
|
$privateonly
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Fulltext search.
|
||||||
|
elseif (! empty($searchterm)) {
|
||||||
|
$linksToDisplay = $LINKSDB->filter(
|
||||||
|
LinkFilter::$FILTER_TEXT,
|
||||||
|
$searchterm,
|
||||||
|
false,
|
||||||
|
$privateonly
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Detect smallHashes in URL.
|
// Detect smallHashes in URL.
|
||||||
elseif (isset($_SERVER['QUERY_STRING'])
|
elseif (! empty($_SERVER['QUERY_STRING'])
|
||||||
&& preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/', $_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(
|
||||||
$linksToDisplay = $LINKSDB->filter($search_type, $search_crits);
|
LinkFilter::$FILTER_HASH,
|
||||||
|
substr(trim($_SERVER["QUERY_STRING"], '/'), 0, 6)
|
||||||
|
);
|
||||||
|
|
||||||
if (count($linksToDisplay) == 0) {
|
if (count($linksToDisplay) == 0) {
|
||||||
$PAGE->render404('The link you are trying to reach does not exist or has been deleted.');
|
$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
|
// Compute paging navigation
|
||||||
$searchterm = empty($_GET['searchterm']) ? '' : '&searchterm=' . $_GET['searchterm'];
|
$searchtagsUrl = empty($searchtags) ? '' : '&searchtags=' . urlencode($searchtags);
|
||||||
$searchtags = empty($_GET['searchtags']) ? '' : '&searchtags=' . $_GET['searchtags'];
|
$searchtermUrl = empty($searchterm) ? '' : '&searchterm=' . urlencode($searchterm);
|
||||||
$previous_page_url = '';
|
$previous_page_url = '';
|
||||||
if ($i != count($keys)) {
|
if ($i != count($keys)) {
|
||||||
$previous_page_url = '?page=' . ($page+1) . $searchterm . $searchtags;
|
$previous_page_url = '?page=' . ($page+1) . $searchtermUrl . $searchtagsUrl;
|
||||||
}
|
}
|
||||||
$next_page_url='';
|
$next_page_url='';
|
||||||
if ($page>1) {
|
if ($page>1) {
|
||||||
$next_page_url = '?page=' . ($page-1) . $searchterm . $searchtags;
|
$next_page_url = '?page=' . ($page-1) . $searchtermUrl . $searchtagsUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = '';
|
$token = isLoggedIn() ? getToken() : '';
|
||||||
if (isLoggedIn()) {
|
|
||||||
$token = getToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill all template fields.
|
// Fill all template fields.
|
||||||
$data = array(
|
$data = array(
|
||||||
|
@ -2065,8 +2103,8 @@ function buildLinkList($PAGE,$LINKSDB)
|
||||||
'page_current' => $page,
|
'page_current' => $page,
|
||||||
'page_max' => $pagecount,
|
'page_max' => $pagecount,
|
||||||
'result_count' => count($linksToDisplay),
|
'result_count' => count($linksToDisplay),
|
||||||
'search_type' => $search_type,
|
'search_term' => $searchterm,
|
||||||
'search_crits' => $search_crits,
|
'search_tags' => $searchtags,
|
||||||
'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL.
|
'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'], // Optional redirector URL.
|
||||||
'token' => $token,
|
'token' => $token,
|
||||||
'links' => $linkDisp,
|
'links' => $linkDisp,
|
||||||
|
|
|
@ -12,6 +12,8 @@ class LinkFilterTest extends PHPUnit_Framework_TestCase
|
||||||
*/
|
*/
|
||||||
protected static $linkFilter;
|
protected static $linkFilter;
|
||||||
|
|
||||||
|
protected static $NB_LINKS_REFDB = 7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instanciate linkFilter with ReferenceLinkDB data.
|
* Instanciate linkFilter with ReferenceLinkDB data.
|
||||||
*/
|
*/
|
||||||
|
@ -27,7 +29,7 @@ public static function setUpBeforeClass()
|
||||||
public function testFilter()
|
public function testFilter()
|
||||||
{
|
{
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
7,
|
self::$NB_LINKS_REFDB,
|
||||||
count(self::$linkFilter->filter('', ''))
|
count(self::$linkFilter->filter('', ''))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -36,6 +38,16 @@ public function testFilter()
|
||||||
2,
|
2,
|
||||||
count(self::$linkFilter->filter('', '', false, true))
|
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 @@ public function testTagFilterWithExclusion()
|
||||||
count(self::$linkFilter->filter(LinkFilter::$FILTER_TAG, '-free'))
|
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">
|
<div id="headerform" class="search">
|
||||||
<form method="GET" class="searchform" name="searchform">
|
<form method="GET" class="searchform" name="searchform">
|
||||||
<input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="Search text"
|
<input type="text" tabindex="1" id="searchform_value" name="searchterm" placeholder="Search text"
|
||||||
{if="!empty($search_crits) && $search_type=='fulltext'"}
|
{if="!empty($search_term)"}
|
||||||
value="{$search_crits}"
|
value="{$search_term}"
|
||||||
{/if}
|
{/if}
|
||||||
>
|
>
|
||||||
<input type="submit" value="Search" class="bigbutton">
|
<input type="submit" value="Search" class="bigbutton">
|
||||||
</form>
|
</form>
|
||||||
<form method="GET" class="tagfilter" name="tagfilter">
|
<form method="GET" class="tagfilter" name="tagfilter">
|
||||||
<input type="text" tabindex="2" name="searchtags" id="tagfilter_value" placeholder="Filter by tag"
|
<input type="text" tabindex="2" name="searchtags" id="tagfilter_value" placeholder="Filter by tag"
|
||||||
{if="!empty($search_crits) && $search_type=='tags'"}
|
{if="!empty($search_tags)"}
|
||||||
value="{function="implode(' ', $search_crits)"}"
|
value="{$search_tags}"
|
||||||
{/if}
|
{/if}
|
||||||
autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
|
autocomplete="off" class="awesomplete" data-multiple data-minChars="1"
|
||||||
data-list="{loop="$tags"}{$key}, {/loop}"
|
data-list="{loop="$tags"}{$key}, {/loop}"
|
||||||
|
@ -44,19 +44,23 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{if="count($links)==0"}
|
{if="count($links)==0"}
|
||||||
<div id="searchcriteria">Nothing found.</i></div>
|
<div id="searchcriteria">Nothing found.</div>
|
||||||
{else}
|
{elseif="!empty($search_term) or !empty($search_tags)"}
|
||||||
{if="$search_type=='fulltext'"}
|
<div id="searchcriteria">
|
||||||
<div id="searchcriteria">{$result_count} results for <i>{$search_crits}</i></div>
|
{$result_count} results
|
||||||
{/if}
|
{if="!empty($search_term)"}
|
||||||
{if="$search_type=='tags'"}
|
for <em>{$search_term}</em>
|
||||||
<div id="searchcriteria">{$result_count} results for tags <i>
|
{/if}
|
||||||
{loop="search_crits"}
|
{if="!empty($search_tags)"}
|
||||||
<span class="linktag" title="Remove tag">
|
{$exploded_tags=explode(' ', $search_tags)}
|
||||||
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
|
tagged
|
||||||
</span>
|
{loop="$exploded_tags"}
|
||||||
{/loop}</i></div>
|
<span class="linktag" title="Remove tag">
|
||||||
{/if}
|
<a href="?removetag={function="urlencode($value)"}">{$value} <span class="remove">x</span></a>
|
||||||
|
</span>
|
||||||
|
{/loop}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<ul>
|
<ul>
|
||||||
{loop="links"}
|
{loop="links"}
|
||||||
|
|
Loading…
Reference in a new issue