diff options
-rw-r--r-- | index.php | 190 | ||||
-rw-r--r-- | shaarli.css | 37 |
2 files changed, 139 insertions, 88 deletions
@@ -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: |
11 | define('DATADIR','data'); // Data subdirectory | 11 | $GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory |
12 | define('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) |
13 | define('DATASTORE',DATADIR.'/datastore.php'); // Data storage file. | 13 | $GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file. |
14 | define('LINKS_PER_PAGE',20); // Default links per page. | 14 | $GLOBALS['config']['LINKS_PER_PAGE'] = 20; // Default links per page. |
15 | define('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. |
16 | define('BAN_AFTER',4); // Ban IP after this many failures. | 16 | $GLOBALS['config']['BAN_AFTER'] = 4; // Ban IP after this many failures. |
17 | define('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) |
18 | define('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 |
19 | define('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. |
20 | define('ENABLE_THUMBNAILS',true); // Enable thumbnails in links. | 20 | $GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links. |
21 | define('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 !) |
25 | define('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. |
26 | define('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 |
27 | ini_set('max_input_time','60'); // High execution time in case of problematic imports/exports. | 28 | ini_set('max_input_time','60'); // High execution time in case of problematic imports/exports. |
28 | ini_set('memory_limit', '128M'); // Try to set max upload file size and read (May not work on some hosts). | 29 | ini_set('memory_limit', '128M'); // Try to set max upload file size and read (May not work on some hosts). |
29 | ini_set('post_max_size', '16M'); | 30 | ini_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) |
37 | ob_start(); | 38 | ob_start(); |
38 | 39 | ||
40 | // Optionnal config file. | ||
41 | if (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: |
40 | if (get_magic_quotes_gpc()) | 44 | if (get_magic_quotes_gpc()) |
41 | { | 45 | { |
@@ -49,13 +53,16 @@ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); | |||
49 | header("Cache-Control: no-store, no-cache, must-revalidate"); | 53 | header("Cache-Control: no-store, no-cache, must-revalidate"); |
50 | header("Cache-Control: post-check=0, pre-check=0", false); | 54 | header("Cache-Control: post-check=0, pre-check=0", false); |
51 | header("Pragma: no-cache"); | 55 | header("Pragma: no-cache"); |
52 | define('shaarli_version','0.0.24 beta'); | 56 | define('shaarli_version','0.0.25 beta'); |
53 | if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); } | 57 | if (!is_dir($GLOBALS['config']['DATADIR'])) { mkdir($GLOBALS['config']['DATADIR'],0705); chmod($GLOBALS['config']['DATADIR'],0705); } |
54 | if (!is_dir(CACHEDIR)) { mkdir(CACHEDIR,0705); chmod(CACHEDIR,0705); } | 58 | if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['DATADIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. |
55 | if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. | 59 | if ($GLOBALS['config']['ENABLE_LOCALCACHE']) |
56 | if (!is_file(CACHEDIR.'/.htaccess')) { file_put_contents(CACHEDIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. | 60 | { |
57 | if (!is_file(CONFIG_FILE)) install(); | 61 | if (!is_dir($GLOBALS['config']['CACHEDIR'])) { mkdir($GLOBALS['config']['CACHEDIR'],0705); chmod($GLOBALS['config']['CACHEDIR'],0705); } |
58 | require 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 | } | ||
64 | if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install(); | ||
65 | require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS. | ||
59 | // Small protection against dodgy config files: | 66 | // Small protection against dodgy config files: |
60 | if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']); | 67 | if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']); |
61 | if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get(); | 68 | if (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() | |||
102 | function logm($message) | 109 | function 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 |
127 | function text2clickable($url) | 134 | function 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. |
181 | function isLoggedIn() | 189 | function 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. |
204 | if (!is_file(IPBANS_FILENAME)) file_put_contents(IPBANS_FILENAME, "<?php\n\$GLOBALS['IPBANS']=".var_export(array('FAILURES'=>array(),'BANS'=>array()),true).";\n?>"); | 212 | if (!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?>"); |
205 | include IPBANS_FILENAME; | 213 | include $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: |
207 | function ban_loginFailed() | 215 | function 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.) |
421 | function html_extract_title($html) | 429 | function 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=' <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=' <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...) |
1354 | function thumbnail($url) | 1375 | function 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;">●</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;">●</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> <a href="?do=addlink"><b>Add link</b></a>'; | 1462 | $menu=' <a href="?do=tools">Tools</a> <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> {$menu} <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> {$menu} <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 | <a href="?do=tagcloud">Tag cloud</a> | 1509 | <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.) |
1644 | function writeConfig() | 1672 | function 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 | ||
1812 | if (startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. | 1846 | if (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). |
1814 | if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) | 1848 | if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) |
1815 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=LINKS_PER_PAGE; | 1849 | if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE']; |
1816 | if (startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } | 1850 | if (startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } |
1817 | if (startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } | 1851 | if (startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } |
1818 | renderPage(); | 1852 | renderPage(); |
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 | |||
33 | padding: 0 10 5 10; | 33 | padding: 0 10 5 10; |
34 | margin: auto; | 34 | margin: 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; |
59 | border-bottom:1px solid #aaa; border-right:1px solid #aaa; } | 64 | border-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 { | ||
88 | html {border:none;background:#fff!important;color:#000!important;} | ||
89 | body {font-size:12pt;width:auto!important;margin:auto!important;} | ||
90 | p {orphans:3; /*pas de ligne seule en bas */widows:3;/*pas de ligne seule en haut*/} | ||
91 | a {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 |