Hashtag system
* Hashtag are auto-linked with a filter search * Supports unicode * Compatible with markdown (excluded in code blocks)
This commit is contained in:
parent
bb9ca54838
commit
9ccca40189
10 changed files with 271 additions and 104 deletions
|
@ -409,7 +409,7 @@ public function filterSearch($filterRequest = array(), $casesensitive = false, $
|
||||||
$searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
$searchterm = !empty($filterRequest['searchterm']) ? escape($filterRequest['searchterm']) : '';
|
||||||
|
|
||||||
// Search tags + fullsearch.
|
// Search tags + fullsearch.
|
||||||
if (empty($type) && ! empty($searchtags) && ! empty($searchterm)) {
|
if (! empty($searchtags) && ! empty($searchterm)) {
|
||||||
$type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
|
$type = LinkFilter::$FILTER_TAG | LinkFilter::$FILTER_TEXT;
|
||||||
$request = array($searchtags, $searchterm);
|
$request = array($searchtags, $searchterm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,11 @@ class LinkFilter
|
||||||
*/
|
*/
|
||||||
public static $FILTER_DAY = 'FILTER_DAY';
|
public static $FILTER_DAY = 'FILTER_DAY';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Allowed characters for hashtags (regex syntax).
|
||||||
|
*/
|
||||||
|
public static $HASHTAG_CHARS = '\p{Pc}\p{N}\p{L}\p{Mn}';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array all available links.
|
* @var array all available links.
|
||||||
*/
|
*/
|
||||||
|
@ -263,8 +268,10 @@ public function filterTags($tags, $casesensitive = false, $privateonly = false)
|
||||||
for ($i = 0 ; $i < count($searchtags) && $found; $i++) {
|
for ($i = 0 ; $i < count($searchtags) && $found; $i++) {
|
||||||
// Exclusive search, quit if tag found.
|
// Exclusive search, quit if tag found.
|
||||||
// Or, tag not found in the link, quit.
|
// Or, tag not found in the link, quit.
|
||||||
if (($searchtags[$i][0] == '-' && in_array(substr($searchtags[$i], 1), $linktags))
|
if (($searchtags[$i][0] == '-'
|
||||||
|| ($searchtags[$i][0] != '-') && ! in_array($searchtags[$i], $linktags)
|
&& $this->searchTagAndHashTag(substr($searchtags[$i], 1), $linktags, $link['description']))
|
||||||
|
|| ($searchtags[$i][0] != '-')
|
||||||
|
&& ! $this->searchTagAndHashTag($searchtags[$i], $linktags, $link['description'])
|
||||||
) {
|
) {
|
||||||
$found = false;
|
$found = false;
|
||||||
}
|
}
|
||||||
|
@ -306,6 +313,28 @@ public function filterDay($day)
|
||||||
return $filtered;
|
return $filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tag is found in the taglist, or as an hashtag in the link description.
|
||||||
|
*
|
||||||
|
* @param string $tag Tag to search.
|
||||||
|
* @param array $taglist List of tags for the current link.
|
||||||
|
* @param string $description Link description.
|
||||||
|
*
|
||||||
|
* @return bool True if found, false otherwise.
|
||||||
|
*/
|
||||||
|
protected function searchTagAndHashTag($tag, $taglist, $description)
|
||||||
|
{
|
||||||
|
if (in_array($tag, $taglist)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('/(^| )#'. $tag .'([^'. self::$HASHTAG_CHARS .']|$)/mui', $description) > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a list of tags (str) to an array. Also
|
* Convert a list of tags (str) to an array. Also
|
||||||
* - handle case sensitivity.
|
* - handle case sensitivity.
|
||||||
|
|
|
@ -91,5 +91,80 @@ function count_private($links)
|
||||||
foreach ($links as $link) {
|
foreach ($links as $link) {
|
||||||
$cpt = $link['private'] == true ? $cpt + 1 : $cpt;
|
$cpt = $link['private'] == true ? $cpt + 1 : $cpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $cpt;
|
return $cpt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In a string, converts URLs to clickable links.
|
||||||
|
*
|
||||||
|
* @param string $text input string.
|
||||||
|
* @param string $redirector if a redirector is set, use it to gerenate links.
|
||||||
|
*
|
||||||
|
* @return string returns $text with all links converted to HTML links.
|
||||||
|
*
|
||||||
|
* @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
|
||||||
|
*/
|
||||||
|
function text2clickable($text, $redirector = '')
|
||||||
|
{
|
||||||
|
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
|
||||||
|
|
||||||
|
if (empty($redirector)) {
|
||||||
|
return preg_replace($regex, '<a href="$1">$1</a>', $text);
|
||||||
|
}
|
||||||
|
// Redirector is set, urlencode the final URL.
|
||||||
|
return preg_replace_callback(
|
||||||
|
$regex,
|
||||||
|
function ($matches) use ($redirector) {
|
||||||
|
return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
|
||||||
|
},
|
||||||
|
$text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-link hashtags.
|
||||||
|
*
|
||||||
|
* @param string $description Given description.
|
||||||
|
* @param string $indexUrl Root URL.
|
||||||
|
*
|
||||||
|
* @return string Description with auto-linked hashtags.
|
||||||
|
*/
|
||||||
|
function hashtag_autolink($description, $indexUrl = '')
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* To support unicode: http://stackoverflow.com/a/35498078/1484919
|
||||||
|
* \p{Pc} - to match underscore
|
||||||
|
* \p{N} - numeric character in any script
|
||||||
|
* \p{L} - letter from any language
|
||||||
|
* \p{Mn} - any non marking space (accents, umlauts, etc)
|
||||||
|
*/
|
||||||
|
$regex = '/(^|\s)#([\p{Pc}\p{N}\p{L}\p{Mn}]+)/mui';
|
||||||
|
$replacement = '$1<a href="'. $indexUrl .'?addtag=$2" title="Hashtag $2">#$2</a>';
|
||||||
|
return preg_replace($regex, $replacement, $description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function inserts where relevant so that multiple spaces are properly displayed in HTML
|
||||||
|
* even in the absence of <pre> (This is used in description to keep text formatting).
|
||||||
|
*
|
||||||
|
* @param string $text input text.
|
||||||
|
*
|
||||||
|
* @return string formatted text.
|
||||||
|
*/
|
||||||
|
function space2nbsp($text)
|
||||||
|
{
|
||||||
|
return preg_replace('/(^| ) /m', '$1 ', $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format Shaarli's description
|
||||||
|
*
|
||||||
|
* @param string $description shaare's description.
|
||||||
|
* @param string $redirector if a redirector is set, use it to gerenate links.
|
||||||
|
*
|
||||||
|
* @return string formatted description.
|
||||||
|
*/
|
||||||
|
function format_description($description, $redirector = '', $indexUrl = '') {
|
||||||
|
return nl2br(space2nbsp(hashtag_autolink(text2clickable($description, $redirector), $indexUrl)));
|
||||||
|
}
|
||||||
|
|
|
@ -197,59 +197,6 @@ function is_session_id_valid($sessionId)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* In a string, converts URLs to clickable links.
|
|
||||||
*
|
|
||||||
* @param string $text input string.
|
|
||||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
|
||||||
*
|
|
||||||
* @return string returns $text with all links converted to HTML links.
|
|
||||||
*
|
|
||||||
* @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
|
|
||||||
*/
|
|
||||||
function text2clickable($text, $redirector)
|
|
||||||
{
|
|
||||||
$regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
|
|
||||||
|
|
||||||
if (empty($redirector)) {
|
|
||||||
return preg_replace($regex, '<a href="$1">$1</a>', $text);
|
|
||||||
}
|
|
||||||
// Redirector is set, urlencode the final URL.
|
|
||||||
return preg_replace_callback(
|
|
||||||
$regex,
|
|
||||||
function ($matches) use ($redirector) {
|
|
||||||
return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
|
|
||||||
},
|
|
||||||
$text
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This function inserts where relevant so that multiple spaces are properly displayed in HTML
|
|
||||||
* even in the absence of <pre> (This is used in description to keep text formatting).
|
|
||||||
*
|
|
||||||
* @param string $text input text.
|
|
||||||
*
|
|
||||||
* @return string formatted text.
|
|
||||||
*/
|
|
||||||
function space2nbsp($text)
|
|
||||||
{
|
|
||||||
return preg_replace('/(^| ) /m', '$1 ', $text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format Shaarli's description
|
|
||||||
* TODO: Move me to ApplicationUtils when it's ready.
|
|
||||||
*
|
|
||||||
* @param string $description shaare's description.
|
|
||||||
* @param string $redirector if a redirector is set, use it to gerenate links.
|
|
||||||
*
|
|
||||||
* @return string formatted description.
|
|
||||||
*/
|
|
||||||
function format_description($description, $redirector = false) {
|
|
||||||
return nl2br(space2nbsp(text2clickable($description, $redirector)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sniff browser language to set the locale automatically.
|
* Sniff browser language to set the locale automatically.
|
||||||
* Note that is may not work on your server if the corresponding locale is not installed.
|
* Note that is may not work on your server if the corresponding locale is not installed.
|
||||||
|
|
|
@ -151,7 +151,44 @@ function hook_markdown_render_editlink($data)
|
||||||
*/
|
*/
|
||||||
function reverse_text2clickable($description)
|
function reverse_text2clickable($description)
|
||||||
{
|
{
|
||||||
return preg_replace('!<a +href="([^ ]*)">[^ ]+</a>!m', '$1', $description);
|
$descriptionLines = explode(PHP_EOL, $description);
|
||||||
|
$descriptionOut = '';
|
||||||
|
$codeBlockOn = false;
|
||||||
|
$lineCount = 0;
|
||||||
|
|
||||||
|
foreach ($descriptionLines as $descriptionLine) {
|
||||||
|
// Detect line of code
|
||||||
|
$codeLineOn = preg_match('/^ /', $descriptionLine) > 0;
|
||||||
|
// Detect and toggle block of code
|
||||||
|
if (!$codeBlockOn) {
|
||||||
|
$codeBlockOn = preg_match('/^```/', $descriptionLine) > 0;
|
||||||
|
}
|
||||||
|
elseif (preg_match('/^```/', $descriptionLine) > 0) {
|
||||||
|
$codeBlockOn = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hashtagTitle = ' title="Hashtag [^"]+"';
|
||||||
|
// Reverse `inline code` hashtags.
|
||||||
|
$descriptionLine = preg_replace(
|
||||||
|
'!(`[^`\n]*)<a href="[^ ]*"'. $hashtagTitle .'>([^<]+)</a>([^`\n]*`)!m',
|
||||||
|
'$1$2$3',
|
||||||
|
$descriptionLine
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reverse hashtag links if we're in a code block.
|
||||||
|
$hashtagFilter = ($codeBlockOn || $codeLineOn) ? $hashtagTitle : '';
|
||||||
|
$descriptionLine = preg_replace(
|
||||||
|
'!<a href="[^ ]*"'. $hashtagFilter .'>([^<]+)</a>!m',
|
||||||
|
'$1',
|
||||||
|
$descriptionLine
|
||||||
|
);
|
||||||
|
|
||||||
|
$descriptionOut .= $descriptionLine;
|
||||||
|
if ($lineCount++ < count($descriptionLines) - 1) {
|
||||||
|
$descriptionOut .= PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $descriptionOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,9 +263,9 @@ function process_markdown($description)
|
||||||
$parsedown = new Parsedown();
|
$parsedown = new Parsedown();
|
||||||
|
|
||||||
$processedDescription = $description;
|
$processedDescription = $description;
|
||||||
$processedDescription = reverse_text2clickable($processedDescription);
|
|
||||||
$processedDescription = reverse_nl2br($processedDescription);
|
$processedDescription = reverse_nl2br($processedDescription);
|
||||||
$processedDescription = reverse_space2nbsp($processedDescription);
|
$processedDescription = reverse_space2nbsp($processedDescription);
|
||||||
|
$processedDescription = reverse_text2clickable($processedDescription);
|
||||||
$processedDescription = unescape($processedDescription);
|
$processedDescription = unescape($processedDescription);
|
||||||
$processedDescription = $parsedown
|
$processedDescription = $parsedown
|
||||||
->setMarkupEscaped(false)
|
->setMarkupEscaped(false)
|
||||||
|
|
|
@ -256,7 +256,7 @@ public function testGetKnownLinkFromURL()
|
||||||
$link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
|
$link = self::$publicLinkDB->getLinkFromUrl('http://mediagoblin.org/');
|
||||||
|
|
||||||
$this->assertNotEquals(false, $link);
|
$this->assertNotEquals(false, $link);
|
||||||
$this->assertEquals(
|
$this->assertContains(
|
||||||
'A free software media publishing platform',
|
'A free software media publishing platform',
|
||||||
$link['description']
|
$link['description']
|
||||||
);
|
);
|
||||||
|
@ -293,6 +293,7 @@ public function testAllTags()
|
||||||
// The DB contains a link with `sTuff` and another one with `stuff` tag.
|
// The DB contains a link with `sTuff` and another one with `stuff` tag.
|
||||||
// They need to be grouped with the first case found (`sTuff`).
|
// They need to be grouped with the first case found (`sTuff`).
|
||||||
'sTuff' => 2,
|
'sTuff' => 2,
|
||||||
|
'hashtag' => 2,
|
||||||
),
|
),
|
||||||
self::$publicLinkDB->allTags()
|
self::$publicLinkDB->allTags()
|
||||||
);
|
);
|
||||||
|
@ -315,6 +316,7 @@ public function testAllTags()
|
||||||
'sTuff' => 2,
|
'sTuff' => 2,
|
||||||
'-exclude' => 1,
|
'-exclude' => 1,
|
||||||
'.hidden' => 1,
|
'.hidden' => 1,
|
||||||
|
'hashtag' => 2,
|
||||||
),
|
),
|
||||||
self::$privateLinkDB->allTags()
|
self::$privateLinkDB->allTags()
|
||||||
);
|
);
|
||||||
|
|
|
@ -387,4 +387,30 @@ public function testFilterCrossedSearch()
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter links by #hashtag.
|
||||||
|
*/
|
||||||
|
public function testFilterByHashtag()
|
||||||
|
{
|
||||||
|
$hashtag = 'hashtag';
|
||||||
|
$this->assertEquals(
|
||||||
|
3,
|
||||||
|
count(self::$linkFilter->filter(
|
||||||
|
LinkFilter::$FILTER_TAG,
|
||||||
|
$hashtag
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
$hashtag = 'private';
|
||||||
|
$this->assertEquals(
|
||||||
|
1,
|
||||||
|
count(self::$linkFilter->filter(
|
||||||
|
LinkFilter::$FILTER_TAG,
|
||||||
|
$hashtag,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,4 +93,92 @@ public function testCountPrivateLinks()
|
||||||
$refDB = new ReferenceLinkDB();
|
$refDB = new ReferenceLinkDB();
|
||||||
$this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks()));
|
$this->assertEquals($refDB->countPrivateLinks(), count_private($refDB->getLinks()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test text2clickable without a redirector being set.
|
||||||
|
*/
|
||||||
|
public function testText2clickableWithoutRedirector()
|
||||||
|
{
|
||||||
|
$text = 'stuff http://hello.there/is=someone#here otherstuff';
|
||||||
|
$expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
|
||||||
|
$processedText = text2clickable($text, '');
|
||||||
|
$this->assertEquals($expectedText, $processedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test text2clickable a redirector set.
|
||||||
|
*/
|
||||||
|
public function testText2clickableWithRedirector()
|
||||||
|
{
|
||||||
|
$text = 'stuff http://hello.there/is=someone#here otherstuff';
|
||||||
|
$redirector = 'http://redirector.to';
|
||||||
|
$expectedText = 'stuff <a href="'.
|
||||||
|
$redirector .
|
||||||
|
urlencode('http://hello.there/is=someone#here') .
|
||||||
|
'">http://hello.there/is=someone#here</a> otherstuff';
|
||||||
|
$processedText = text2clickable($text, $redirector);
|
||||||
|
$this->assertEquals($expectedText, $processedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test testSpace2nbsp.
|
||||||
|
*/
|
||||||
|
public function testSpace2nbsp()
|
||||||
|
{
|
||||||
|
$text = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
|
||||||
|
$expectedText = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
|
||||||
|
$processedText = space2nbsp($text);
|
||||||
|
$this->assertEquals($expectedText, $processedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hashtags auto-link.
|
||||||
|
*/
|
||||||
|
public function testHashtagAutolink()
|
||||||
|
{
|
||||||
|
$index = 'http://domain.tld/';
|
||||||
|
$rawDescription = '#hashtag\n
|
||||||
|
# nothashtag\n
|
||||||
|
test#nothashtag #hashtag \#nothashtag\n
|
||||||
|
test #hashtag #hashtag test #hashtag.test\n
|
||||||
|
#hashtag #hashtag-nothashtag #hashtag_hashtag\n
|
||||||
|
What is #ашок anyway?\n
|
||||||
|
カタカナ #カタカナ」カタカナ\n';
|
||||||
|
$autolinkedDescription = hashtag_autolink($rawDescription, $index);
|
||||||
|
|
||||||
|
$this->assertContains($this->getHashtagLink('hashtag', $index), $autolinkedDescription);
|
||||||
|
$this->assertNotContains(' #hashtag', $autolinkedDescription);
|
||||||
|
$this->assertNotContains('>#nothashtag', $autolinkedDescription);
|
||||||
|
$this->assertContains($this->getHashtagLink('ашок', $index), $autolinkedDescription);
|
||||||
|
$this->assertContains($this->getHashtagLink('カタカナ', $index), $autolinkedDescription);
|
||||||
|
$this->assertContains($this->getHashtagLink('hashtag_hashtag', $index), $autolinkedDescription);
|
||||||
|
$this->assertNotContains($this->getHashtagLink('hashtag-nothashtag', $index), $autolinkedDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test hashtags auto-link without index URL.
|
||||||
|
*/
|
||||||
|
public function testHashtagAutolinkNoIndex()
|
||||||
|
{
|
||||||
|
$rawDescription = 'blabla #hashtag x#nothashtag';
|
||||||
|
$autolinkedDescription = hashtag_autolink($rawDescription);
|
||||||
|
|
||||||
|
$this->assertContains($this->getHashtagLink('hashtag'), $autolinkedDescription);
|
||||||
|
$this->assertNotContains(' #hashtag', $autolinkedDescription);
|
||||||
|
$this->assertNotContains('>#nothashtag', $autolinkedDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Util function to build an hashtag link.
|
||||||
|
*
|
||||||
|
* @param string $hashtag Hashtag name.
|
||||||
|
* @param string $index Index URL.
|
||||||
|
*
|
||||||
|
* @return string HTML hashtag link.
|
||||||
|
*/
|
||||||
|
private function getHashtagLink($hashtag, $index = '')
|
||||||
|
{
|
||||||
|
$hashtagLink = '<a href="'. $index .'?addtag=$1" title="Hashtag $1">#$1</a>';
|
||||||
|
return str_replace('$1', $hashtag, $hashtagLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,41 +253,4 @@ public function testIsSessionIdInvalid()
|
||||||
is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
|
is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test text2clickable without a redirector being set.
|
|
||||||
*/
|
|
||||||
public function testText2clickableWithoutRedirector()
|
|
||||||
{
|
|
||||||
$text = 'stuff http://hello.there/is=someone#here otherstuff';
|
|
||||||
$expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
|
|
||||||
$processedText = text2clickable($text, '');
|
|
||||||
$this->assertEquals($expectedText, $processedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test text2clickable a redirector set.
|
|
||||||
*/
|
|
||||||
public function testText2clickableWithRedirector()
|
|
||||||
{
|
|
||||||
$text = 'stuff http://hello.there/is=someone#here otherstuff';
|
|
||||||
$redirector = 'http://redirector.to';
|
|
||||||
$expectedText = 'stuff <a href="'.
|
|
||||||
$redirector .
|
|
||||||
urlencode('http://hello.there/is=someone#here') .
|
|
||||||
'">http://hello.there/is=someone#here</a> otherstuff';
|
|
||||||
$processedText = text2clickable($text, $redirector);
|
|
||||||
$this->assertEquals($expectedText, $processedText);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test testSpace2nbsp.
|
|
||||||
*/
|
|
||||||
public function testSpace2nbsp()
|
|
||||||
{
|
|
||||||
$text = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
|
|
||||||
$expectedText = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
|
|
||||||
$processedText = space2nbsp($text);
|
|
||||||
$this->assertEquals($expectedText, $processedText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ function __construct()
|
||||||
$this->addLink(
|
$this->addLink(
|
||||||
'Link title: @website',
|
'Link title: @website',
|
||||||
'?WDWyig',
|
'?WDWyig',
|
||||||
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this.',
|
'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
|
||||||
0,
|
0,
|
||||||
'20150310_114651',
|
'20150310_114651',
|
||||||
'sTuff'
|
'sTuff'
|
||||||
|
@ -27,25 +27,25 @@ function __construct()
|
||||||
$this->addLink(
|
$this->addLink(
|
||||||
'Free as in Freedom 2.0 @website',
|
'Free as in Freedom 2.0 @website',
|
||||||
'https://static.fsf.org/nosvn/faif-2.0.pdf',
|
'https://static.fsf.org/nosvn/faif-2.0.pdf',
|
||||||
'Richard Stallman and the Free Software Revolution. Read this.',
|
'Richard Stallman and the Free Software Revolution. Read this. #hashtag',
|
||||||
0,
|
0,
|
||||||
'20150310_114633',
|
'20150310_114633',
|
||||||
'free gnu software stallman -exclude stuff'
|
'free gnu software stallman -exclude stuff hashtag'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->addLink(
|
$this->addLink(
|
||||||
'MediaGoblin',
|
'MediaGoblin',
|
||||||
'http://mediagoblin.org/',
|
'http://mediagoblin.org/',
|
||||||
'A free software media publishing platform',
|
'A free software media publishing platform #hashtagOther',
|
||||||
0,
|
0,
|
||||||
'20130614_184135',
|
'20130614_184135',
|
||||||
'gnu media web .hidden'
|
'gnu media web .hidden hashtag'
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->addLink(
|
$this->addLink(
|
||||||
'w3c-markup-validator',
|
'w3c-markup-validator',
|
||||||
'https://dvcs.w3.org/hg/markup-validator/summary',
|
'https://dvcs.w3.org/hg/markup-validator/summary',
|
||||||
'Mercurial repository for the W3C Validator',
|
'Mercurial repository for the W3C Validator #private',
|
||||||
1,
|
1,
|
||||||
'20141125_084734',
|
'20141125_084734',
|
||||||
'css html w3c web Mercurial'
|
'css html w3c web Mercurial'
|
||||||
|
@ -54,7 +54,7 @@ function __construct()
|
||||||
$this->addLink(
|
$this->addLink(
|
||||||
'UserFriendly - Web Designer',
|
'UserFriendly - Web Designer',
|
||||||
'http://ars.userfriendly.org/cartoons/?id=20121206',
|
'http://ars.userfriendly.org/cartoons/?id=20121206',
|
||||||
'Naming conventions...',
|
'Naming conventions... #private',
|
||||||
0,
|
0,
|
||||||
'20121206_142300',
|
'20121206_142300',
|
||||||
'dev cartoon web'
|
'dev cartoon web'
|
||||||
|
|
Loading…
Reference in a new issue