aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSeb Sauvage <sebsauvage@sebsauvage.net>2011-10-13 13:10:10 +0200
committerEmilien Klein <emilien@klein.st>2011-10-13 13:10:10 +0200
commit008d8b95ef7d7b5e8e79b633ecc6d0097e0b0e28 (patch)
tree732f5070f50066c9c83a671ed410e71b076071a1
parent99c9c9541b012eebdb84ee33a765bb18a62fb0d7 (diff)
downloadShaarli-008d8b95ef7d7b5e8e79b633ecc6d0097e0b0e28.tar.gz
Shaarli-008d8b95ef7d7b5e8e79b633ecc6d0097e0b0e28.tar.zst
Shaarli-008d8b95ef7d7b5e8e79b633ecc6d0097e0b0e28.zip
Version 0.0.25 beta:
- Changed: Now thumbnails generated by Shaarli are croped to a height of 120 pixels. - Changed: YouTube thumbnails now use default.jpg instead of 2.jpg (This is usually more pertinent). - Changed: As per request, configuration options (such as HIDE_TIMESTAMPS, ENABLE_THUMBNAILS, etc.) can now be put in a an external file so that you do not have to tweak them again when you upgrade Shaarli. Just add the file data/options.php. - Corrected: Shaarli now supports newlines in titles (thanks to dixy). - Changed: If a single link is displayed, the page title contains the title of the link. - Changed: Shaarli page title is clickable (and has the same link as “Home”). - Added: Better CSS for printing (thanks to jerrywham suggestion) - Changed: A few CSS tweaks (thanks to maethor for suggestion) - Added: You can now use a redirector or anonymizing proxy for links (such as http://anonym.to/? to mask you HTTP_REFERER). Just go to Tools > Configure > Redirector. (thanks to Accent Grave for the suggestion) - Added: The option ENABLE_LOCALCACHE can be set to false for those who have a limited quota on their host. This will disable the local thumbnail cache. Services which require the use of the cache will have no thumbnails (vimeo, flickr, direct link to image). Other services will still have a thumbnail (youtube,imgur.com,dailymotion,imageshack.us). - Corrected: The link to the RSS feed in page header was not correct.
-rw-r--r--index.php190
-rw-r--r--shaarli.css37
2 files changed, 139 insertions, 88 deletions
diff --git a/index.php b/index.php
index 6e7e638c..ce6a51c4 100644
--- a/index.php
+++ b/index.php
@@ -1,29 +1,30 @@
1<?php 1<?php
2// Shaarli 0.0.24 beta - Shaare your links... 2// Shaarli 0.0.25 beta - Shaare your links...
3// The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net 3// The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net
4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli 4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli
5// Licence: http://www.opensource.org/licenses/zlib-license.php 5// Licence: http://www.opensource.org/licenses/zlib-license.php
6 6
7// Requires: php 5.1.x 7// Requires: php 5.1.x
8 8// (but autocomplete fields will only work if you have php 5.2.x)
9// ----------------------------------------------------------------------------------------------- 9// -----------------------------------------------------------------------------------------------
10// User config: 10// User config:
11define('DATADIR','data'); // Data subdirectory 11$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory
12define('CONFIG_FILE',DATADIR.'/config.php'); // Configuration file (user login/password) 12$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password)
13define('DATASTORE',DATADIR.'/datastore.php'); // Data storage file. 13$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file.
14define('LINKS_PER_PAGE',20); // Default links per page. 14$GLOBALS['config']['LINKS_PER_PAGE'] = 20; // Default links per page.
15define('IPBANS_FILENAME',DATADIR.'/ipbans.php'); // File storage for failures and bans. 15$GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php'; // File storage for failures and bans.
16define('BAN_AFTER',4); // Ban IP after this many failures. 16$GLOBALS['config']['BAN_AFTER'] = 4; // Ban IP after this many failures.
17define('BAN_DURATION',1800); // Ban duration for IP address after login failures (in seconds) (1800 sec. = 30 minutes) 17$GLOBALS['config']['BAN_DURATION'] = 1800; // Ban duration for IP address after login failures (in seconds) (1800 sec. = 30 minutes)
18define('OPEN_SHAARLI',false); // If true, anyone can add/edit/delete links without having to login 18$GLOBALS['config']['OPEN_SHAARLI'] = false; // If true, anyone can add/edit/delete links without having to login
19define('HIDE_TIMESTAMPS',false); // If true, the moment when links were saved are not shown to users that are not logged in. 19$GLOBALS['config']['HIDE_TIMESTAMPS'] = false; // If true, the moment when links were saved are not shown to users that are not logged in.
20define('ENABLE_THUMBNAILS',true); // Enable thumbnails in links. 20$GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links.
21define('CACHEDIR','cache'); // Cache directory for thumbnails for SLOW services (like flickr) 21$GLOBALS['config']['CACHEDIR'] = 'cache'; // Cache directory for thumbnails for SLOW services (like flickr)
22$GLOBALS['config']['ENABLE_LOCALCACHE'] = true; // Enable Shaarli to store thumbnail in a local cache. Disable to reduce webspace usage.
22 23
23// ----------------------------------------------------------------------------------------------- 24// -----------------------------------------------------------------------------------------------
24// Program config (touch at your own risks !) 25// Program config (touch at your own risks !)
25define('UPDATECHECK_FILENAME',DATADIR.'/lastupdatecheck.txt'); // For updates check of Shaarli. 26$GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli.
26define('UPDATECHECK_INTERVAL',86400); // Updates check frequency for Shaarli. 86400 seconds=24 hours 27$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours
27ini_set('max_input_time','60'); // High execution time in case of problematic imports/exports. 28ini_set('max_input_time','60'); // High execution time in case of problematic imports/exports.
28ini_set('memory_limit', '128M'); // Try to set max upload file size and read (May not work on some hosts). 29ini_set('memory_limit', '128M'); // Try to set max upload file size and read (May not work on some hosts).
29ini_set('post_max_size', '16M'); 30ini_set('post_max_size', '16M');
@@ -36,6 +37,9 @@ error_reporting(E_ALL^E_WARNING); // See all error except warnings.
36//error_reporting(-1); // See all errors (for debugging only) 37//error_reporting(-1); // See all errors (for debugging only)
37ob_start(); 38ob_start();
38 39
40// Optionnal config file.
41if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
42
39// In case stupid admin has left magic_quotes enabled in php.ini: 43// In case stupid admin has left magic_quotes enabled in php.ini:
40if (get_magic_quotes_gpc()) 44if (get_magic_quotes_gpc())
41{ 45{
@@ -49,13 +53,16 @@ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
49header("Cache-Control: no-store, no-cache, must-revalidate"); 53header("Cache-Control: no-store, no-cache, must-revalidate");
50header("Cache-Control: post-check=0, pre-check=0", false); 54header("Cache-Control: post-check=0, pre-check=0", false);
51header("Pragma: no-cache"); 55header("Pragma: no-cache");
52define('shaarli_version','0.0.24 beta'); 56define('shaarli_version','0.0.25 beta');
53if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); } 57if (!is_dir($GLOBALS['config']['DATADIR'])) { mkdir($GLOBALS['config']['DATADIR'],0705); chmod($GLOBALS['config']['DATADIR'],0705); }
54if (!is_dir(CACHEDIR)) { mkdir(CACHEDIR,0705); chmod(CACHEDIR,0705); } 58if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['DATADIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
55if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. 59if ($GLOBALS['config']['ENABLE_LOCALCACHE'])
56if (!is_file(CACHEDIR.'/.htaccess')) { file_put_contents(CACHEDIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. 60{
57if (!is_file(CONFIG_FILE)) install(); 61 if (!is_dir($GLOBALS['config']['CACHEDIR'])) { mkdir($GLOBALS['config']['CACHEDIR'],0705); chmod($GLOBALS['config']['CACHEDIR'],0705); }
58require CONFIG_FILE; // Read login/password hash into $GLOBALS. 62 if (!is_file($GLOBALS['config']['CACHEDIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['CACHEDIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
63}
64if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install();
65require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
59// Small protection against dodgy config files: 66// Small protection against dodgy config files:
60if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']); 67if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']);
61if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get(); 68if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
@@ -83,16 +90,16 @@ function checkUpdate()
83 if (!isLoggedIn()) return ''; // Do not check versions for visitors. 90 if (!isLoggedIn()) return ''; // Do not check versions for visitors.
84 91
85 // Get latest version number at most once a day. 92 // Get latest version number at most once a day.
86 if (!is_file(UPDATECHECK_FILENAME) || (filemtime(UPDATECHECK_FILENAME)<time()-(UPDATECHECK_INTERVAL))) 93 if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL'])))
87 { 94 {
88 $version=shaarli_version; 95 $version=shaarli_version;
89 list($httpstatus,$headers,$data) = getHTTP('http://sebsauvage.net/files/shaarli_version.txt',2); 96 list($httpstatus,$headers,$data) = getHTTP('http://sebsauvage.net/files/shaarli_version.txt',2);
90 if (strpos($httpstatus,'200 OK')) $version=$data; 97 if (strpos($httpstatus,'200 OK')) $version=$data;
91 // If failed, nevermind. We don't want to bother the user with that. 98 // If failed, nevermind. We don't want to bother the user with that.
92 file_put_contents(UPDATECHECK_FILENAME,$version); // touch file date 99 file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
93 } 100 }
94 // Compare versions: 101 // Compare versions:
95 $newestversion=file_get_contents(UPDATECHECK_FILENAME); 102 $newestversion=file_get_contents($GLOBALS['config']['UPDATECHECK_FILENAME']);
96 if (version_compare($newestversion,shaarli_version)==1) return $newestversion; 103 if (version_compare($newestversion,shaarli_version)==1) return $newestversion;
97 return ''; 104 return '';
98} 105}
@@ -102,7 +109,7 @@ function checkUpdate()
102function logm($message) 109function logm($message)
103{ 110{
104 $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n"; 111 $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
105 file_put_contents(DATADIR.'/log.txt',$t,FILE_APPEND); 112 file_put_contents($GLOBALS['config']['DATADIR'].'/log.txt',$t,FILE_APPEND);
106} 113}
107 114
108/* Returns the small hash of a string 115/* Returns the small hash of a string
@@ -126,7 +133,8 @@ function smallHash($text)
126// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722 133// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
127function text2clickable($url) 134function text2clickable($url)
128{ 135{
129 return preg_replace('!((?:https?|ftp)://\S+[[:alnum:]]/?)!si','<a href="$1" rel="nofollow">$1</a> ',$url); 136 $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'];
137 return preg_replace('!((?:https?|ftp)://\S+[[:alnum:]]/?)!si','<a href="'.$redir.'$1" rel="nofollow">$1</a> ',$url);
130} 138}
131// ------------------------------------------------------------------------------------------ 139// ------------------------------------------------------------------------------------------
132// Sniff browser language to display dates in the right format automatically. 140// Sniff browser language to display dates in the right format automatically.
@@ -180,7 +188,7 @@ function check_auth($login,$password)
180// Returns true if the user is logged in. 188// Returns true if the user is logged in.
181function isLoggedIn() 189function isLoggedIn()
182{ 190{
183 if (OPEN_SHAARLI) return true; 191 if ($GLOBALS['config']['OPEN_SHAARLI']) return true;
184 192
185 // If session does not exist on server side, or IP address has changed, or session has expired, logout. 193 // If session does not exist on server side, or IP address has changed, or session has expired, logout.
186 if (empty($_SESSION['uid']) || $_SESSION['ip']!=allIPs() || time()>=$_SESSION['expires_on']) 194 if (empty($_SESSION['uid']) || $_SESSION['ip']!=allIPs() || time()>=$_SESSION['expires_on'])
@@ -201,21 +209,21 @@ function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SES
201// ------------------------------------------------------------------------------------------ 209// ------------------------------------------------------------------------------------------
202// Brute force protection system 210// Brute force protection system
203// Several consecutive failed logins will ban the IP address for 30 minutes. 211// Several consecutive failed logins will ban the IP address for 30 minutes.
204if (!is_file(IPBANS_FILENAME)) file_put_contents(IPBANS_FILENAME, "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"); 212if (!is_file($GLOBALS['config']['IPBANS_FILENAME'])) file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>");
205include IPBANS_FILENAME; 213include $GLOBALS['config']['IPBANS_FILENAME'];
206// Signal a failed login. Will ban the IP if too many failures: 214// Signal a failed login. Will ban the IP if too many failures:
207function ban_loginFailed() 215function ban_loginFailed()
208{ 216{
209 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 217 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
210 if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0; 218 if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0;
211 $gb['FAILURES'][$ip]++; 219 $gb['FAILURES'][$ip]++;
212 if ($gb['FAILURES'][$ip]>(BAN_AFTER-1)) 220 if ($gb['FAILURES'][$ip]>($GLOBALS['config']['BAN_AFTER']-1))
213 { 221 {
214 $gb['BANS'][$ip]=time()+BAN_DURATION; 222 $gb['BANS'][$ip]=time()+$GLOBALS['config']['BAN_DURATION'];
215 logm('IP address banned from login'); 223 logm('IP address banned from login');
216 } 224 }
217 $GLOBALS['IPBANS'] = $gb; 225 $GLOBALS['IPBANS'] = $gb;
218 file_put_contents(IPBANS_FILENAME, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 226 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
219} 227}
220 228
221// Signals a successful login. Resets failed login counter. 229// Signals a successful login. Resets failed login counter.
@@ -224,7 +232,7 @@ function ban_loginOk()
224 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS']; 232 $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
225 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 233 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
226 $GLOBALS['IPBANS'] = $gb; 234 $GLOBALS['IPBANS'] = $gb;
227 file_put_contents(IPBANS_FILENAME, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 235 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
228} 236}
229 237
230// Checks if the user CAN login. If 'true', the user can try to login. 238// Checks if the user CAN login. If 'true', the user can try to login.
@@ -238,7 +246,7 @@ function ban_canLogin()
238 { // Ban expired, user can try to login again. 246 { // Ban expired, user can try to login again.
239 logm('Ban lifted.'); 247 logm('Ban lifted.');
240 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]); 248 unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
241 file_put_contents(IPBANS_FILENAME, "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>"); 249 file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
242 return true; // Ban has expired, user can login. 250 return true; // Ban has expired, user can login.
243 } 251 }
244 return false; // User is banned. 252 return false; // User is banned.
@@ -420,7 +428,7 @@ function getHTTP($url,$timeout=30)
420// (Returns an empty string if not found.) 428// (Returns an empty string if not found.)
421function html_extract_title($html) 429function html_extract_title($html)
422{ 430{
423 return preg_match('!<title>(.*?)</title>!i', $html, $matches) ? $matches[1] : ''; 431 return preg_match('!<title>(.*)</title>!is', $html, $matches) ? trim(str_replace("\n",' ', $matches[1])) : '' ;
424} 432}
425 433
426// ------------------------------------------------------------------------------------------ 434// ------------------------------------------------------------------------------------------
@@ -511,14 +519,14 @@ class linkdb implements Iterator, Countable, ArrayAccess
511 // ---- Misc methods 519 // ---- Misc methods
512 private function checkdb() // Check if db directory and file exists. 520 private function checkdb() // Check if db directory and file exists.
513 { 521 {
514 if (!file_exists(DATASTORE)) // Create a dummy database for example. 522 if (!file_exists($GLOBALS['config']['DATASTORE'])) // Create a dummy database for example.
515 { 523 {
516 $this->links = array(); 524 $this->links = array();
517 $link = array('title'=>'Shaarli - sebsauvage.net','url'=>'http://sebsauvage.net/wiki/doku.php?id=php:shaarli','description'=>'Welcome to Shaarli ! This is a bookmark. To edit or delete me, you must first login.','private'=>0,'linkdate'=>'20110914_190000','tags'=>'opensource software'); 525 $link = array('title'=>'Shaarli - sebsauvage.net','url'=>'http://sebsauvage.net/wiki/doku.php?id=php:shaarli','description'=>'Welcome to Shaarli ! This is a bookmark. To edit or delete me, you must first login.','private'=>0,'linkdate'=>'20110914_190000','tags'=>'opensource software');
518 $this->links[$link['linkdate']] = $link; 526 $this->links[$link['linkdate']] = $link;
519 $link = array('title'=>'My secret stuff... - Pastebin.com','url'=>'http://pastebin.com/smCEEeSn','description'=>'SShhhh!! I\'m a private link only YOU can see. You can delete me too.','private'=>1,'linkdate'=>'20110914_074522','tags'=>'secretstuff'); 527 $link = array('title'=>'My secret stuff... - Pastebin.com','url'=>'http://pastebin.com/smCEEeSn','description'=>'SShhhh!! I\'m a private link only YOU can see. You can delete me too.','private'=>1,'linkdate'=>'20110914_074522','tags'=>'secretstuff');
520 $this->links[$link['linkdate']] = $link; 528 $this->links[$link['linkdate']] = $link;
521 file_put_contents(DATASTORE, PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); // Write database to disk 529 file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); // Write database to disk
522 } 530 }
523 } 531 }
524 532
@@ -526,7 +534,7 @@ class linkdb implements Iterator, Countable, ArrayAccess
526 private function readdb() 534 private function readdb()
527 { 535 {
528 // Read data 536 // Read data
529 $this->links=(file_exists(DATASTORE) ? unserialize(gzinflate(base64_decode(substr(file_get_contents(DATASTORE),strlen(PHPPREFIX),-strlen(PHPSUFFIX))))) : array() ); 537 $this->links=(file_exists($GLOBALS['config']['DATASTORE']) ? unserialize(gzinflate(base64_decode(substr(file_get_contents($GLOBALS['config']['DATASTORE']),strlen(PHPPREFIX),-strlen(PHPSUFFIX))))) : array() );
530 // Note that gzinflate is faster than gzuncompress. See: http://www.php.net/manual/en/function.gzdeflate.php#96439 538 // Note that gzinflate is faster than gzuncompress. See: http://www.php.net/manual/en/function.gzdeflate.php#96439
531 539
532 // If user is not logged in, filter private links. 540 // If user is not logged in, filter private links.
@@ -546,7 +554,7 @@ class linkdb implements Iterator, Countable, ArrayAccess
546 public function savedb() 554 public function savedb()
547 { 555 {
548 if (!$this->loggedin) die('You are not authorized to change the database.'); 556 if (!$this->loggedin) die('You are not authorized to change the database.');
549 file_put_contents(DATASTORE, PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); 557 file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX);
550 } 558 }
551 559
552 // Returns the link for a given URL (if it exists). false it does not exist. 560 // Returns the link for a given URL (if it exists). false it does not exist.
@@ -644,7 +652,7 @@ function showRSS()
644 $link = $linksToDisplay[$keys[$i]]; 652 $link = $linksToDisplay[$keys[$i]];
645 $rfc822date = linkdate2rfc822($link['linkdate']); 653 $rfc822date = linkdate2rfc822($link['linkdate']);
646 echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid>'.htmlspecialchars($link['url']).'</guid><link>'.htmlspecialchars($link['url']).'</link>'; 654 echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid>'.htmlspecialchars($link['url']).'</guid><link>'.htmlspecialchars($link['url']).'</link>';
647 if (!HIDE_TIMESTAMPS || isLoggedIn()) echo '<pubDate>'.htmlspecialchars($rfc822date).'</pubDate>'; 655 if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) echo '<pubDate>'.htmlspecialchars($rfc822date).'</pubDate>';
648 echo '<description><![CDATA['.nl2br(htmlspecialchars($link['description'])).']]></description></item>'."\n"; 656 echo '<description><![CDATA['.nl2br(htmlspecialchars($link['description'])).']]></description></item>'."\n";
649 $i++; 657 $i++;
650 } 658 }
@@ -676,13 +684,13 @@ function showATOM()
676 $iso8601date = linkdate2iso8601($link['linkdate']); 684 $iso8601date = linkdate2iso8601($link['linkdate']);
677 $latestDate = max($latestDate,$iso8601date); 685 $latestDate = max($latestDate,$iso8601date);
678 $entries.='<entry><title>'.htmlspecialchars($link['title']).'</title><link href="'.htmlspecialchars($link['url']).'"/><id>'.htmlspecialchars($link['url']).'</id>'; 686 $entries.='<entry><title>'.htmlspecialchars($link['title']).'</title><link href="'.htmlspecialchars($link['url']).'"/><id>'.htmlspecialchars($link['url']).'</id>';
679 if (!HIDE_TIMESTAMPS || isLoggedIn()) $entries.='<updated>'.htmlspecialchars($iso8601date).'</updated>'; 687 if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $entries.='<updated>'.htmlspecialchars($iso8601date).'</updated>';
680 $entries.='<summary>'.nl2br(htmlspecialchars($link['description'])).'</summary></entry>'."\n"; 688 $entries.='<summary>'.nl2br(htmlspecialchars($link['description'])).'</summary></entry>'."\n";
681 $i++; 689 $i++;
682 } 690 }
683 $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">'; 691 $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
684 $feed.='<title>'.htmlspecialchars($GLOBALS['title']).'</title>'; 692 $feed.='<title>'.htmlspecialchars($GLOBALS['title']).'</title>';
685 if (!HIDE_TIMESTAMPS || isLoggedIn()) $feed.='<updated>'.htmlspecialchars($latestDate).'</updated>'; 693 if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.htmlspecialchars($latestDate).'</updated>';
686 $feed.='<link href="'.htmlspecialchars($pageaddr).'" />'; 694 $feed.='<link href="'.htmlspecialchars($pageaddr).'" />';
687 $feed.='<author><uri>'.htmlspecialchars($pageaddr).'</uri></author>'; 695 $feed.='<author><uri>'.htmlspecialchars($pageaddr).'</uri></author>';
688 $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do. 696 $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
@@ -705,7 +713,7 @@ function renderPage()
705 // -------- Display login form. 713 // -------- Display login form.
706 if (startswith($_SERVER["QUERY_STRING"],'do=login')) 714 if (startswith($_SERVER["QUERY_STRING"],'do=login'))
707 { 715 {
708 if (OPEN_SHAARLI) { header('Location: ?'); exit; } // No need to login for open Shaarli 716 if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli
709 if (!ban_canLogin()) 717 if (!ban_canLogin())
710 { 718 {
711 $loginform='<div id="headerform">You have been banned from login after too many failed attempts. Try later.</div>'; 719 $loginform='<div id="headerform">You have been banned from login after too many failed attempts. Try later.</div>';
@@ -820,7 +828,7 @@ HTML;
820 { 828 {
821 $pageabsaddr=serverUrl().$_SERVER["SCRIPT_NAME"]; // Why doesn't php have a built-in function for that ? 829 $pageabsaddr=serverUrl().$_SERVER["SCRIPT_NAME"]; // Why doesn't php have a built-in function for that ?
822 // The javascript code for the bookmarklet: 830 // The javascript code for the bookmarklet:
823 $changepwd = (OPEN_SHAARLI ? '' : '<a href="?do=changepasswd"><b>Change password</b></a> - Change your password.<br><br>' ); 831 $changepwd = ($GLOBALS['config']['OPEN_SHAARLI'] ? '' : '<a href="?do=changepasswd"><b>Change password</b></a> - Change your password.<br><br>' );
824 $toolbar= <<<HTML 832 $toolbar= <<<HTML
825<div id="headerform"><br> 833<div id="headerform"><br>
826 {$changepwd} 834 {$changepwd}
@@ -839,7 +847,7 @@ HTML;
839 // -------- User wants to change his/her password. 847 // -------- User wants to change his/her password.
840 if (startswith($_SERVER["QUERY_STRING"],'do=changepasswd')) 848 if (startswith($_SERVER["QUERY_STRING"],'do=changepasswd'))
841 { 849 {
842 if (OPEN_SHAARLI) die('You are not supposed to change a password on an Open Shaarli.'); 850 if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.');
843 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) 851 if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
844 { 852 {
845 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! 853 if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away !
@@ -882,6 +890,7 @@ HTML;
882 $tz = $_POST['continent'].'/'.$_POST['city']; 890 $tz = $_POST['continent'].'/'.$_POST['city'];
883 $GLOBALS['timezone'] = $tz; 891 $GLOBALS['timezone'] = $tz;
884 $GLOBALS['title']=$_POST['title']; 892 $GLOBALS['title']=$_POST['title'];
893 $GLOBALS['redirector']=$_POST['redirector'];
885 writeConfig(); 894 writeConfig();
886 echo '<script language="JavaScript">alert("Configuration was saved.");document.location=\'?do=tools\';</script>'; 895 echo '<script language="JavaScript">alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
887 exit; 896 exit;
@@ -890,6 +899,7 @@ HTML;
890 { 899 {
891 $token = getToken(); 900 $token = getToken();
892 $title = htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES); 901 $title = htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES);
902 $redirector = htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES);
893 list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']); 903 list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']);
894 $timezone_html=''; if ($timezone_form!='') $timezone_html='<tr><td valign="top"><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>'; 904 $timezone_html=''; if ($timezone_form!='') $timezone_html='<tr><td valign="top"><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
895 $changepwdform= <<<HTML 905 $changepwdform= <<<HTML
@@ -897,6 +907,7 @@ ${timezone_js}<form method="POST" action="" name="configform" id="configform"><i
897<table border="0" cellpadding="20"> 907<table border="0" cellpadding="20">
898<tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr> 908<tr><td><b>Page title:</b></td><td><input type="text" name="title" id="title" size="50" value="{$title}"></td></tr>
899{$timezone_html} 909{$timezone_html}
910<tr><td valign="top"><b>Redirector</b></td><td><input type="text" name="redirector" id="redirector" size="50" value="{$redirector}"><br>(e.g. <i>http://anonym.to/?</i> will mask the HTTP_REFERER)</td></tr>
900<tr><td></td><td align="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr> 911<tr><td></td><td align="right"><input type="submit" name="Save" value="Save config" class="bigbutton"></td></tr>
901</table> 912</table>
902</form> 913</form>
@@ -1283,13 +1294,13 @@ function templateLinkList()
1283 if (!empty($_GET['searchterm'])) // Fulltext search 1294 if (!empty($_GET['searchterm'])) // Fulltext search
1284 { 1295 {
1285 $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm'])); 1296 $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm']));
1286 $searched='&nbsp;<b>'.count($linksToDisplay).' results for <i>'.htmlspecialchars(trim($_GET['searchterm'])).'</i></b>:'; 1297 $searched=count($linksToDisplay).' results for <i>'.htmlspecialchars(trim($_GET['searchterm'])).'</i>:';
1287 } 1298 }
1288 elseif (!empty($_GET['searchtags'])) // Search by tag 1299 elseif (!empty($_GET['searchtags'])) // Search by tag
1289 { 1300 {
1290 $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); 1301 $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
1291 $tagshtml=''; foreach(explode(' ',trim($_GET['searchtags'])) as $tag) $tagshtml.='<span class="linktag" title="Remove tag"><a href="?removetag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).' <span style="border-left:1px solid #aaa; padding-left:5px; color:#6767A7;">x</span></a></span> '; 1302 $tagshtml=''; foreach(explode(' ',trim($_GET['searchtags'])) as $tag) $tagshtml.='<span class="linktag" title="Remove tag"><a href="?removetag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).' <span style="border-left:1px solid #aaa; padding-left:5px; color:#6767A7;">x</span></a></span> ';
1292 $searched='&nbsp;<b>'.count($linksToDisplay).' results for tags '.$tagshtml.':</b>'; 1303 $searched=''.count($linksToDisplay).' results for tags '.$tagshtml.':';
1293 } 1304 }
1294 elseif (preg_match('/[a-zA-Z0-9-_@]{6}/',$_SERVER["QUERY_STRING"])) // Detect smallHashes in URL 1305 elseif (preg_match('/[a-zA-Z0-9-_@]{6}/',$_SERVER["QUERY_STRING"])) // Detect smallHashes in URL
1295 { 1306 {
@@ -1297,7 +1308,7 @@ function templateLinkList()
1297 } 1308 }
1298 else 1309 else
1299 $linksToDisplay = $LINKSDB; // otherwise, display without filtering. 1310 $linksToDisplay = $LINKSDB; // otherwise, display without filtering.
1300 1311 if ($searched!='') $searched='<div id="searchcriteria">'.$searched.'</div>';
1301 $linklist=''; 1312 $linklist='';
1302 $actions=''; 1313 $actions='';
1303 1314
@@ -1307,13 +1318,19 @@ function templateLinkList()
1307 If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); ) 1318 If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); )
1308 */ 1319 */
1309 $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // Stupid and ugly. Thanks php. 1320 $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // Stupid and ugly. Thanks php.
1321
1322 // If there is only a single link, we change on-the-fly the title of the page.
1323 if (count($linksToDisplay)==1) $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title'];
1324
1310 $pagecount = ceil(count($keys)/$_SESSION['LINKS_PER_PAGE']); 1325 $pagecount = ceil(count($keys)/$_SESSION['LINKS_PER_PAGE']);
1311 $pagecount = ($pagecount==0 ? 1 : $pagecount); 1326 $pagecount = ($pagecount==0 ? 1 : $pagecount);
1312 $page=( empty($_GET['page']) ? 1 : intval($_GET['page'])); 1327 $page=( empty($_GET['page']) ? 1 : intval($_GET['page']));
1313 $page = ( $page<1 ? 1 : $page ); 1328 $page = ( $page<1 ? 1 : $page );
1314 $page = ( $page>$pagecount ? $pagecount : $page ); 1329 $page = ( $page>$pagecount ? $pagecount : $page );
1315 $i = ($page-1)*$_SESSION['LINKS_PER_PAGE']; // Start index. 1330 $i = ($page-1)*$_SESSION['LINKS_PER_PAGE']; // Start index.
1316 $end = $i+$_SESSION['LINKS_PER_PAGE']; 1331 $end = $i+$_SESSION['LINKS_PER_PAGE'];
1332 $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']; // optional redirector URL
1333
1317 while ($i<$end && $i<count($keys)) 1334 while ($i<$end && $i<count($keys))
1318 { 1335 {
1319 $link = $linksToDisplay[$keys[$i]]; 1336 $link = $linksToDisplay[$keys[$i]];
@@ -1322,11 +1339,15 @@ function templateLinkList()
1322 $classprivate = ($link['private']==0 ? '' : 'class="private"'); 1339 $classprivate = ($link['private']==0 ? '' : 'class="private"');
1323 if (isLoggedIn()) $actions=' <form method="GET" class="buttoneditform"><input type="hidden" name="edit_link" value="'.$link['linkdate'].'"><input type="submit" value="Edit" class="smallbutton"></form>'; 1340 if (isLoggedIn()) $actions=' <form method="GET" class="buttoneditform"><input type="hidden" name="edit_link" value="'.$link['linkdate'].'"><input type="submit" value="Edit" class="smallbutton"></form>';
1324 $tags=''; 1341 $tags='';
1325 if ($link['tags']!='') foreach(explode(' ',$link['tags']) as $tag) { $tags.='<span class="linktag" title="Add tag"><a href="?addtag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).'</a></span> '; } 1342 if ($link['tags']!='')
1343 {
1344 foreach(explode(' ',$link['tags']) as $tag) { $tags.='<span class="linktag" title="Add tag"><a href="?addtag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).'</a></span> '; }
1345 $tags='<div class="linktaglist">'.$tags.'</div>';
1346 }
1326 $linklist.='<li '.$classprivate.'>'.thumbnail($link['url']); 1347 $linklist.='<li '.$classprivate.'>'.thumbnail($link['url']);
1327 $linklist.='<div class="linkcontainer"><span class="linktitle"><a href="'.htmlspecialchars($link['url']).'">'.htmlspecialchars($title).'</a></span>'.$actions.'<br>'; 1348 $linklist.='<div class="linkcontainer"><span class="linktitle"><a href="'.$redir.htmlspecialchars($link['url']).'">'.htmlspecialchars($title).'</a></span>'.$actions.'<br>';
1328 if ($description!='') $linklist.='<div class="linkdescription">'.nl2br($description).'</div><br>'; 1349 if ($description!='') $linklist.='<div class="linkdescription">'.nl2br($description).'</div><br>';
1329 if (!HIDE_TIMESTAMPS || isLoggedIn()) $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">'.htmlspecialchars(linkdate2locale($link['linkdate'])).' </a> - </span>'; 1350 if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">'.htmlspecialchars(linkdate2locale($link['linkdate'])).' </a> - </span>';
1330 else $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">link</a> - </span>'; 1351 else $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">link</a> - </span>';
1331 $linklist.='<span class="linkurl" title="Short link">'.htmlspecialchars($link['url']).'</span><br>'.$tags."</div></li>\n"; 1352 $linklist.='<span class="linkurl" title="Short link">'.htmlspecialchars($link['url']).'</span><br>'.$tags."</div></li>\n";
1332 $i++; 1353 $i++;
@@ -1353,16 +1374,16 @@ HTML;
1353// Understands various services (youtube.com...) 1374// Understands various services (youtube.com...)
1354function thumbnail($url) 1375function thumbnail($url)
1355{ 1376{
1356 if (!ENABLE_THUMBNAILS) return ''; 1377 if (!$GLOBALS['config']['ENABLE_THUMBNAILS']) return '';
1357 1378
1358 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. 1379 // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link.
1359 // (eg. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/2.jpg ) 1380 // (eg. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg )
1360 // ^^^^^^^^^^^ ^^^^^^^^^^^ 1381 // ^^^^^^^^^^^ ^^^^^^^^^^^
1361 $domain = parse_url($url,PHP_URL_HOST); 1382 $domain = parse_url($url,PHP_URL_HOST);
1362 if ($domain=='youtube.com' || $domain=='www.youtube.com') 1383 if ($domain=='youtube.com' || $domain=='www.youtube.com')
1363 { 1384 {
1364 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail 1385 parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail
1365 if (!empty($params['v'])) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="http://img.youtube.com/vi/'.htmlspecialchars($params['v']).'/2.jpg" width="120" height="90"></a></div>'; 1386 if (!empty($params['v'])) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="http://img.youtube.com/vi/'.htmlspecialchars($params['v']).'/default.jpg" width="120" height="90"></a></div>';
1366 } 1387 }
1367 if ($domain=='imgur.com') 1388 if ($domain=='imgur.com')
1368 { 1389 {
@@ -1393,9 +1414,13 @@ function thumbnail($url)
1393 } 1414 }
1394 } 1415 }
1395 1416
1417
1396 // Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL. 1418 // Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL.
1397 // So we deport the thumbnail generation in order not to slow down page generation 1419 // So we deport the thumbnail generation in order not to slow down page generation
1398 // (and we also cache the thumbnail) 1420 // (and we also cache the thumbnail)
1421
1422 if (!$GLOBALS['config']['ENABLE_LOCALCACHE']) return ''; // If local cache is disabled, no thumbnails for services which require the use a local cache.
1423
1399 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') || $domain=='vimeo.com') 1424 if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') || $domain=='vimeo.com')
1400 { 1425 {
1401 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation) 1426 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
@@ -1432,7 +1457,7 @@ function templatePage($data)
1432 if ($newversion!='') $newversion='<div id="newversion"><span style="text-decoration:blink;">&#x25CF;</span> Shaarli '.htmlspecialchars($newversion).' is <a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli#download">available</a>.</div>'; 1457 if ($newversion!='') $newversion='<div id="newversion"><span style="text-decoration:blink;">&#x25CF;</span> Shaarli '.htmlspecialchars($newversion).' is <a href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli#download">available</a>.</div>';
1433 $linkcount = count($LINKSDB); 1458 $linkcount = count($LINKSDB);
1434 $open=''; 1459 $open='';
1435 if (OPEN_SHAARLI) 1460 if ($GLOBALS['config']['OPEN_SHAARLI'])
1436 { 1461 {
1437 $menu=' <a href="?do=tools">Tools</a> &nbsp;<a href="?do=addlink"><b>Add link</b></a>'; 1462 $menu=' <a href="?do=tools">Tools</a> &nbsp;<a href="?do=addlink"><b>Add link</b></a>';
1438 $open='Open '; 1463 $open='Open ';
@@ -1445,10 +1470,10 @@ function templatePage($data)
1445 if (!array_key_exists($k,$data)) $data[$k]=''; 1470 if (!array_key_exists($k,$data)) $data[$k]='';
1446 } 1471 }
1447 $jsincludes=''; $jsincludes_bottom = ''; 1472 $jsincludes=''; $jsincludes_bottom = '';
1448 if (OPEN_SHAARLI || isLoggedIn()) 1473 if ($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn())
1449 { 1474 {
1450 $jsincludes='<script language="JavaScript" src="jquery.min.js"></script><script language="JavaScript" src="jquery-ui.custom.min.js"></script>';
1451 $source = serverUrl().$_SERVER['SCRIPT_NAME']; 1475 $source = serverUrl().$_SERVER['SCRIPT_NAME'];
1476 $jsincludes='<script language="JavaScript" src="jquery.min.js"></script><script language="JavaScript" src="jquery-ui.custom.min.js"></script>';
1452 $jsincludes_bottom = <<<JS 1477 $jsincludes_bottom = <<<JS
1453<script language="JavaScript"> 1478<script language="JavaScript">
1454$(document).ready(function() 1479$(document).ready(function()
@@ -1464,20 +1489,23 @@ JS;
1464 $feedsearch=''; 1489 $feedsearch='';
1465 if (!empty($_GET['searchtags'])) $feedsearch.='&searchtags='.$_GET['searchtags']; 1490 if (!empty($_GET['searchtags'])) $feedsearch.='&searchtags='.$_GET['searchtags'];
1466 elseif (!empty($_GET['searchterm'])) $feedsearch.='&searchterm='.$_GET['searchterm']; 1491 elseif (!empty($_GET['searchterm'])) $feedsearch.='&searchterm='.$_GET['searchterm'];
1492 $filtered_feed= ($feedsearch=='' ? '' : 'Filtered ');
1467 $version=shaarli_version; 1493 $version=shaarli_version;
1468 1494
1469 $title = htmlspecialchars( $GLOBALS['title'] ); 1495 $title = htmlspecialchars( $GLOBALS['title'] );
1496 $pagetitle = htmlspecialchars( empty($GLOBALS['pagetitle']) ? $title : $GLOBALS['pagetitle'] );
1470 echo <<<HTML 1497 echo <<<HTML
1471<html> 1498<html>
1472<head> 1499<head>
1473<title>{$title}</title> 1500<title>{$pagetitle}</title>
1474<link rel="alternate" type="application/rss+xml" href="{$feedurl}" /> 1501<link rel="alternate" type="application/rss+xml" href="{$feedurl}?do=rss{$feedsearch}" title="{$filtered_feed}RSS Feed" />
1502<link rel="alternate" type="application/atom+xml" href="{$feedurl}?do=atom{$feedsearch}" title="{$filtered_feed}ATOM Feed" />
1475<link type="text/css" rel="stylesheet" href="shaarli.css?version={$version}" /> 1503<link type="text/css" rel="stylesheet" href="shaarli.css?version={$version}" />
1476{$jsincludes} 1504{$jsincludes}
1477</head> 1505</head>
1478<body {$data['onload']}>{$newversion} 1506<body {$data['onload']}>{$newversion}
1479<div id="pageheader"><div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;">Shaare your links...<br>{$linkcount} links</div> 1507<div id="pageheader"><div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;">Shaare your links...<br>{$linkcount} links</div>
1480 <b><i>{$title}</i></b> - <a href="?">Home</a>&nbsp;{$menu}&nbsp;<a href="{$feedurl}?do=rss{$feedsearch}" style="padding-left:30px;">RSS Feed</a> <a href="{$feedurl}?do=atom{$feedsearch}" style="padding-left:10px;">ATOM Feed</a> 1508 <span id="shaarli_title"><a href="?">{$title}</a></span> - <a href="?">Home</a>&nbsp;{$menu}&nbsp;<a href="{$feedurl}?do=rss{$feedsearch}" style="padding-left:30px;">RSS Feed</a> <a href="{$feedurl}?do=atom{$feedsearch}" style="padding-left:10px;">ATOM Feed</a>
1481&nbsp;&nbsp; <a href="?do=tagcloud">Tag cloud</a> 1509&nbsp;&nbsp; <a href="?do=tagcloud">Tag cloud</a>
1482{$data['pageheader']} 1510{$data['pageheader']}
1483</div> 1511</div>
@@ -1617,7 +1645,7 @@ function processWS()
1617 if (empty($_SESSION['tags'])) $_SESSION['tags'] = $LINKSDB->allTags(); 1645 if (empty($_SESSION['tags'])) $_SESSION['tags'] = $LINKSDB->allTags();
1618 foreach($_SESSION['tags'] as $key=>$value) 1646 foreach($_SESSION['tags'] as $key=>$value)
1619 { 1647 {
1620 if (startsWith($key,$last,$case=false) && !in_array($key,$tags)) $suggested[$addtags.$key.' ']=0;//FIXME 1648 if (startsWith($key,$last,$case=false) && !in_array($key,$tags)) $suggested[$addtags.$key.' ']=0;
1621 } 1649 }
1622 echo json_encode(array_keys($suggested)); 1650 echo json_encode(array_keys($suggested));
1623 exit; 1651 exit;
@@ -1643,10 +1671,13 @@ function processWS()
1643// (otherwise, the function simply returns.) 1671// (otherwise, the function simply returns.)
1644function writeConfig() 1672function writeConfig()
1645{ 1673{
1646 if (is_file(CONFIG_FILE) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config. 1674 if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
1675 if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
1647 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; '; 1676 $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
1648 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).'; ?>'; 1677 $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
1649 if (!file_put_contents(CONFIG_FILE,$config) || strcmp(file_get_contents(CONFIG_FILE),$config)!=0) 1678 $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
1679 $config .= ' ?>';
1680 if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
1650 { 1681 {
1651 echo '<script language="JavaScript">alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>'; 1682 echo '<script language="JavaScript">alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>';
1652 exit; 1683 exit;
@@ -1670,18 +1701,18 @@ function genThumbnail()
1670 1701
1671 // Let's see if we don't already have the image for this URL in the cache. 1702 // Let's see if we don't already have the image for this URL in the cache.
1672 $thumbname=hash('sha1',$_GET['url']).'.jpg'; 1703 $thumbname=hash('sha1',$_GET['url']).'.jpg';
1673 if (is_file(CACHEDIR.'/'.$thumbname)) 1704 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$thumbname))
1674 { // We have the thumbnail, just serve it: 1705 { // We have the thumbnail, just serve it:
1675 header('Content-Type: image/jpeg'); 1706 header('Content-Type: image/jpeg');
1676 echo file_get_contents(CACHEDIR.'/'.$thumbname); 1707 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname);
1677 return; 1708 return;
1678 } 1709 }
1679 // We may also serve a blank image (if service did not respond) 1710 // We may also serve a blank image (if service did not respond)
1680 $blankname=hash('sha1',$_GET['url']).'.gif'; 1711 $blankname=hash('sha1',$_GET['url']).'.gif';
1681 if (is_file(CACHEDIR.'/'.$blankname)) 1712 if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$blankname))
1682 { 1713 {
1683 header('Content-Type: image/gif'); 1714 header('Content-Type: image/gif');
1684 echo file_get_contents(CACHEDIR.'/'.$blankname); 1715 echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname);
1685 return; 1716 return;
1686 } 1717 }
1687 1718
@@ -1721,7 +1752,7 @@ function genThumbnail()
1721 list($httpstatus,$headers,$data) = getHTTP($imageurl,10); // Image is 240x120, so 10 seconds to download should be enough. 1752 list($httpstatus,$headers,$data) = getHTTP($imageurl,10); // Image is 240x120, so 10 seconds to download should be enough.
1722 if (strpos($httpstatus,'200 OK')) 1753 if (strpos($httpstatus,'200 OK'))
1723 { 1754 {
1724 file_put_contents(CACHEDIR.'/'.$thumbname,$data); // Save image to cache. 1755 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname,$data); // Save image to cache.
1725 header('Content-Type: image/jpeg'); 1756 header('Content-Type: image/jpeg');
1726 echo $data; 1757 echo $data;
1727 return; 1758 return;
@@ -1743,7 +1774,7 @@ function genThumbnail()
1743 list($httpstatus,$headers,$data) = getHTTP($imageurl,10); 1774 list($httpstatus,$headers,$data) = getHTTP($imageurl,10);
1744 if (strpos($httpstatus,'200 OK')) 1775 if (strpos($httpstatus,'200 OK'))
1745 { 1776 {
1746 file_put_contents(CACHEDIR.'/'.$thumbname,$data); // Save image to cache. 1777 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname,$data); // Save image to cache.
1747 header('Content-Type: image/jpeg'); 1778 header('Content-Type: image/jpeg');
1748 echo $data; 1779 echo $data;
1749 return; 1780 return;
@@ -1755,7 +1786,7 @@ function genThumbnail()
1755 list($httpstatus,$headers,$data) = getHTTP($url,30); // We allow 30 seconds max to download (and downloads are limited to 4 Mb) 1786 list($httpstatus,$headers,$data) = getHTTP($url,30); // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
1756 if (strpos($httpstatus,'200 OK')) 1787 if (strpos($httpstatus,'200 OK'))
1757 { 1788 {
1758 $filepath=CACHEDIR.'/'.$thumbname; 1789 $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname;
1759 file_put_contents($filepath,$data); // Save image to cache. 1790 file_put_contents($filepath,$data); // Save image to cache.
1760 if (resizeImage($filepath)) 1791 if (resizeImage($filepath))
1761 { 1792 {
@@ -1765,9 +1796,10 @@ function genThumbnail()
1765 } 1796 }
1766 } 1797 }
1767 1798
1799
1768 // Otherwise, return an empty image (8x8 transparent gif) 1800 // Otherwise, return an empty image (8x8 transparent gif)
1769 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7'); 1801 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
1770 file_put_contents(CACHEDIR.'/'.$blankname,$blankgif); // Also put something in cache so that this URL is not requested twice. 1802 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname,$blankgif); // Also put something in cache so that this URL is not requested twice.
1771 header('Content-Type: image/gif'); 1803 header('Content-Type: image/gif');
1772 echo $blankgif; 1804 echo $blankgif;
1773} 1805}
@@ -1788,11 +1820,13 @@ function resizeImage($filepath)
1788 if (!$im) return false; // Unable to open image (corrupted or not an image) 1820 if (!$im) return false; // Unable to open image (corrupted or not an image)
1789 $w = imagesx($im); 1821 $w = imagesx($im);
1790 $h = imagesy($im); 1822 $h = imagesy($im);
1823 $ystart = 0; $yheight=$h;
1824 if ($h>$w) { $ystart= ($h/2)-($w/2); $yheight=$w/2; }
1791 $nw = 120; // Desired width 1825 $nw = 120; // Desired width
1792 $nh = floor(($h*$nw)/$w); // Compute new width/height while keeping ratio 1826 $nh = min(floor(($h*$nw)/$w),120); // Compute new width/height, but maximum 120 pixels height.
1793 // Resize image: 1827 // Resize image:
1794 $im2 = imagecreatetruecolor($nw,$nh); 1828 $im2 = imagecreatetruecolor($nw,$nh);
1795 imagecopyresampled($im2, $im, 0, 0, 0, 0, $nw, $nh, $w, $h); 1829 imagecopyresampled($im2, $im, 0, 0, 0, $ystart, $nw, $nh, $w, $yheight);
1796 imageinterlace($im2,true); // For progressive JPEG. 1830 imageinterlace($im2,true); // For progressive JPEG.
1797 $tempname=$filepath.'_TEMP.jpg'; 1831 $tempname=$filepath.'_TEMP.jpg';
1798 imagejpeg($im2, $tempname, 90); 1832 imagejpeg($im2, $tempname, 90);
@@ -1810,9 +1844,9 @@ function invalidateCaches()
1810} 1844}
1811 1845
1812if (startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. 1846if (startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
1813$LINKSDB=new linkdb(isLoggedIn() || OPEN_SHAARLI); // Read links from database (and filter private links if used it not logged in). 1847$LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
1814if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) 1848if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI)
1815if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=LINKS_PER_PAGE; 1849if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE'];
1816if (startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } 1850if (startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
1817if (startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } 1851if (startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
1818renderPage(); 1852renderPage();
diff --git a/shaarli.css b/shaarli.css
index a3b9434b..320c7a43 100644
--- a/shaarli.css
+++ b/shaarli.css
@@ -33,10 +33,13 @@ filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#6A6A6A', EndC
33padding: 0 10 5 10; 33padding: 0 10 5 10;
34margin: auto; 34margin: auto;
35} 35}
36#shaarli_title { font-weight:bold; font-style:italic; }
37#shaarli_title a { color: #fff !important; }
36#pageheader a:link { color:#bbb; text-decoration:none;} 38#pageheader a:link { color:#bbb; text-decoration:none;}
37#pageheader a:visited { color:#bbb; text-decoration:none;} 39#pageheader a:visited { color:#bbb; text-decoration:none;}
38#pageheader a:hover { color:#FFFFC9; text-decoration:none;} 40#pageheader a:hover { color:#FFFFC9; text-decoration:none;}
39#pageheader a:active { color:#bbb; text-decoration:none;} 41#pageheader a:active { color:#bbb; text-decoration:none;}
42#searchcriteria { padding: 4 0 5 5; font-weight:bold;}
40.paging { background-color:#777; color:#ccc; text-align:center; padding:0 0 3 0; clear:both;} 43.paging { background-color:#777; color:#ccc; text-align:center; padding:0 0 3 0; clear:both;}
41.paging a:link { color:#ccc; text-decoration:none;} 44.paging a:link { color:#ccc; text-decoration:none;}
42.paging a:visited { color:#ccc; } 45.paging a:visited { color:#ccc; }
@@ -47,17 +50,20 @@ margin: auto;
47#linklist li { padding:4 10 8 20; border-top: 1px solid #bbb; clear:both; } 50#linklist li { padding:4 10 8 20; border-top: 1px solid #bbb; clear:both; }
48#linklist li.private { background-color: #ccc; border-left:8px solid #888; } 51#linklist li.private { background-color: #ccc; border-left:8px solid #888; }
49.linktitle { font-size:14pt; font-weight:bold; } 52.linktitle { font-size:14pt; font-weight:bold; }
50.linktitle a { text-decoration: none; color:#0000EE; } 53.linktitle a { text-decoration: none; color:#3465A4; }
51.linktitle a:hover { text-decoration: underline; } 54.linktitle a:hover { color:#F57900; }
52.linktitle a:visited { color:#0000BB; }
53.linkdate { font-size:8pt; color:#888; } 55.linkdate { font-size:8pt; color:#888; }
54.linkdate a { text-decoration: none; color:#888; } 56.linkdate a { text-decoration: none; color:#888; }
55.linkdate a:hover { text-decoration: underline; } 57.linkdate a:hover { color: #F57900 }
56.linkurl { font-size:8pt; color:#4BAA74; } 58.linkurl { font-size:8pt; color:#4BAA74; }
57.linkdescription { color:#000; margin-top:0px; margin-bottom:0px; font-weight:normal; } 59.linkdescription { color:#000; margin-top:0px; margin-bottom:0px; font-weight:normal; }
60.linkdescription a { text-decoration: none; color:#3465A4; }
61.linkdescription a:hover { color:#F57900; }
62.linktaglist { }
58.linktag { font-size:9pt; color:#777; background-color:#ddd; padding:0 6 0 6; -moz-box-shadow: inset 2px 2px 3px #ffffff; -webkit-box-shadow: inset 2px 2px 3px #ffffff; box-shadow: inset 2px 2px 3px ffffff; 63.linktag { font-size:9pt; color:#777; background-color:#ddd; padding:0 6 0 6; -moz-box-shadow: inset 2px 2px 3px #ffffff; -webkit-box-shadow: inset 2px 2px 3px #ffffff; box-shadow: inset 2px 2px 3px ffffff;
59border-bottom:1px solid #aaa; border-right:1px solid #aaa; } 64border-bottom:1px solid #aaa; border-right:1px solid #aaa; border-radius: 0.3em; }
60.linktag a { color:#777; text-decoration:none; } 65.linktag:hover { border-color: #555573; color:#000; }
66.linktag a { color:#777; text-decoration:none; }
61.linkshort { font-size:8pt; color:#888; } 67.linkshort { font-size:8pt; color:#888; }
62.linkshort a { text-decoration: none; color:#393964; } 68.linkshort a { text-decoration: none; color:#393964; }
63.linkshort a:hover { text-decoration: underline; } 69.linkshort a:hover { text-decoration: underline; }
@@ -68,16 +74,27 @@ border-bottom:1px solid #aaa; border-right:1px solid #aaa; }
68#cloudtag a { color:black; text-decoration:none; } 74#cloudtag a { color:black; text-decoration:none; }
69#installform td { font-size: 10pt; padding:10 5 10 5; } 75#installform td { font-size: 10pt; padding:10 5 10 5; }
70#configform td { color:#ccc; font-size: 10pt; padding:10 5 10 5; } 76#configform td { color:#ccc; font-size: 10pt; padding:10 5 10 5; }
71.thumbnail { float:right; margin-left: 10px; } 77.thumbnail { float:right; margin-left: 10px; }
72 78/* If you want thumbnails on the left:
73/* If you want thumbnails on the right:
74.thumbnail { float:left; margin-right: 10px; } 79.thumbnail { float:left; margin-right: 10px; }
75.linkcontainer { position: static; margin-left:130px; } 80.linkcontainer { position: static; margin-left:130px; }
76*/ 81*/
77 82
78
79/* Minimal customisation for jQuery widgets */ 83/* Minimal customisation for jQuery widgets */
80.ui-autocomplete { background-color:#fff; padding-left:5px;} 84.ui-autocomplete { background-color:#fff; padding-left:5px;}
81.ui-state-hover { background-color: #604dff; color:#fff; } 85.ui-state-hover { background-color: #604dff; color:#fff; }
82 86
87@media print {
88html {border:none;background:#fff!important;color:#000!important;}
89body {font-size:12pt;width:auto!important;margin:auto!important;}
90p {orphans:3; /*pas de ligne seule en bas */widows:3;/*pas de ligne seule en haut*/}
91a {color:#000!important;text-decoration:none!important;}
92#pageheader, .paging, #linklist li form, #footer {display:none;}
93#linklist li { padding:2 0 10 0; border-top: 2px solid #000; clear:both; }
94#linklist li.private { background-color: none; border-left:0; }
95.linkdate { line-height:2; }
96.linkurl { color:#000; }
97.linkdescription { font-size:10pt;}
98.linktag { border: 1px solid black; font-style:italic; font-size:8pt;}
99
83} \ No newline at end of file 100} \ No newline at end of file