aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--application/LinkDB.php20
-rw-r--r--application/Utils.php53
-rw-r--r--index.php48
-rw-r--r--plugins/qrcode/qrcode.php2
-rw-r--r--tests/LinkDBTest.php23
-rw-r--r--tests/UtilsTest.php37
-rw-r--r--tests/plugins/PlugQrcodeTest.php4
-rw-r--r--tpl/daily.html2
-rw-r--r--tpl/linklist.html6
-rw-r--r--tpl/picwall.html2
10 files changed, 160 insertions, 37 deletions
diff --git a/application/LinkDB.php b/application/LinkDB.php
index 15fadbc3..f771ac8b 100644
--- a/application/LinkDB.php
+++ b/application/LinkDB.php
@@ -57,18 +57,25 @@ class LinkDB implements Iterator, Countable, ArrayAccess
57 // Hide public links 57 // Hide public links
58 private $_hidePublicLinks; 58 private $_hidePublicLinks;
59 59
60 // link redirector set in user settings.
61 private $_redirector;
62
60 /** 63 /**
61 * Creates a new LinkDB 64 * Creates a new LinkDB
62 * 65 *
63 * Checks if the datastore exists; else, attempts to create a dummy one. 66 * Checks if the datastore exists; else, attempts to create a dummy one.
64 * 67 *
65 * @param $isLoggedIn is the user logged in? 68 * @param string $datastore datastore file path.
69 * @param boolean $isLoggedIn is the user logged in?
70 * @param boolean $hidePublicLinks if true all links are private.
71 * @param string $redirector link redirector set in user settings.
66 */ 72 */
67 function __construct($datastore, $isLoggedIn, $hidePublicLinks) 73 function __construct($datastore, $isLoggedIn, $hidePublicLinks, $redirector = '')
68 { 74 {
69 $this->_datastore = $datastore; 75 $this->_datastore = $datastore;
70 $this->_loggedIn = $isLoggedIn; 76 $this->_loggedIn = $isLoggedIn;
71 $this->_hidePublicLinks = $hidePublicLinks; 77 $this->_hidePublicLinks = $hidePublicLinks;
78 $this->_redirector = $redirector;
72 $this->_checkDB(); 79 $this->_checkDB();
73 $this->_readDB(); 80 $this->_readDB();
74 } 81 }
@@ -259,7 +266,14 @@ You use the community supported version of the original Shaarli project, by Seba
259 266
260 // Escape links data 267 // Escape links data
261 foreach($this->_links as &$link) { 268 foreach($this->_links as &$link) {
262 sanitizeLink($link); 269 sanitizeLink($link);
270 // Do not use the redirector for internal links (Shaarli note URL starting with a '?').
271 if (!empty($this->_redirector) && !startsWith($link['url'], '?')) {
272 $link['real_url'] = $this->_redirector . urlencode($link['url']);
273 }
274 else {
275 $link['real_url'] = $link['url'];
276 }
263 } 277 }
264 } 278 }
265 279
diff --git a/application/Utils.php b/application/Utils.php
index b8579b48..f84f70e4 100644
--- a/application/Utils.php
+++ b/application/Utils.php
@@ -148,3 +148,56 @@ function is_session_id_valid($sessionId)
148 148
149 return true; 149 return true;
150} 150}
151
152/**
153 * In a string, converts URLs to clickable links.
154 *
155 * @param string $text input string.
156 * @param string $redirector if a redirector is set, use it to gerenate links.
157 *
158 * @return string returns $text with all links converted to HTML links.
159 *
160 * @see Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
161 */
162function text2clickable($text, $redirector)
163{
164 $regex = '!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si';
165
166 if (empty($redirector)) {
167 return preg_replace($regex, '<a href="$1">$1</a>', $text);
168 }
169 // Redirector is set, urlencode the final URL.
170 return preg_replace_callback(
171 $regex,
172 function ($matches) use ($redirector) {
173 return '<a href="' . $redirector . urlencode($matches[1]) .'">'. $matches[1] .'</a>';
174 },
175 $text
176 );
177}
178
179/**
180 * This function inserts &nbsp; where relevant so that multiple spaces are properly displayed in HTML
181 * even in the absence of <pre> (This is used in description to keep text formatting).
182 *
183 * @param string $text input text.
184 *
185 * @return string formatted text.
186 */
187function space2nbsp($text)
188{
189 return preg_replace('/(^| ) /m', '$1&nbsp;', $text);
190}
191
192/**
193 * Format Shaarli's description
194 * TODO: Move me to ApplicationUtils when it's ready.
195 *
196 * @param string $description shaare's description.
197 * @param string $redirector if a redirector is set, use it to gerenate links.
198 *
199 * @return string formatted description.
200 */
201function format_description($description, $redirector) {
202 return nl2br(space2nbsp(text2clickable($description, $redirector)));
203}
diff --git a/index.php b/index.php
index bb00c11d..3954be97 100644
--- a/index.php
+++ b/index.php
@@ -340,21 +340,6 @@ function logm($message)
340 file_put_contents($GLOBALS['config']['LOG_FILE'], $t, FILE_APPEND); 340 file_put_contents($GLOBALS['config']['LOG_FILE'], $t, FILE_APPEND);
341} 341}
342 342
343// In a string, converts URLs to clickable links.
344// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
345function text2clickable($url)
346{
347 $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'];
348 return preg_replace('!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si','<a href="'.$redir.'$1" rel="nofollow">$1</a>',$url);
349}
350
351// This function inserts &nbsp; where relevant so that multiple spaces are properly displayed in HTML
352// even in the absence of <pre> (This is used in description to keep text formatting)
353function keepMultipleSpaces($text)
354{
355 return str_replace(' ',' &nbsp;',$text);
356
357}
358// ------------------------------------------------------------------------------------------ 343// ------------------------------------------------------------------------------------------
359// Sniff browser language to display dates in the right format automatically. 344// Sniff browser language to display dates in the right format automatically.
360// (Note that is may not work on your server if the corresponding local is not installed.) 345// (Note that is may not work on your server if the corresponding local is not installed.)
@@ -746,7 +731,8 @@ function showRSS()
746 $LINKSDB = new LinkDB( 731 $LINKSDB = new LinkDB(
747 $GLOBALS['config']['DATASTORE'], 732 $GLOBALS['config']['DATASTORE'],
748 isLoggedIn(), 733 isLoggedIn(),
749 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 734 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
735 $GLOBALS['redirector']
750 ); 736 );
751 // Read links from database (and filter private links if user it not logged in). 737 // Read links from database (and filter private links if user it not logged in).
752 738
@@ -797,7 +783,9 @@ function showRSS()
797 // If user wants permalinks first, put the final link in description 783 // If user wants permalinks first, put the final link in description
798 if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)'; 784 if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
799 if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink; 785 if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
800 echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable($link['description']))).$descriptionlink.']]></description>'."\n</item>\n"; 786 echo '<description><![CDATA['.
787 format_description($link['description'], $GLOBALS['redirector']) .
788 $descriptionlink . ']]></description>' . "\n</item>\n";
801 $i++; 789 $i++;
802 } 790 }
803 echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->'; 791 echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
@@ -835,7 +823,8 @@ function showATOM()
835 $LINKSDB = new LinkDB( 823 $LINKSDB = new LinkDB(
836 $GLOBALS['config']['DATASTORE'], 824 $GLOBALS['config']['DATASTORE'],
837 isLoggedIn(), 825 isLoggedIn(),
838 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 826 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
827 $GLOBALS['redirector']
839 ); 828 );
840 829
841 // Optionally filter the results: 830 // Optionally filter the results:
@@ -876,7 +865,9 @@ function showATOM()
876 if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)'; 865 if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
877 if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink; 866 if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
878 867
879 $entries.='<content type="html"><![CDATA['.nl2br(keepMultipleSpaces(text2clickable($link['description']))).$descriptionlink."]]></content>\n"; 868 $entries .= '<content type="html"><![CDATA['.
869 format_description($link['description'], $GLOBALS['redirector']) .
870 $descriptionlink . "]]></content>\n";
880 if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification) 871 if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification)
881 { 872 {
882 foreach(explode(' ',$link['tags']) as $tag) 873 foreach(explode(' ',$link['tags']) as $tag)
@@ -929,7 +920,8 @@ function showDailyRSS() {
929 $LINKSDB = new LinkDB( 920 $LINKSDB = new LinkDB(
930 $GLOBALS['config']['DATASTORE'], 921 $GLOBALS['config']['DATASTORE'],
931 isLoggedIn(), 922 isLoggedIn(),
932 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 923 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
924 $GLOBALS['redirector']
933 ); 925 );
934 926
935 /* Some Shaarlies may have very few links, so we need to look 927 /* Some Shaarlies may have very few links, so we need to look
@@ -983,7 +975,7 @@ function showDailyRSS() {
983 // We pre-format some fields for proper output. 975 // We pre-format some fields for proper output.
984 foreach ($linkdates as $linkdate) { 976 foreach ($linkdates as $linkdate) {
985 $l = $LINKSDB[$linkdate]; 977 $l = $LINKSDB[$linkdate];
986 $l['formatedDescription'] = nl2br(keepMultipleSpaces(text2clickable($l['description']))); 978 $l['formatedDescription'] = format_description($l['description'], $GLOBALS['redirector']);
987 $l['thumbnail'] = thumbnail($l['url']); 979 $l['thumbnail'] = thumbnail($l['url']);
988 $l['timestamp'] = linkdate2timestamp($l['linkdate']); 980 $l['timestamp'] = linkdate2timestamp($l['linkdate']);
989 if (startsWith($l['url'], '?')) { 981 if (startsWith($l['url'], '?')) {
@@ -1016,7 +1008,8 @@ function showDaily()
1016 $LINKSDB = new LinkDB( 1008 $LINKSDB = new LinkDB(
1017 $GLOBALS['config']['DATASTORE'], 1009 $GLOBALS['config']['DATASTORE'],
1018 isLoggedIn(), 1010 isLoggedIn(),
1019 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 1011 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
1012 $GLOBALS['redirector']
1020 ); 1013 );
1021 1014
1022 $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD. 1015 $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
@@ -1047,7 +1040,7 @@ function showDaily()
1047 $taglist = explode(' ',$link['tags']); 1040 $taglist = explode(' ',$link['tags']);
1048 uasort($taglist, 'strcasecmp'); 1041 uasort($taglist, 'strcasecmp');
1049 $linksToDisplay[$key]['taglist']=$taglist; 1042 $linksToDisplay[$key]['taglist']=$taglist;
1050 $linksToDisplay[$key]['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable($link['description']))); 1043 $linksToDisplay[$key]['formatedDescription'] = format_description($link['description'], $GLOBALS['redirector']);
1051 $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']); 1044 $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']);
1052 $linksToDisplay[$key]['timestamp'] = linkdate2timestamp($link['linkdate']); 1045 $linksToDisplay[$key]['timestamp'] = linkdate2timestamp($link['linkdate']);
1053 } 1046 }
@@ -1107,7 +1100,8 @@ function renderPage()
1107 $LINKSDB = new LinkDB( 1100 $LINKSDB = new LinkDB(
1108 $GLOBALS['config']['DATASTORE'], 1101 $GLOBALS['config']['DATASTORE'],
1109 isLoggedIn(), 1102 isLoggedIn(),
1110 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 1103 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
1104 $GLOBALS['redirector']
1111 ); 1105 );
1112 1106
1113 $PAGE = new pageBuilder; 1107 $PAGE = new pageBuilder;
@@ -1781,7 +1775,8 @@ function importFile()
1781 $LINKSDB = new LinkDB( 1775 $LINKSDB = new LinkDB(
1782 $GLOBALS['config']['DATASTORE'], 1776 $GLOBALS['config']['DATASTORE'],
1783 isLoggedIn(), 1777 isLoggedIn(),
1784 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] 1778 $GLOBALS['config']['HIDE_PUBLIC_LINKS'],
1779 $GLOBALS['redirector']
1785 ); 1780 );
1786 $filename=$_FILES['filetoupload']['name']; 1781 $filename=$_FILES['filetoupload']['name'];
1787 $filesize=$_FILES['filetoupload']['size']; 1782 $filesize=$_FILES['filetoupload']['size'];
@@ -1932,8 +1927,7 @@ function buildLinkList($PAGE,$LINKSDB)
1932 while ($i<$end && $i<count($keys)) 1927 while ($i<$end && $i<count($keys))
1933 { 1928 {
1934 $link = $linksToDisplay[$keys[$i]]; 1929 $link = $linksToDisplay[$keys[$i]];
1935 $link['description']=nl2br(keepMultipleSpaces(text2clickable($link['description']))); 1930 $link['description'] = format_description($link['description'], $GLOBALS['redirector']);
1936 $title=$link['title'];
1937 $classLi = $i%2!=0 ? '' : 'publicLinkHightLight'; 1931 $classLi = $i%2!=0 ? '' : 'publicLinkHightLight';
1938 $link['class'] = ($link['private']==0 ? $classLi : 'private'); 1932 $link['class'] = ($link['private']==0 ? $classLi : 'private');
1939 $link['timestamp']=linkdate2timestamp($link['linkdate']); 1933 $link['timestamp']=linkdate2timestamp($link['linkdate']);
diff --git a/plugins/qrcode/qrcode.php b/plugins/qrcode/qrcode.php
index 1080c964..5f6e76a2 100644
--- a/plugins/qrcode/qrcode.php
+++ b/plugins/qrcode/qrcode.php
@@ -17,7 +17,7 @@ function hook_qrcode_render_linklist($data)
17 $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html'); 17 $qrcode_html = file_get_contents(PluginManager::$PLUGINS_PATH . '/qrcode/qrcode.html');
18 18
19 foreach ($data['links'] as &$value) { 19 foreach ($data['links'] as &$value) {
20 $qrcode = sprintf($qrcode_html, $value['url'], $value['url'], PluginManager::$PLUGINS_PATH); 20 $qrcode = sprintf($qrcode_html, $value['real_url'], $value['real_url'], PluginManager::$PLUGINS_PATH);
21 $value['link_plugin'][] = $qrcode; 21 $value['link_plugin'][] = $qrcode;
22 } 22 }
23 23
diff --git a/tests/LinkDBTest.php b/tests/LinkDBTest.php
index 8929713d..ff917f6d 100644
--- a/tests/LinkDBTest.php
+++ b/tests/LinkDBTest.php
@@ -511,4 +511,27 @@ class LinkDBTest extends PHPUnit_Framework_TestCase
511 sizeof(self::$publicLinkDB->filterFullText('free software')) 511 sizeof(self::$publicLinkDB->filterFullText('free software'))
512 ); 512 );
513 } 513 }
514
515 /**
516 * Test real_url without redirector.
517 */
518 public function testLinkRealUrlWithoutRedirector()
519 {
520 $db = new LinkDB(self::$testDatastore, false, false);
521 foreach($db as $link) {
522 $this->assertEquals($link['url'], $link['real_url']);
523 }
524 }
525
526 /**
527 * Test real_url with redirector.
528 */
529 public function testLinkRealUrlWithRedirector()
530 {
531 $redirector = 'http://redirector.to?';
532 $db = new LinkDB(self::$testDatastore, false, false, $redirector);
533 foreach($db as $link) {
534 $this->assertStringStartsWith($redirector, $link['real_url']);
535 }
536 }
514} 537}
diff --git a/tests/UtilsTest.php b/tests/UtilsTest.php
index 4847ea94..02eecda2 100644
--- a/tests/UtilsTest.php
+++ b/tests/UtilsTest.php
@@ -187,4 +187,41 @@ class UtilsTest extends PHPUnit_Framework_TestCase
187 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=') 187 is_session_id_valid('c0ZqcWF3VFE2NmJBdm1HMVQ0ZHJ3UmZPbTFsNGhkNHI=')
188 ); 188 );
189 } 189 }
190
191 /**
192 * Test text2clickable without a redirector being set.
193 */
194 public function testText2clickableWithoutRedirector()
195 {
196 $text = 'stuff http://hello.there/is=someone#here otherstuff';
197 $expectedText = 'stuff <a href="http://hello.there/is=someone#here">http://hello.there/is=someone#here</a> otherstuff';
198 $processedText = text2clickable($text, '');
199 $this->assertEquals($expectedText, $processedText);
200 }
201
202 /**
203 * Test text2clickable a redirector set.
204 */
205 public function testText2clickableWithRedirector()
206 {
207 $text = 'stuff http://hello.there/is=someone#here otherstuff';
208 $redirector = 'http://redirector.to';
209 $expectedText = 'stuff <a href="'.
210 $redirector .
211 urlencode('http://hello.there/is=someone#here') .
212 '">http://hello.there/is=someone#here</a> otherstuff';
213 $processedText = text2clickable($text, $redirector);
214 $this->assertEquals($expectedText, $processedText);
215 }
216
217 /**
218 * Test testSpace2nbsp.
219 */
220 public function testSpace2nbsp()
221 {
222 $text = ' Are you thrilled by flags ?'. PHP_EOL .' Really?';
223 $expectedText = '&nbsp; Are you &nbsp; thrilled &nbsp;by flags &nbsp; ?'. PHP_EOL .'&nbsp;Really?';
224 $processedText = space2nbsp($text);
225 $this->assertEquals($expectedText, $processedText);
226 }
190} 227}
diff --git a/tests/plugins/PlugQrcodeTest.php b/tests/plugins/PlugQrcodeTest.php
index 86dc7f29..c749fa86 100644
--- a/tests/plugins/PlugQrcodeTest.php
+++ b/tests/plugins/PlugQrcodeTest.php
@@ -30,7 +30,7 @@ class PlugQrcodeTest extends PHPUnit_Framework_TestCase
30 'title' => $str, 30 'title' => $str,
31 'links' => array( 31 'links' => array(
32 array( 32 array(
33 'url' => $str, 33 'real_url' => $str,
34 ) 34 )
35 ) 35 )
36 ); 36 );
@@ -39,7 +39,7 @@ class PlugQrcodeTest extends PHPUnit_Framework_TestCase
39 $link = $data['links'][0]; 39 $link = $data['links'][0];
40 // data shouldn't be altered 40 // data shouldn't be altered
41 $this->assertEquals($str, $data['title']); 41 $this->assertEquals($str, $data['title']);
42 $this->assertEquals($str, $link['url']); 42 $this->assertEquals($str, $link['real_url']);
43 43
44 // plugin data 44 // plugin data
45 $this->assertEquals(1, count($link['link_plugin'])); 45 $this->assertEquals(1, count($link['link_plugin']));
diff --git a/tpl/daily.html b/tpl/daily.html
index 93a3ab45..063dc89a 100644
--- a/tpl/daily.html
+++ b/tpl/daily.html
@@ -66,7 +66,7 @@
66 </div> 66 </div>
67 {/if} 67 {/if}
68 <div class="dailyEntryTitle"> 68 <div class="dailyEntryTitle">
69 <a href="{$link.url}">{$link.title}</a> 69 <a href="{$link.real_url}">{$link.title}</a>
70 </div> 70 </div>
71 {if="$link.thumbnail"} 71 {if="$link.thumbnail"}
72 <div class="dailyEntryThumbnail">{$link.thumbnail}</div> 72 <div class="dailyEntryThumbnail">{$link.thumbnail}</div>
diff --git a/tpl/linklist.html b/tpl/linklist.html
index f6e9e82b..666748a7 100644
--- a/tpl/linklist.html
+++ b/tpl/linklist.html
@@ -70,7 +70,9 @@
70 </form> 70 </form>
71 </div> 71 </div>
72 {/if} 72 {/if}
73 <span class="linktitle"><a href="{$redirector}{$value.url}">{$value.title}</a></span> 73 <span class="linktitle">
74 <a href="{$value.real_url}">{$value.title}</a>
75 </span>
74 <br> 76 <br>
75 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if} 77 {if="$value.description"}<div class="linkdescription">{$value.description}</div>{/if}
76 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} 78 {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"}
@@ -83,7 +85,7 @@
83 <span>{$value}</span> - 85 <span>{$value}</span> -
84 {/loop} 86 {/loop}
85 87
86 <a href="{$value.url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br> 88 <a href="{$value.real_url}"><span class="linkurl" title="Short link">{$value.url}</span></a><br>
87 {if="$value.tags"} 89 {if="$value.tags"}
88 <div class="linktaglist"> 90 <div class="linktaglist">
89 {loop="value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop} 91 {loop="value.taglist"}<span class="linktag" title="Add tag"><a href="?addtag={$value|urlencode}">{$value}</a></span> {/loop}
diff --git a/tpl/picwall.html b/tpl/picwall.html
index 97d5efdf..230c948b 100644
--- a/tpl/picwall.html
+++ b/tpl/picwall.html
@@ -16,7 +16,7 @@
16 <div id="picwall_container"> 16 <div id="picwall_container">
17 {loop="linksToDisplay"} 17 {loop="linksToDisplay"}
18 <div class="picwall_pictureframe"> 18 <div class="picwall_pictureframe">
19 {$value.thumbnail}<a href="{$value.url}"><span class="info">{$value.title}</span></a> 19 {$value.thumbnail}<a href="{$value.real_url}"><span class="info">{$value.title}</span></a>
20 {loop="$value.picwall_plugin"} 20 {loop="$value.picwall_plugin"}
21 {$value} 21 {$value}
22 {/loop} 22 {/loop}