<?php
-// Shaarli 0.0.42 beta - Shaare your links...
+// Shaarli 0.0.45beta - Shaare your links...
// The personal, minimalist, super-fast, no-database Delicious clone. By sebsauvage.net
// http://sebsauvage.net/wiki/doku.php?id=php:shaarli
// Licence: http://www.opensource.org/licenses/zlib-license.php
-// Requires: PHP 5.1.x (but autocomplete fields will only work if you have PHP 5.2.x)
+// Requires: PHP 5.3.x
// -----------------------------------------------------------------------------------------------
// NEVER TRUST IN PHP.INI
// Some hosts do not define a default timezone in php.ini,
date_default_timezone_set('UTC');
// -----------------------------------------------------------------------------------------------
-// Hardcoded parameter (These parameters can be overwritten by creating the file /config/options.php)
+// Hardcoded parameter (These parameters can be overwritten by editing the file /data/config.php)
+// You should not touch any code below (or at your own risks!)
$GLOBALS['config']['DATADIR'] = 'data'; // Data subdirectory
$GLOBALS['config']['CONFIG_FILE'] = $GLOBALS['config']['DATADIR'].'/config.php'; // Configuration file (user login/password)
$GLOBALS['config']['DATASTORE'] = $GLOBALS['config']['DATADIR'].'/datastore.php'; // Data storage file.
$GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours
// Note: You must have publisher.php in the same directory as Shaarli index.php
$GLOBALS['config']['ARCHIVE_ORG'] = false; // For each link, add a link to an archived version on archive.org
+$GLOBALS['config']['ENABLE_RSS_PERMALINKS'] = true; // Enable RSS permalinks by default. This corresponds to the default behavior of shaarli before this was added as an option.
+$GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
// -----------------------------------------------------------------------------------------------
-// You should not touch below (or at your own risks!)
-// Optional config file.
-if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
-
-define('shaarli_version','0.0.42 beta');
-define('PHPPREFIX','<?php /* '); // Prefix to encapsulate data in PHP code.
-define('PHPSUFFIX',' */ ?>'); // Suffix to encapsulate data in PHP code.
+define('shaarli_version','0.0.45beta');
// http://server.com/x/shaarli --> /shaarli/
define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
// Force cookie path (but do not change lifetime)
$cookie=session_get_cookie_params();
$cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['HTTP_HOST']); // Set default cookie expiration and path.
+session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['SERVER_NAME']); // Set default cookie expiration and path.
// Set session parameters on server side.
define('INACTIVITY_TIMEOUT',3600); // (in seconds). If the user does not access any page within this time, his/her session is considered expired.
ini_set('memory_limit', '128M'); // Try to set max upload file size and read (May not work on some hosts).
ini_set('post_max_size', '16M');
ini_set('upload_max_filesize', '16M');
-checkphpversion();
error_reporting(E_ALL^E_WARNING); // See all error except warnings.
//error_reporting(-1); // See all errors (for debugging only)
+// User configuration
+if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
+ require_once $GLOBALS['config']['CONFIG_FILE'];
+}
+
+// Shaarli library
+require_once 'application/LinkDB.php';
+require_once 'application/TimeZone.php';
+require_once 'application/Utils.php';
+require_once 'application/Config.php';
+
+// Ensure the PHP version is supported
+try {
+ checkPHPVersion('5.3', PHP_VERSION);
+} catch(Exception $e) {
+ header('Content-Type: text/plain; charset=utf-8');
+ echo $e->getMessage();
+ exit;
+}
+
include "inc/rain.tpl.class.php"; //include Rain TPL
raintpl::$tpl_dir = $GLOBALS['config']['RAINTPL_TPL']; // template directory
raintpl::$cache_dir = $GLOBALS['config']['RAINTPL_TMP']; // cache directory
header("Pragma: no-cache");
// Directories creations (Note that your web host may require different rights than 705.)
-if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory ('.realpath(dirname(__FILE__)).').</pre>');
+if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory.</pre>');
// Handling of old config file which do not have the new parameters.
-if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(indexUrl());
+if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(indexUrl());
if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
-if (empty($GLOBALS['disablejquery'])) $GLOBALS['disablejquery']=false;
if (empty($GLOBALS['privateLinkByDefault'])) $GLOBALS['privateLinkByDefault']=false;
if (empty($GLOBALS['titleLink'])) $GLOBALS['titleLink']='?';
// I really need to rewrite Shaarli with a proper configuation manager.
// Run config screen if first run:
-if (!is_file($GLOBALS['config']['CONFIG_FILE'])) install();
+if (! is_file($GLOBALS['config']['CONFIG_FILE'])) {
+ install();
+}
-require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
+$GLOBALS['title'] = !empty($GLOBALS['title']) ? escape($GLOBALS['title']) : '';
+$GLOBALS['titleLink'] = !empty($GLOBALS['titleLink']) ? escape($GLOBALS['titleLink']) : '';
+$GLOBALS['redirector'] = !empty($GLOBALS['redirector']) ? escape($GLOBALS['redirector']) : '';
// a token depending of deployment salt, user password, and the current ip
define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GLOBALS['salt']));
autoLocale(); // Sniff browser language and set date format accordingly.
header('Content-Type: text/html; charset=utf-8'); // We use UTF-8 for proper international characters handling.
-// Check PHP version
-function checkphpversion()
-{
- if (version_compare(PHP_VERSION, '5.1.0') < 0)
- {
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Your server supports PHP '.PHP_VERSION.'. Shaarli requires at least php 5.1.0, and thus cannot run. Sorry.';
- exit;
- }
+//==================================================================================================
+// Checking session state (i.e. is the user still logged in)
+//==================================================================================================
+
+function setup_login_state() {
+ $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
+ $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
+ if ($GLOBALS['config']['OPEN_SHAARLI']) {
+ $userIsLoggedIn = true;
+ }
+ if (!isset($GLOBALS['login'])) {
+ $userIsLoggedIn = false; // Shaarli is not configured yet.
+ $loginFailure = true;
+ }
+ if (isset($_COOKIE['shaarli_staySignedIn']) &&
+ $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
+ !$loginFailure)
+ {
+ fillSessionInfo();
+ $userIsLoggedIn = true;
+ }
+ // If session does not exist on server side, or IP address has changed, or session has expired, logout.
+ if (empty($_SESSION['uid']) ||
+ ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) ||
+ time() >= $_SESSION['expires_on'])
+ {
+ logout();
+ $userIsLoggedIn = false;
+ $loginFailure = true;
+ }
+ if (!empty($_SESSION['longlastingsession'])) {
+ $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
+ }
+ else {
+ $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
+ }
+ if (!$loginFailure) {
+ $userIsLoggedIn = true;
+ }
+
+ return $userIsLoggedIn;
}
+$userIsLoggedIn = setup_login_state();
// Checks if an update is available for Shaarli.
// (at most once a day, and only for registered user.)
function checkUpdate()
{
if (!isLoggedIn()) return ''; // Do not check versions for visitors.
+ if (empty($GLOBALS['config']['ENABLE_UPDATECHECK'])) return ''; // Do not check if the user doesn't want to.
// Get latest version number at most once a day.
if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL'])))
{
$version=shaarli_version;
- list($httpstatus,$headers,$data) = getHTTP('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.txt',2);
- if (strpos($httpstatus,'200 OK')!==false) $version=$data;
+ list($httpstatus,$headers,$data) = getHTTP('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.php',2);
+ if (strpos($httpstatus,'200 OK')!==false) $version=str_replace(' */ ?>','',str_replace('<?php /* ','',$data));
// If failed, never mind. We don't want to bother the user with that.
file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
}
file_put_contents($GLOBALS['config']['DATADIR'].'/log.txt',$t,FILE_APPEND);
}
-// Same as nl2br(), but escapes < and >
-function nl2br_escaped($html)
-{
- return str_replace('>','>',str_replace('<','<',nl2br($html)));
-}
-
-/* Returns the small hash of a string, using RFC 4648 base64url format
- e.g. smallHash('20111006_131924') --> yZH23w
- Small hashes:
- - are unique (well, as unique as crc32, at last)
- - are always 6 characters long.
- - only use the following characters: a-z A-Z 0-9 - _ @
- - are NOT cryptographically secure (they CAN be forged)
- In Shaarli, they are used as a tinyurl-like link to individual entries.
-*/
-function smallHash($text)
-{
- $t = rtrim(base64_encode(hash('crc32',$text,true)),'=');
- return strtr($t, '+/', '-_');
-}
-
// In a string, converts URLs to clickable links.
// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
function text2clickable($url)
// (Note that is may not work on your server if the corresponding local is not installed.)
function autoLocale()
{
- $loc='en_US'; // Default if browser does not send HTTP_ACCEPT_LANGUAGE
+ $attempts = array('en_US'); // Default if browser does not send HTTP_ACCEPT_LANGUAGE
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // e.g. "fr,fr-fr;q=0.8,en;q=0.5,en-us;q=0.3"
{ // (It's a bit crude, but it works very well. Preferred language is always presented first.)
- if (preg_match('/([a-z]{2}(-[a-z]{2})?)/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) $loc=$matches[1];
+ if (preg_match('/([a-z]{2})-?([a-z]{2})?/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) {
+ $loc = $matches[1] . (!empty($matches[2]) ? '_' . strtoupper($matches[2]) : '');
+ $attempts = array($loc.'.UTF-8', $loc, str_replace('_', '-', $loc).'.UTF-8', str_replace('_', '-', $loc),
+ $loc . '_' . strtoupper($loc).'.UTF-8', $loc . '_' . strtoupper($loc),
+ $loc . '_' . $loc.'.UTF-8', $loc . '_' . $loc, $loc . '-' . strtoupper($loc).'.UTF-8',
+ $loc . '-' . strtoupper($loc), $loc . '-' . $loc.'.UTF-8', $loc . '-' . $loc);
+ }
}
- setlocale(LC_TIME,$loc); // LC_TIME = Set local for date/time format only.
+ setlocale(LC_TIME, $attempts); // LC_TIME = Set local for date/time format only.
}
// ------------------------------------------------------------------------------------------
// Returns true if the user is logged in.
function isLoggedIn()
{
- if ($GLOBALS['config']['OPEN_SHAARLI']) return true;
-
- if (!isset($GLOBALS['login'])) return false; // Shaarli is not configured yet.
-
- if (@$_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN)
- {
- fillSessionInfo();
- return true;
- }
- // If session does not exist on server side, or IP address has changed, or session has expired, logout.
- if (empty($_SESSION['uid']) || ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || time()>=$_SESSION['expires_on'])
- {
- logout();
- return false;
- }
- if (!empty($_SESSION['longlastingsession'])) $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
- else $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
-
- return true;
+ global $userIsLoggedIn;
+ return $userIsLoggedIn;
}
// Force logout.
-function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); }
-setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
+function logout() {
+ if (isset($_SESSION)) {
+ unset($_SESSION['uid']);
+ unset($_SESSION['ip']);
+ unset($_SESSION['username']);
+ unset($_SESSION['privateonly']);
+ }
+ setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
}
$_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // Set session expiration on server-side.
$cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
- session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['HTTP_HOST']); // Set session cookie expiration on client side
+ session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
// Note: Never forget the trailing slash on the cookie path!
session_regenerate_id(true); // Send cookie with new expiration date to browser.
}
else // Standard session expiration (=when browser closes)
{
$cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
- session_set_cookie_params(0,$cookiedir,$_SERVER['HTTP_HOST']); // 0 means "When browser closes"
+ session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes"
session_regenerate_id(true);
}
// Optional redirect after login:
ban_loginFailed();
$redir = '';
if (isset($_GET['post'])) { $redir = '&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):''); }
- echo '<script language="JavaScript">alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
+ echo '<script>alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
exit;
}
}
{
$https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection.
$serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]);
- return 'http'.($https?'s':'').'://'.$_SERVER['HTTP_HOST'].$serverport;
+ return 'http'.($https?'s':'').'://'.$_SERVER['SERVER_NAME'].$serverport;
}
// Returns the absolute URL of current script, without the query.
return $maxsize;
}
-// Tells if a string start with a substring or not.
-function startsWith($haystack,$needle,$case=true)
-{
- if($case){return (strcmp(substr($haystack, 0, strlen($needle)),$needle)===0);}
- return (strcasecmp(substr($haystack, 0, strlen($needle)),$needle)===0);
-}
-
-// Tells if a string ends with a substring or not.
-function endsWith($haystack,$needle,$case=true)
-{
- if($case){return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);}
- return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);
-}
-
/* Converts a linkdate time (YYYYMMDD_HHMMSS) of an article to a timestamp (Unix epoch)
(used to build the ADD_DATE attribute in Netscape-bookmarks file)
PS: I could have used strptime(), but it does not exist on Windows. I'm too kind. */
function linkdate2timestamp($linkdate)
{
- $Y=$M=$D=$h=$m=$s=0;
- $r = sscanf($linkdate,'%4d%2d%2d_%2d%2d%2d',$Y,$M,$D,$h,$m,$s);
- return mktime($h,$m,$s,$M,$D,$Y);
+ if(strcmp($linkdate, '_000000') !== 0 || !$linkdate){
+ $Y=$M=$D=$h=$m=$s=0;
+ $r = sscanf($linkdate,'%4d%2d%2d_%2d%2d%2d',$Y,$M,$D,$h,$m,$s);
+ return mktime($h,$m,$s,$M,$D,$Y);
+ }
+ return time();
}
/* Converts a linkdate time (YYYYMMDD_HHMMSS) of an article to a RFC822 date.
return date('c',linkdate2timestamp($linkdate)); // 'c' is for ISO 8601 date format.
}
-/* Converts a linkdate time (YYYYMMDD_HHMMSS) of an article to a localized date format.
- (used to display link date on screen)
- The date format is automatically chosen according to locale/languages sniffed from browser headers (see autoLocale()). */
-function linkdate2locale($linkdate)
-{
- return utf8_encode(strftime('%c',linkdate2timestamp($linkdate))); // %c is for automatic date format according to locale.
- // Note that if you use a locale which is not installed on your webserver,
- // the date will not be displayed in the chosen locale, but probably in US notation.
-}
-
// Parse HTTP response headers and return an associative array.
function http_parse_headers_shaarli( $headers )
{
private function initialize()
{
$this->tpl = new RainTPL;
- $this->tpl->assign('newversion',checkUpdate());
- $this->tpl->assign('feedurl',htmlspecialchars(indexUrl()));
+ $this->tpl->assign('newversion',escape(checkUpdate()));
+ $this->tpl->assign('feedurl',escape(indexUrl()));
$searchcrits=''; // Search criteria
if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.urlencode($_GET['searchtags']);
elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.urlencode($_GET['searchterm']);
}
}
-// ------------------------------------------------------------------------------------------
-/* Data storage for links.
- This object behaves like an associative array.
- Example:
- $mylinks = new linkdb();
- echo $mylinks['20110826_161819']['title'];
- foreach($mylinks as $link)
- echo $link['title'].' at url '.$link['url'].' ; description:'.$link['description'];
-
- Available keys:
- title : Title of the link
- url : URL of the link. Can be absolute or relative. Relative URLs are permalinks (e.g.'?m-ukcw')
- description : description of the entry
- private : Is this link private? 0=no, other value=yes
- linkdate : date of the creation of this entry, in the form YYYYMMDD_HHMMSS (e.g.'20110914_192317')
- tags : tags attached to this entry (separated by spaces)
-
- We implement 3 interfaces:
- - ArrayAccess so that this object behaves like an associative array.
- - Iterator so that this object can be used in foreach() loops.
- - Countable interface so that we can do a count() on this object.
-*/
-class linkdb implements Iterator, Countable, ArrayAccess
-{
- private $links; // List of links (associative array. Key=linkdate (e.g. "20110823_124546"), value= associative array (keys:title,description...)
- private $urls; // List of all recorded URLs (key=url, value=linkdate) for fast reserve search (url-->linkdate)
- private $keys; // List of linkdate keys (for the Iterator interface implementation)
- private $position; // Position in the $this->keys array. (for the Iterator interface implementation.)
- private $loggedin; // Is the user logged in? (used to filter private links)
-
- // Constructor:
- function __construct($isLoggedIn)
- // Input : $isLoggedIn : is the user logged in?
- {
- $this->loggedin = $isLoggedIn;
- $this->checkdb(); // Make sure data file exists.
- $this->readdb(); // Then read it.
- }
-
- // ---- Countable interface implementation
- public function count() { return count($this->links); }
-
- // ---- ArrayAccess interface implementation
- public function offsetSet($offset, $value)
- {
- if (!$this->loggedin) die('You are not authorized to add a link.');
- if (empty($value['linkdate']) || empty($value['url'])) die('Internal Error: A link should always have a linkdate and URL.');
- if (empty($offset)) die('You must specify a key.');
- $this->links[$offset] = $value;
- $this->urls[$value['url']]=$offset;
- }
- public function offsetExists($offset) { return array_key_exists($offset,$this->links); }
- public function offsetUnset($offset)
- {
- if (!$this->loggedin) die('You are not authorized to delete a link.');
- $url = $this->links[$offset]['url']; unset($this->urls[$url]);
- unset($this->links[$offset]);
- }
- public function offsetGet($offset) { return isset($this->links[$offset]) ? $this->links[$offset] : null; }
-
- // ---- Iterator interface implementation
- function rewind() { $this->keys=array_keys($this->links); rsort($this->keys); $this->position=0; } // Start over for iteration, ordered by date (latest first).
- function key() { return $this->keys[$this->position]; } // current key
- function current() { return $this->links[$this->keys[$this->position]]; } // current value
- function next() { ++$this->position; } // go to next item
- function valid() { return isset($this->keys[$this->position]); } // Check if current position is valid.
-
- // ---- Misc methods
- private function checkdb() // Check if db directory and file exists.
- {
- if (!file_exists($GLOBALS['config']['DATASTORE'])) // Create a dummy database for example.
- {
- $this->links = array();
- $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');
- $this->links[$link['linkdate']] = $link;
- $link = array('title'=>'My secret stuff... - Pastebin.com','url'=>'http://sebsauvage.net/paste/?8434b27936c09649#bR7XsXhoTiLcqCpQbmOpBi3rq2zzQUC5hBI7ZT1O3x8=','description'=>'SShhhh!! I\'m a private link only YOU can see. You can delete me too.','private'=>1,'linkdate'=>'20110914_074522','tags'=>'secretstuff');
- $this->links[$link['linkdate']] = $link;
- file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); // Write database to disk
- }
- }
-
- // Read database from disk to memory
- private function readdb()
- {
- // Read data
- $this->links=(file_exists($GLOBALS['config']['DATASTORE']) ? unserialize(gzinflate(base64_decode(substr(file_get_contents($GLOBALS['config']['DATASTORE']),strlen(PHPPREFIX),-strlen(PHPSUFFIX))))) : array() );
- // Note that gzinflate is faster than gzuncompress. See: http://www.php.net/manual/en/function.gzdeflate.php#96439
-
- // If user is not logged in, filter private links.
- if (!$this->loggedin)
- {
- $toremove=array();
- foreach($this->links as $link) { if ($link['private']!=0) $toremove[]=$link['linkdate']; }
- foreach($toremove as $linkdate) { unset($this->links[$linkdate]); }
- }
-
- // Keep the list of the mapping URLs-->linkdate up-to-date.
- $this->urls=array();
- foreach($this->links as $link) { $this->urls[$link['url']]=$link['linkdate']; }
- }
-
- // Save database from memory to disk.
- public function savedb()
- {
- if (!$this->loggedin) die('You are not authorized to change the database.');
- file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX);
- invalidateCaches();
- }
-
- // Returns the link for a given URL (if it exists). False if it does not exist.
- public function getLinkFromUrl($url)
- {
- if (isset($this->urls[$url])) return $this->links[$this->urls[$url]];
- return false;
- }
-
- // Case insensitive search among links (in the URLs, title and description). Returns filtered list of links.
- // e.g. print_r($mydb->filterFulltext('hollandais'));
- public function filterFulltext($searchterms)
- {
- // FIXME: explode(' ',$searchterms) and perform a AND search.
- // FIXME: accept double-quotes to search for a string "as is"?
- $filtered=array();
- $s = strtolower($searchterms);
- foreach($this->links as $l)
- {
- $found= (strpos(strtolower($l['title']),$s)!==false)
- || (strpos(strtolower($l['description']),$s)!==false)
- || (strpos(strtolower($l['url']),$s)!==false)
- || (strpos(strtolower($l['tags']),$s)!==false);
- if ($found) $filtered[$l['linkdate']] = $l;
- }
- krsort($filtered);
- return $filtered;
- }
-
- // Filter by tag.
- // You can specify one or more tags (tags can be separated by space or comma).
- // e.g. print_r($mydb->filterTags('linux programming'));
- public function filterTags($tags,$casesensitive=false)
- {
- $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags)));
- $searchtags=explode(' ',$t);
- $filtered=array();
- foreach($this->links as $l)
- {
- $linktags = explode(' ',($casesensitive?$l['tags']:strtolower($l['tags'])));
- if (count(array_intersect($linktags,$searchtags)) == count($searchtags))
- $filtered[$l['linkdate']] = $l;
- }
- krsort($filtered);
- return $filtered;
- }
-
- // Filter by day. Day must be in the form 'YYYYMMDD' (e.g. '20120125')
- // Sort order is: older articles first.
- // e.g. print_r($mydb->filterDay('20120125'));
- public function filterDay($day)
- {
- $filtered=array();
- foreach($this->links as $l)
- {
- if (startsWith($l['linkdate'],$day)) $filtered[$l['linkdate']] = $l;
- }
- ksort($filtered);
- return $filtered;
- }
- // Filter by smallHash.
- // Only 1 article is returned.
- public function filterSmallHash($smallHash)
- {
- $filtered=array();
- foreach($this->links as $l)
- {
- if ($smallHash==smallHash($l['linkdate'])) // Yes, this is ugly and slow
- {
- $filtered[$l['linkdate']] = $l;
- return $filtered;
- }
- }
- return $filtered;
- }
-
- // Returns the list of all tags
- // Output: associative array key=tags, value=0
- public function allTags()
- {
- $tags=array();
- foreach($this->links as $link)
- foreach(explode(' ',$link['tags']) as $tag)
- if (!empty($tag)) $tags[$tag]=(empty($tags[$tag]) ? 1 : $tags[$tag]+1);
- arsort($tags); // Sort tags by usage (most used tag first)
- return $tags;
- }
-
- // Returns the list of days containing articles (oldest first)
- // Output: An array containing days (in format YYYYMMDD).
- public function days()
- {
- $linkdays=array();
- foreach(array_keys($this->links) as $day)
- {
- $linkdays[substr($day,0,8)]=0;
- }
- $linkdays=array_keys($linkdays);
- sort($linkdays);
- return $linkdays;
- }
-}
-
// ------------------------------------------------------------------------------------------
// Output the last N links in RSS 2.0 format.
function showRSS()
// $usepermalink : If true, use permalink instead of final link.
// User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks
- $usepermalinks = isset($_GET['permalinks']);
+ // Also enabled through a config option
+ $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
// Cache system
$query = $_SERVER["QUERY_STRING"];
$cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; }
// If cached was not found (or not usable), then read the database and build the response:
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if user it not logged in).
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
+ // Read links from database (and filter private links if user it not logged in).
// Optionally filter the results:
$linksToDisplay=array();
if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
- elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
+ else if (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $linksToDisplay = $LINKSDB;
+
$nblinksToDisplay = 50; // Number of links to display.
if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
{
$nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
}
- $pageaddr=htmlspecialchars(indexUrl());
+ $pageaddr=escape(indexUrl());
echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">';
- echo '<channel><title>'.htmlspecialchars($GLOBALS['title']).'</title><link>'.$pageaddr.'</link>';
+ echo '<channel><title>'.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>';
echo '<description>Shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n\n";
if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
{
echo '<!-- PubSubHubbub Discovery -->';
- echo '<link rel="hub" href="'.htmlspecialchars($GLOBALS['config']['PUBSUBHUB_URL']).'" xmlns="http://www.w3.org/2005/Atom" />';
- echo '<link rel="self" href="'.htmlspecialchars($pageaddr).'?do=rss" xmlns="http://www.w3.org/2005/Atom" />';
+ echo '<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" xmlns="http://www.w3.org/2005/Atom" />';
+ echo '<link rel="self" href="'.$pageaddr.'?do=rss" xmlns="http://www.w3.org/2005/Atom" />';
echo '<!-- End Of PubSubHubbub Discovery -->';
}
$i=0;
$link = $linksToDisplay[$keys[$i]];
$guid = $pageaddr.'?'.smallHash($link['linkdate']);
$rfc822date = linkdate2rfc822($link['linkdate']);
- $absurl = htmlspecialchars($link['url']);
+ $absurl = $link['url'];
if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute
if ($usepermalinks===true)
- echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$guid.'</link>';
+ echo '<item><title>'.$link['title'].'</title><guid isPermaLink="true">'.$guid.'</guid><link>'.$guid.'</link>';
else
- echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>';
- if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) echo '<pubDate>'.htmlspecialchars($rfc822date)."</pubDate>\n";
+ echo '<item><title>'.$link['title'].'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>';
+ if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) echo '<pubDate>'.escape($rfc822date)."</pubDate>\n";
if ($link['tags']!='') // Adding tags to each RSS entry (as mentioned in RSS specification)
{
- foreach(explode(' ',$link['tags']) as $tag) { echo '<category domain="'.htmlspecialchars($pageaddr).'">'.htmlspecialchars($tag).'</category>'."\n"; }
+ foreach(explode(' ',$link['tags']) as $tag) { echo '<category domain="'.$pageaddr.'">'.$tag.'</category>'."\n"; }
}
// Add permalink in description
// If user wants permalinks first, put the final link in description
if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
- echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description'])))).$descriptionlink.']]></description>'."\n</item>\n";
+ echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable($link['description']))).$descriptionlink.']]></description>'."\n</item>\n";
$i++;
}
- echo '</channel></rss><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
+ echo '</channel></rss><!-- Cached version of '.escape(pageUrl()).' -->';
$cache->cache(ob_get_contents());
ob_end_flush();
// $usepermalink : If true, use permalink instead of final link.
// User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks
- $usepermalinks = isset($_GET['permalinks']);
+ $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
// Cache system
$query = $_SERVER["QUERY_STRING"];
$cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; }
// If cached was not found (or not usable), then read the database and build the response:
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
-
+// Read links from database (and filter private links if used it not logged in).
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
// Optionally filter the results:
$linksToDisplay=array();
if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']);
- elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
+ else if (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $linksToDisplay = $LINKSDB;
+
$nblinksToDisplay = 50; // Number of links to display.
if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
{
$nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
}
- $pageaddr=htmlspecialchars(indexUrl());
+ $pageaddr=escape(indexUrl());
$latestDate = '';
$entries='';
$i=0;
$guid = $pageaddr.'?'.smallHash($link['linkdate']);
$iso8601date = linkdate2iso8601($link['linkdate']);
$latestDate = max($latestDate,$iso8601date);
- $absurl = htmlspecialchars($link['url']);
+ $absurl = $link['url'];
if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute
- $entries.='<entry><title>'.htmlspecialchars($link['title']).'</title>';
+ $entries.='<entry><title>'.$link['title'].'</title>';
if ($usepermalinks===true)
$entries.='<link href="'.$guid.'" /><id>'.$guid.'</id>';
else
$entries.='<link href="'.$absurl.'" /><id>'.$guid.'</id>';
- if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $entries.='<updated>'.htmlspecialchars($iso8601date).'</updated>';
+ if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $entries.='<updated>'.escape($iso8601date).'</updated>';
// Add permalink in description
- $descriptionlink = htmlspecialchars('(<a href="'.$guid.'">Permalink</a>)');
+ $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
// If user wants permalinks first, put the final link in description
- if ($usepermalinks===true) $descriptionlink = htmlspecialchars('(<a href="'.$absurl.'">Link</a>)');
- if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
+ if ($usepermalinks===true) $descriptionlink = '(<a href="'.$absurl.'">Link</a>)';
+ if (strlen($link['description'])>0) $descriptionlink = '<br>'.$descriptionlink;
- $entries.='<content type="html">'.htmlspecialchars(nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))))).$descriptionlink."</content>\n";
+ $entries.='<content type="html"><![CDATA['.nl2br(keepMultipleSpaces(text2clickable($link['description']))).$descriptionlink."]]></content>\n";
if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification)
{
foreach(explode(' ',$link['tags']) as $tag)
- { $entries.='<category scheme="'.htmlspecialchars($pageaddr,ENT_QUOTES).'" term="'.htmlspecialchars($tag,ENT_QUOTES).'" />'."\n"; }
+ { $entries.='<category scheme="'.$pageaddr.'" term="'.$tag.'" />'."\n"; }
}
$entries.="</entry>\n";
$i++;
}
$feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
- $feed.='<title>'.htmlspecialchars($GLOBALS['title']).'</title>';
- if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.htmlspecialchars($latestDate).'</updated>';
- $feed.='<link rel="self" href="'.htmlspecialchars(serverUrl().$_SERVER["REQUEST_URI"]).'" />';
+ $feed.='<title>'.$GLOBALS['title'].'</title>';
+ if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.escape($latestDate).'</updated>';
+ $feed.='<link rel="self" href="'.escape(serverUrl().$_SERVER["REQUEST_URI"]).'" />';
if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
{
$feed.='<!-- PubSubHubbub Discovery -->';
- $feed.='<link rel="hub" href="'.htmlspecialchars($GLOBALS['config']['PUBSUBHUB_URL']).'" />';
+ $feed.='<link rel="hub" href="'.escape($GLOBALS['config']['PUBSUBHUB_URL']).'" />';
$feed.='<!-- End Of PubSubHubbub Discovery -->';
}
- $feed.='<author><name>'.htmlspecialchars($pageaddr).'</name><uri>'.htmlspecialchars($pageaddr).'</uri></author>';
- $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
+ $feed.='<author><name>'.$pageaddr.'</name><uri>'.$pageaddr.'</uri></author>';
+ $feed.='<id>'.$pageaddr.'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
$feed.=$entries;
- $feed.='</feed><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
+ $feed.='</feed><!-- Cached version of '.escape(pageUrl()).' -->';
echo $feed;
$cache->cache(ob_get_contents());
// Daily RSS feed: 1 RSS entry per day giving all the links on that day.
// Gives the last 7 days (which have links).
// This RSS feed cannot be filtered.
-function showDailyRSS()
-{
+function showDailyRSS() {
// Cache system
$query = $_SERVER["QUERY_STRING"];
- $cache = new pageCache(pageUrl(),startsWith($query,'do=dailyrss') && !isLoggedIn());
- $cached = $cache->cachedVersion(); if (!empty($cached)) { echo $cached; exit; }
+ $cache = new pageCache(pageUrl(), startsWith($query, 'do=dailyrss') && !isLoggedIn());
+ $cached = $cache->cachedVersion();
+ if (!empty($cached)) {
+ echo $cached;
+ exit;
+ }
+
// If cached was not found (or not usable), then read the database and build the response:
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
+ // Read links from database (and filter private links if used it not logged in).
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
/* Some Shaarlies may have very few links, so we need to look
back in time (rsort()) until we have enough days ($nb_of_days).
*/
- $linkdates=array(); foreach($LINKSDB as $linkdate=>$value) { $linkdates[]=$linkdate; }
+ $linkdates = array();
+ foreach ($LINKSDB as $linkdate => $value) {
+ $linkdates[] = $linkdate;
+ }
rsort($linkdates);
- $nb_of_days=7; // We take 7 days.
- $today=Date('Ymd');
- $days=array();
- foreach($linkdates as $linkdate)
- {
- $day=substr($linkdate,0,8); // Extract day (without time)
- if (strcmp($day,$today)<0)
- {
- if (empty($days[$day])) $days[$day]=array();
- $days[$day][]=$linkdate;
+ $nb_of_days = 7; // We take 7 days.
+ $today = Date('Ymd');
+ $days = array();
+
+ foreach ($linkdates as $linkdate) {
+ $day = substr($linkdate, 0, 8); // Extract day (without time)
+ if (strcmp($day,$today) < 0) {
+ if (empty($days[$day])) {
+ $days[$day] = array();
+ }
+ $days[$day][] = $linkdate;
+ }
+
+ if (count($days) > $nb_of_days) {
+ break; // Have we collected enough days?
}
- if (count($days)>$nb_of_days) break; // Have we collected enough days?
}
// Build the RSS feed.
header('Content-Type: application/rss+xml; charset=utf-8');
- $pageaddr=htmlspecialchars(indexUrl());
+ $pageaddr = escape(indexUrl());
echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
- echo '<channel><title>Daily - '.htmlspecialchars($GLOBALS['title']).'</title><link>'.$pageaddr.'</link>';
- echo '<description>Daily shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n";
-
- foreach($days as $day=>$linkdates) // For each day.
- {
- $daydate = utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))); // Full text date
+ echo '<channel>';
+ echo '<title>Daily - '. $GLOBALS['title'] . '</title>';
+ echo '<link>'. $pageaddr .'</link>';
+ echo '<description>Daily shared links</description>';
+ echo '<language>en-en</language>';
+ echo '<copyright>'. $pageaddr .'</copyright>'. PHP_EOL;
+
+ // For each day.
+ foreach ($days as $day => $linkdates) {
+ $daydate = linkdate2timestamp($day.'_000000'); // Full text date
$rfc822date = linkdate2rfc822($day.'_000000');
- $absurl=htmlspecialchars(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
- echo '<item><title>'.htmlspecialchars($GLOBALS['title'].' - '.$daydate).'</title><guid>'.$absurl.'</guid><link>'.$absurl.'</link>';
- echo '<pubDate>'.htmlspecialchars($rfc822date)."</pubDate>";
+ $absurl = escape(indexUrl().'?do=daily&day='.$day); // Absolute URL of the corresponding "Daily" page.
// Build the HTML body of this RSS entry.
- $html='';
- $href='';
- $links=array();
+ $html = '';
+ $href = '';
+ $links = array();
+
// We pre-format some fields for proper output.
- foreach($linkdates as $linkdate)
- {
+ foreach ($linkdates as $linkdate) {
$l = $LINKSDB[$linkdate];
- $l['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($l['description']))));
+ $l['formatedDescription'] = nl2br(keepMultipleSpaces(text2clickable($l['description'])));
$l['thumbnail'] = thumbnail($l['url']);
- $l['localdate']=linkdate2locale($l['linkdate']);
- if (startsWith($l['url'],'?')) $l['url']=indexUrl().$l['url']; // make permalink URL absolute
- $links[$linkdate]=$l;
+ $l['timestamp'] = linkdate2timestamp($l['linkdate']);
+ if (startsWith($l['url'], '?')) {
+ $l['url'] = indexUrl() . $l['url']; // make permalink URL absolute
+ }
+ $links[$linkdate] = $l;
}
+
// Then build the HTML for this day:
$tpl = new RainTPL;
- $tpl->assign('links',$links);
- $html = $tpl->draw('dailyrss',$return_string=true);
- echo "\n";
- echo '<description><![CDATA['.$html.']]></description>'."\n</item>\n\n";
+ $tpl->assign('title', $GLOBALS['title']);
+ $tpl->assign('daydate', $daydate);
+ $tpl->assign('absurl', $absurl);
+ $tpl->assign('links', $links);
+ $tpl->assign('rfc822date', escape($rfc822date));
+ $html = $tpl->draw('dailyrss', $return_string=true);
+ echo $html . PHP_EOL;
}
- echo '</channel></rss><!-- Cached version of '.htmlspecialchars(pageUrl()).' -->';
+ echo '</channel></rss><!-- Cached version of '. escape(pageUrl()) .' -->';
$cache->cache(ob_get_contents());
ob_end_flush();
// "Daily" page.
function showDaily()
{
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
-
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
$day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
if (isset($_GET['day'])) $day=$_GET['day'];
$days = $LINKSDB->days();
$i = array_search($day,$days);
- if ($i==false) { $i=count($days)-1; $day=$days[$i]; }
+ if ($i===false) { $i=count($days)-1; $day=$days[$i]; }
$previousday='';
$nextday='';
if ($i!==false)
{
- if ($i>1) $previousday=$days[$i-1];
+ if ($i>=1) $previousday=$days[$i-1];
if ($i<count($days)-1) $nextday=$days[$i+1];
}
- $linksToDisplay=$LINKSDB->filterDay($day);
+ try {
+ $linksToDisplay = $LINKSDB->filterDay($day);
+ } catch (Exception $exc) {
+ error_log($exc);
+ $linksToDisplay = array();
+ }
+
// We pre-format some fields for proper output.
foreach($linksToDisplay as $key=>$link)
{
+
$taglist = explode(' ',$link['tags']);
uasort($taglist, 'strcasecmp');
$linksToDisplay[$key]['taglist']=$taglist;
- $linksToDisplay[$key]['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))));
+ $linksToDisplay[$key]['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable($link['description'])));
$linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']);
+ $linksToDisplay[$key]['timestamp'] = linkdate2timestamp($link['linkdate']);
}
/* We need to spread the articles on 3 columns.
$PAGE = new pageBuilder;
$PAGE->assign('linksToDisplay',$linksToDisplay);
$PAGE->assign('linkcount',count($LINKSDB));
- $PAGE->assign('col1',$columns[0]);
- $PAGE->assign('col1',$columns[0]);
- $PAGE->assign('col2',$columns[1]);
- $PAGE->assign('col3',$columns[2]);
- $PAGE->assign('day',utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))));
+ $PAGE->assign('cols', $columns);
+ $PAGE->assign('day',linkdate2timestamp($day.'_000000'));
$PAGE->assign('previousday',$previousday);
$PAGE->assign('nextday',$nextday);
$PAGE->renderPage('daily');
// Render HTML page (according to URL parameters and user rights)
function renderPage()
{
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
// -------- Display login form.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login'))
$token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful.
$PAGE = new pageBuilder;
$PAGE->assign('token',$token);
- $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER']:''));
+ $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
$PAGE->renderPage('loginform');
exit;
}
if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']);
elseif (!empty($_GET['searchtags'])) $links = $LINKSDB->filterTags(trim($_GET['searchtags']));
else $links = $LINKSDB;
+
$body='';
$linksToDisplay=array();
// Get only links which have a thumbnail.
foreach($links as $link)
{
- $permalink='?'.htmlspecialchars(smallhash($link['linkdate']),ENT_QUOTES);
+ $permalink='?'.escape(smallhash($link['linkdate']));
$thumb=lazyThumbnail($link['url'],$permalink);
if ($thumb!='') // Only output links which have a thumbnail.
{
$link['thumbnail']=$thumb; // Thumbnail HTML code.
- $link['permalink']=$permalink;
$linksToDisplay[]=$link; // Add to array.
}
}
+
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('linksToDisplay',$linksToDisplay);
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tagcloud'))
{
$tags= $LINKSDB->allTags();
+
// We sort tags alphabetically, then choose a font size according to count.
// First, find max value.
$maxcount=0; foreach($tags as $key=>$value) $maxcount=max($maxcount,$value);
ksort($tags);
$tagList=array();
foreach($tags as $key=>$value)
+ // Tag font size scaling: default 15 and 30 logarithm bases affect scaling, 22 and 6 are arbitrary font sizes for max and min sizes.
{
- $tagList[$key] = array('count'=>$value,'size'=>max(40*$value/$maxcount,8));
+ $tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6);
}
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER
parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params);
+ // Prevent redirection loop
+ if (isset($params['addtag'])) {
+ unset($params['addtag']);
+ }
+
// Check if this tag is already in the search query and ignore it if it is.
// Each tag is always separated by a space
- $current_tags = explode(' ', $params['searchtags']);
+ if (isset($params['searchtags'])) {
+ $current_tags = explode(' ', $params['searchtags']);
+ } else {
+ $current_tags = array();
+ }
$addtag = true;
foreach ($current_tags as $value) {
if ($value === $_GET['addtag']) {
}
// -------- User clicks on a tag in result count: Remove the tag from the list of searched tags (searchtags=...)
- if (isset($_GET['removetag']))
- {
+ if (isset($_GET['removetag'])) {
// Get previous URL (http_referer) and remove the tag from the searchtags parameters in query.
- if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?'); exit; } // In case browser does not send HTTP_REFERER
- parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params);
- if (isset($params['searchtags']))
- {
+ if (empty($_SERVER['HTTP_REFERER'])) {
+ header('Location: ?');
+ exit;
+ }
+
+ // In case browser does not send HTTP_REFERER
+ parse_str(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_QUERY), $params);
+
+ // Prevent redirection loop
+ if (isset($params['removetag'])) {
+ unset($params['removetag']);
+ }
+
+ if (isset($params['searchtags'])) {
$tags = explode(' ',$params['searchtags']);
$tags=array_diff($tags, array($_GET['removetag'])); // Remove value from array $tags.
- if (count($tags)==0) unset($params['searchtags']); else $params['searchtags'] = implode(' ',$tags);
+ if (count($tags)==0) {
+ unset($params['searchtags']);
+ } else {
+ $params['searchtags'] = implode(' ',$tags);
+ }
unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different)
}
header('Location: ?'.http_build_query($params));
}
// -------- User wants to change the number of links per page (linksperpage=...)
- if (isset($_GET['linksperpage']))
- {
- if (is_numeric($_GET['linksperpage'])) { $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage'])); }
- // Make sure the referrer is Shaarli itself.
- $referer = '?';
- if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
- $referer = $_SERVER['HTTP_REFERER'];
- header('Location: '.$referer);
+ if (isset($_GET['linksperpage'])) {
+ if (is_numeric($_GET['linksperpage'])) {
+ $_SESSION['LINKS_PER_PAGE']=abs(intval($_GET['linksperpage']));
+ }
+
+ header('Location: '. generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('linksperpage')));
exit;
}
// -------- User wants to see only private links (toggle)
- if (isset($_GET['privateonly']))
- {
- if (empty($_SESSION['privateonly']))
- {
- $_SESSION['privateonly']=1; // See only private links
- }
- else
- {
+ if (isset($_GET['privateonly'])) {
+ if (empty($_SESSION['privateonly'])) {
+ $_SESSION['privateonly'] = 1; // See only private links
+ } else {
unset($_SESSION['privateonly']); // See all links
}
- // Make sure the referrer is Shaarli itself.
- $referer = '?';
- if (!empty($_SERVER['HTTP_REFERER']) && strcmp(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_HOST),$_SERVER['HTTP_HOST'])==0)
- $referer = $_SERVER['HTTP_REFERER'];
- header('Location: '.$referer);
+
+ header('Location: '. generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('privateonly')));
exit;
}
// Make sure old password is correct.
$oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']);
- if ($oldhash!=$GLOBALS['hash']) { echo '<script language="JavaScript">alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
+ if ($oldhash!=$GLOBALS['hash']) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
// Save new password
$GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
$GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
- writeConfig();
- echo '<script language="JavaScript">alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after changing password.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
+ exit;
+ }
+ echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
exit;
}
else // show the change password form.
if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
$tz = 'UTC';
if (!empty($_POST['continent']) && !empty($_POST['city']))
- if (isTZvalid($_POST['continent'],$_POST['city']))
+ if (isTimeZoneValid($_POST['continent'],$_POST['city']))
$tz = $_POST['continent'].'/'.$_POST['city'];
$GLOBALS['timezone'] = $tz;
$GLOBALS['title']=$_POST['title'];
$GLOBALS['titleLink']=$_POST['titleLink'];
$GLOBALS['redirector']=$_POST['redirector'];
$GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
- $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
$GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
- writeConfig();
- echo '<script language="JavaScript">alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
+ $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
+ $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']);
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after configuration update.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?do=tools\';</script>';
+ exit;
+ }
+ echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
exit;
}
else // Show the configuration form.
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken());
- $PAGE->assign('title',htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES));
- $PAGE->assign('redirector',htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES));
- list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']);
- $PAGE->assign('timezone_form',$timezone_form); // FIXME: Put entire tz form generation in template?
+ $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] );
+ $PAGE->assign('redirector', empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] );
+ list($timezone_form, $timezone_js) = generateTimeZoneForm($GLOBALS['timezone']);
+ $PAGE->assign('timezone_form', $timezone_form);
$PAGE->assign('timezone_js',$timezone_js);
$PAGE->renderPage('configure');
exit;
$PAGE = new pageBuilder;
$PAGE->assign('linkcount',count($LINKSDB));
$PAGE->assign('token',getToken());
+ $PAGE->assign('tags', $LINKSDB->allTags());
$PAGE->renderPage('changetag');
exit;
}
$LINKSDB[$key]=$value;
}
$LINKSDB->savedb(); // Save to disk.
- echo '<script language="JavaScript">alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
+ echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
exit;
}
$LINKSDB[$key]=$value;
}
$LINKSDB->savedb(); // Save to disk.
- echo '<script language="JavaScript">alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
+ echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
exit;
}
}
{
if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away!
$tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces.
+ $tags = implode(' ', array_unique(explode(' ', $tags))); // Remove duplicates.
$linkdate=$_POST['lf_linkdate'];
$url = trim($_POST['lf_url']);
- if (!startsWith($url,'http:') && !startsWith($url,'https:') && !startsWith($url,'ftp:') && !startsWith($url,'magnet:') && !startsWith($url,'?'))
+ if (!startsWith($url,'http:') && !startsWith($url,'https:') && !startsWith($url,'ftp:') && !startsWith($url,'magnet:') && !startsWith($url,'?') && !startsWith($url,'javascript:'))
$url = 'http://'.$url;
$link = array('title'=>trim($_POST['lf_title']),'url'=>$url,'description'=>trim($_POST['lf_description']),'private'=>(isset($_POST['lf_private']) ? 1 : 0),
'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags));
pubsubhub();
// If we are called from the bookmarklet, we must close the popup:
- if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script language="JavaScript">self.close();</script>'; exit; }
- $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
- $returnurl .= '#'.smallHash($linkdate); // Scroll to the link which has been edited.
- header('Location: '.$returnurl); // After saving the link, redirect to the page the user was on.
+ if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
+ $returnurl = ( !empty($_POST['returnurl']) ? escape($_POST['returnurl']) : '?' );
+ $returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited.
+ $location = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
+ header('Location: '. $location); // After saving the link, redirect to the page the user was on.
exit;
}
if (isset($_POST['cancel_edit']))
{
// If we are called from the bookmarklet, we must close the popup:
- if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script language="JavaScript">self.close();</script>'; exit; }
+ if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
$returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
$returnurl .= '#'.smallHash($_POST['lf_linkdate']); // Scroll to the link which has been edited.
+ $returnurl = generateLocation($returnurl, $_SERVER['HTTP_HOST'], array('addlink', 'post', 'edit_link'));
header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
exit;
}
$LINKSDB->savedb(); // save to disk
// If we are called from the bookmarklet, we must close the popup:
- if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script language="JavaScript">self.close();</script>'; exit; }
- $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
- if ($returnurl=='?') { $returnurl = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '?'); }
- header('Location: '.$returnurl); // After deleting the link, redirect to the page the user was on.
+ if (isset($_GET['source']) && ($_GET['source']=='bookmarklet' || $_GET['source']=='firefoxsocialapi')) { echo '<script>self.close();</script>'; exit; }
+ // Pick where we're going to redirect
+ // =============================================================
+ // Basically, we can't redirect to where we were previously if it was a permalink
+ // or an edit_link, because it would 404.
+ // Cases:
+ // - / : nothing in $_GET, redirect to self
+ // - /?page : redirect to self
+ // - /?searchterm : redirect to self (there might be other links)
+ // - /?searchtags : redirect to self
+ // - /permalink : redirect to / (the link does not exist anymore)
+ // - /?edit_link : redirect to / (the link does not exist anymore)
+ // PHP treats the permalink as a $_GET variable, so we need to check if every condition for self
+ // redirect is not satisfied, and only then redirect to /
+ $location = "?";
+ // Self redirection
+ if (count($_GET) == 0
+ || isset($_GET['page'])
+ || isset($_GET['searchterm'])
+ || isset($_GET['searchtags'])
+ ) {
+ if (isset($_POST['returnurl'])) {
+ $location = $_POST['returnurl']; // Handle redirects given by the form
+ } else {
+ $location = generateLocation($_SERVER['HTTP_REFERER'], $_SERVER['HTTP_HOST'], array('delete_link'));
+ }
+ }
+
+ header('Location: ' . $location); // After deleting the link, redirect to appropriate location
exit;
}
$PAGE->assign('link',$link);
$PAGE->assign('link_is_new',false);
$PAGE->assign('token',getToken()); // XSRF protection.
- $PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''));
+ $PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''));
+ $PAGE->assign('tags', $LINKSDB->allTags());
$PAGE->renderPage('editlink');
exit;
}
{
$url=$_GET['post'];
- // We remove the annoying parameters added by FeedBurner and GoogleFeedProxy (?utm_source=...)
- $i=strpos($url,'&utm_source='); if ($i!==false) $url=substr($url,0,$i);
- $i=strpos($url,'?utm_source='); if ($i!==false) $url=substr($url,0,$i);
- $i=strpos($url,'#xtor=RSS-'); if ($i!==false) $url=substr($url,0,$i);
+
+ // We remove the annoying parameters added by FeedBurner, GoogleFeedProxy, Facebook...
+ $annoyingpatterns = array('/[\?&]utm_source=[^&]*/',
+ '/[\?&]utm_campaign=[^&]*/',
+ '/[\?&]utm_medium=[^&]*/',
+ '/#xtor=RSS-[^&]*/',
+ '/[\?&]fb_[^&]*/',
+ '/[\?&]__scoop[^&]*/',
+ '/#tk\.rss_all\?/',
+ '/[\?&]action_ref_map=[^&]*/',
+ '/[\?&]action_type_map=[^&]*/',
+ '/[\?&]action_object_map=[^&]*/',
+ '/[\?&]utm_content=[^&]*/',
+ '/[\?&]fb=[^&]*/',
+ '/[\?&]xtor=[^&]*/'
+ );
+ foreach($annoyingpatterns as $pattern)
+ {
+ $url = preg_replace($pattern, "", $url);
+ }
$link_is_new = false;
$link = $LINKSDB->getLinkFromUrl($url); // Check if URL is not already in database (in this case, we will edit the existing link)
$PAGE->assign('link_is_new',$link_is_new);
$PAGE->assign('token',getToken()); // XSRF protection.
$PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''));
+ $PAGE->assign('source',(isset($_GET['source']) ? $_GET['source'] : ''));
+ $PAGE->assign('tags', $LINKSDB->allTags());
$PAGE->renderPage('editlink');
exit;
}
($exportWhat=='private' && $link['private']!=0) ||
($exportWhat=='public' && $link['private']==0))
{
- echo '<DT><A HREF="'.htmlspecialchars($link['url']).'" ADD_DATE="'.linkdate2timestamp($link['linkdate']).'" PRIVATE="'.$link['private'].'"';
- if ($link['tags']!='') echo ' TAGS="'.htmlspecialchars(str_replace(' ',',',$link['tags'])).'"';
- echo '>'.htmlspecialchars($link['title'])."</A>\n";
- if ($link['description']!='') echo '<DD>'.htmlspecialchars($link['description'])."\n";
+ echo '<DT><A HREF="'.$link['url'].'" ADD_DATE="'.linkdate2timestamp($link['linkdate']).'" PRIVATE="'.$link['private'].'"';
+ if ($link['tags']!='') echo ' TAGS="'.str_replace(' ',',',$link['tags']).'"';
+ echo '>'.$link['title']."</A>\n";
+ if ($link['description']!='') echo '<DD>'.$link['description']."\n";
}
}
exit;
if (!isset($_POST['token']) || (!isset($_FILES)) || (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size']==0))
{
$returnurl = ( empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER'] );
- echo '<script language="JavaScript">alert("The file you are trying to upload is probably bigger than what this webserver can accept ('.getMaxFileSize().' bytes). Please upload in smaller chunks.");document.location=\''.htmlspecialchars($returnurl).'\';</script>';
+ echo '<script>alert("The file you are trying to upload is probably bigger than what this webserver can accept ('.getMaxFileSize().' bytes). Please upload in smaller chunks.");document.location=\''.escape($returnurl).'\';</script>';
exit;
}
if (!tokenOk($_POST['token'])) die('Wrong token.');
// -------- Otherwise, simply display search form and links:
$PAGE = new pageBuilder;
- $PAGE->assign('linkcount',count($LINKSDB));
buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
$PAGE->renderPage('linklist');
exit;
function importFile()
{
if (!(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'])) { die('Not allowed.'); }
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
+ $LINKSDB = new LinkDB(
+ $GLOBALS['config']['DATASTORE'],
+ isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI'],
+ $GLOBALS['config']['HIDE_PUBLIC_LINKS']
+ );
$filename=$_FILES['filetoupload']['name'];
$filesize=$_FILES['filetoupload']['size'];
$data=file_get_contents($_FILES['filetoupload']['tmp_name']);
}
$LINKSDB->savedb();
- echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
+ echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
}
else
{
- echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
+ echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
}
}
if (isset($_GET['searchterm'])) // Fulltext search
{
$linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm']));
- $search_crits=htmlspecialchars(trim($_GET['searchterm']));
+ $search_crits=escape(trim($_GET['searchterm']));
$search_type='fulltext';
}
elseif (isset($_GET['searchtags'])) // Search by tag
{
$linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags']));
- $search_crits=explode(' ',trim($_GET['searchtags']));
+ $search_crits=explode(' ',escape(trim($_GET['searchtags'])));
$search_type='tags';
}
elseif (isset($_SERVER['QUERY_STRING']) && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/',$_SERVER['QUERY_STRING'])) // Detect smallHashes in URL
{
header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
echo '<h1>404 Not found.</h1>Oh crap. The link you are trying to reach does not exist or has been deleted.';
- echo '<br>You would mind <a href="?">clicking here</a>?';
+ echo '<br>Would you mind <a href="?">clicking here</a>?';
exit;
}
$search_type='permalink';
else
$linksToDisplay = $LINKSDB; // Otherwise, display without filtering.
+
// Option: Show only private links
if (!empty($_SESSION['privateonly']))
{
while ($i<$end && $i<count($keys))
{
$link = $linksToDisplay[$keys[$i]];
- $link['description']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))));
+ $link['description']=nl2br(keepMultipleSpaces(text2clickable($link['description'])));
$title=$link['title'];
$classLi = $i%2!=0 ? '' : 'publicLinkHightLight';
$link['class'] = ($link['private']==0 ? $classLi : 'private');
- $link['localdate']=linkdate2locale($link['linkdate']);
+ $link['timestamp']=linkdate2timestamp($link['linkdate']);
$taglist = explode(' ',$link['tags']);
uasort($taglist, 'strcasecmp');
$link['taglist']=$taglist;
+
+ if ($link["url"][0] === '?' && // Check for both signs of a note: starting with ? and 7 chars long. I doubt that you'll post any links that look like this.
+ strlen($link["url"]) === 7) {
+ $link["url"] = indexUrl() . $link["url"];
+ }
+
$linkDisp[$keys[$i]] = $link;
$i++;
}
$PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // Optional redirector URL.
$PAGE->assign('token',$token);
$PAGE->assign('links',$linkDisp);
+ $PAGE->assign('tags', $LINKSDB->allTags());
return;
}
if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
}
$sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
- return array('src'=>indexUrl().'?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url),
+ return array('src'=>indexUrl().'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
}
if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
{
$sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
- return array('src'=>indexUrl().'?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url),
+ return array('src'=>indexUrl().'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
}
return array(); // No thumbnail.
$t = computeThumbnail($url,$href);
if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
- $html='<a href="'.htmlspecialchars($t['href']).'"><img src="'.htmlspecialchars($t['src']).'"';
- if (!empty($t['width'])) $html.=' width="'.htmlspecialchars($t['width']).'"';
- if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"';
- if (!empty($t['style'])) $html.=' style="'.htmlspecialchars($t['style']).'"';
- if (!empty($t['alt'])) $html.=' alt="'.htmlspecialchars($t['alt']).'"';
+ $html='<a href="'.escape($t['href']).'"><img src="'.escape($t['src']).'"';
+ if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
+ if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
+ if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
+ if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='></a>';
return $html;
}
-
// Returns the HTML code to display a thumbnail for a link
// for the picture wall (using lazy image loading)
// Understands various services (youtube.com...)
$t = computeThumbnail($url,$href);
if (count($t)==0) return ''; // Empty array = no thumbnail for this URL.
- $html='<a href="'.htmlspecialchars($t['href']).'">';
+ $html='<a href="'.escape($t['href']).'">';
- // Lazy image (only loaded by JavaScript when in the viewport).
- if (!empty($GLOBALS['disablejquery'])) // (except if jQuery is disabled)
- $html.='<img class="lazyimage" src="'.htmlspecialchars($t['src']).'"';
- else
- $html.='<img class="lazyimage" src="#" data-original="'.htmlspecialchars($t['src']).'"';
+ // Lazy image
+ $html.='<img class="b-lazy" src="#" data-src="'.escape($t['src']).'"';
- if (!empty($t['width'])) $html.=' width="'.htmlspecialchars($t['width']).'"';
- if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"';
- if (!empty($t['style'])) $html.=' style="'.htmlspecialchars($t['style']).'"';
- if (!empty($t['alt'])) $html.=' alt="'.htmlspecialchars($t['alt']).'"';
+ if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
+ if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
+ if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
+ if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='>';
// No-JavaScript fallback.
- $html.='<noscript><img src="'.htmlspecialchars($t['src']).'"';
- if (!empty($t['width'])) $html.=' width="'.htmlspecialchars($t['width']).'"';
- if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"';
- if (!empty($t['style'])) $html.=' style="'.htmlspecialchars($t['style']).'"';
- if (!empty($t['alt'])) $html.=' alt="'.htmlspecialchars($t['alt']).'"';
+ $html.='<noscript><img src="'.escape($t['src']).'"';
+ if (!empty($t['width'])) $html.=' width="'.escape($t['width']).'"';
+ if (!empty($t['height'])) $html.=' height="'.escape($t['height']).'"';
+ if (!empty($t['style'])) $html.=' style="'.escape($t['style']).'"';
+ if (!empty($t['alt'])) $html.=' alt="'.escape($t['alt']).'"';
$html.='></noscript></a>';
return $html;
if (!empty($_POST['setlogin']) && !empty($_POST['setpassword']))
{
$tz = 'UTC';
- if (!empty($_POST['continent']) && !empty($_POST['city']))
- if (isTZvalid($_POST['continent'],$_POST['city']))
+ if (!empty($_POST['continent']) && !empty($_POST['city'])) {
+ if (isTimeZoneValid($_POST['continent'], $_POST['city'])) {
$tz = $_POST['continent'].'/'.$_POST['city'];
+ }
+ }
$GLOBALS['timezone'] = $tz;
// Everything is ok, let's create config file.
$GLOBALS['login'] = $_POST['setlogin'];
$GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
$GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
- $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.htmlspecialchars(indexUrl()) : $_POST['title'] );
- writeConfig();
- echo '<script language="JavaScript">alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
+ $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
+ $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
+ try {
+ writeConfig($GLOBALS, isLoggedIn());
+ }
+ catch(Exception $e) {
+ error_log(
+ 'ERROR while writing config file after installation.' . PHP_EOL .
+ $e->getMessage()
+ );
+
+ // TODO: do not handle exceptions/errors in JS.
+ echo '<script>alert("'. $e->getMessage() .'");document.location=\'?\';</script>';
+ exit;
+ }
+ echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
exit;
}
// Display config form:
- list($timezone_form,$timezone_js) = templateTZform();
- $timezone_html=''; if ($timezone_form!='') $timezone_html='<tr><td valign="top"><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
+ list($timezone_form, $timezone_js) = generateTimeZoneForm();
+ $timezone_html = '';
+ if ($timezone_form != '') {
+ $timezone_html = '<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
+ }
$PAGE = new pageBuilder;
$PAGE->assign('timezone_html',$timezone_html);
exit;
}
-// Generates the timezone selection form and JavaScript.
-// Input: (optional) current timezone (can be 'UTC/UTC'). It will be pre-selected.
-// Output: array(html,js)
-// Example: list($htmlform,$js) = templateTZform('Europe/Paris'); // Europe/Paris pre-selected.
-// Returns array('','') if server does not support timezones list. (e.g. PHP 5.1 on free.fr)
-function templateTZform($ptz=false)
-{
- if (function_exists('timezone_identifiers_list')) // because of old PHP version (5.1) which can be found on free.fr
- {
- // Try to split the provided timezone.
- if ($ptz==false) { $l=timezone_identifiers_list(); $ptz=$l[0]; }
- $spos=strpos($ptz,'/'); $pcontinent=substr($ptz,0,$spos); $pcity=substr($ptz,$spos+1);
-
- // Display config form:
- $timezone_form = '';
- $timezone_js = '';
- // The list is in the form "Europe/Paris", "America/Argentina/Buenos_Aires"...
- // We split the list in continents/cities.
- $continents = array();
- $cities = array();
- foreach(timezone_identifiers_list() as $tz)
- {
- if ($tz=='UTC') $tz='UTC/UTC';
- $spos = strpos($tz,'/');
- if ($spos!==false)
- {
- $continent=substr($tz,0,$spos); $city=substr($tz,$spos+1);
- $continents[$continent]=1;
- if (!isset($cities[$continent])) $cities[$continent]='';
- $cities[$continent].='<option value="'.$city.'"'.($pcity==$city?' selected':'').'>'.$city.'</option>';
- }
- }
- $continents_html = '';
- $continents = array_keys($continents);
- foreach($continents as $continent)
- $continents_html.='<option value="'.$continent.'"'.($pcontinent==$continent?' selected':'').'>'.$continent.'</option>';
- $cities_html = $cities[$pcontinent];
- $timezone_form = "Continent: <select name=\"continent\" id=\"continent\" onChange=\"onChangecontinent();\">${continents_html}</select>";
- $timezone_form .= " City: <select name=\"city\" id=\"city\">${cities[$pcontinent]}</select><br />";
- $timezone_js = "<script language=\"JavaScript\">";
- $timezone_js .= "function onChangecontinent(){document.getElementById(\"city\").innerHTML = citiescontinent[document.getElementById(\"continent\").value];}";
- $timezone_js .= "var citiescontinent = ".json_encode($cities).";" ;
- $timezone_js .= "</script>" ;
- return array($timezone_form,$timezone_js);
- }
- return array('','');
-}
-
-// Tells if a timezone is valid or not.
-// If not valid, returns false.
-// If system does not support timezone list, returns false.
-function isTZvalid($continent,$city)
-{
- $tz = $continent.'/'.$city;
- if (function_exists('timezone_identifiers_list')) // because of old PHP version (5.1) which can be found on free.fr
- {
- if (in_array($tz, timezone_identifiers_list())) // it's a valid timezone?
- return true;
- }
- return false;
-}
if (!function_exists('json_encode')) {
function json_encode($data) {
switch ($type = gettype($data)) {
}
}
-// Webservices (for use with jQuery/jQueryUI)
-// e.g. index.php?ws=tags&term=minecr
-function processWS()
-{
- if (empty($_GET['ws']) || empty($_GET['term'])) return;
- $term = $_GET['term'];
- $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in).
- header('Content-Type: application/json; charset=utf-8');
-
- // Search in tags (case insensitive, cumulative search)
- if ($_GET['ws']=='tags')
- {
- $tags=explode(' ',str_replace(',',' ',$term)); $last = array_pop($tags); // Get the last term ("a b c d" ==> "a b c", "d")
- $addtags=''; if ($tags) $addtags=implode(' ',$tags).' '; // We will pre-pend previous tags
- $suggested=array();
- /* To speed up things, we store list of tags in session */
- if (empty($_SESSION['tags'])) $_SESSION['tags'] = $LINKSDB->allTags();
- foreach($_SESSION['tags'] as $key=>$value)
- {
- if (startsWith($key,$last,$case=false) && !in_array($key,$tags)) $suggested[$addtags.$key.' ']=0;
- }
- echo json_encode(array_keys($suggested));
- exit;
- }
-
- // Search a single tag (case sensitive, single tag search)
- if ($_GET['ws']=='singletag')
- {
- /* To speed up things, we store list of tags in session */
- if (empty($_SESSION['tags'])) $_SESSION['tags'] = $LINKSDB->allTags();
- foreach($_SESSION['tags'] as $key=>$value)
- {
- if (startsWith($key,$term,$case=true)) $suggested[$key]=0;
- }
- echo json_encode(array_keys($suggested));
- exit;
- }
-}
-// Re-write configuration file according to globals.
-// Requires some $GLOBALS to be set (login,hash,salt,title).
-// If the config file cannot be saved, an error message is displayed and the user is redirected to "Tools" menu.
-// (otherwise, the function simply returns.)
-function writeConfig()
-{
- if (is_file($GLOBALS['config']['CONFIG_FILE']) && !isLoggedIn()) die('You are not authorized to alter config.'); // Only logged in user can alter config.
- $config='<?php $GLOBALS[\'login\']='.var_export($GLOBALS['login'],true).'; $GLOBALS[\'hash\']='.var_export($GLOBALS['hash'],true).'; $GLOBALS[\'salt\']='.var_export($GLOBALS['salt'],true).'; ';
- $config .='$GLOBALS[\'timezone\']='.var_export($GLOBALS['timezone'],true).'; date_default_timezone_set('.var_export($GLOBALS['timezone'],true).'); $GLOBALS[\'title\']='.var_export($GLOBALS['title'],true).';';
- $config .= '$GLOBALS[\'titleLink\']='.var_export($GLOBALS['titleLink'],true).'; ';
- $config .= '$GLOBALS[\'redirector\']='.var_export($GLOBALS['redirector'],true).'; ';
- $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
- $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
- $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
- $config .= ' ?>';
- if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
- {
- 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>';
- exit;
- }
-}
/* Because some f*cking services like flickr require an extra HTTP request to get the thumbnail URL,
I have deported the thumbnail URL code generation here, otherwise this would slow down page generation.
// This is more complex: we have to perform a HTTP request, then parse the result.
// Maybe we should deport this to JavaScript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098
$vid = substr(parse_url($url,PHP_URL_PATH),1);
- list($httpstatus,$headers,$data) = getHTTP('https://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5);
+ list($httpstatus,$headers,$data) = getHTTP('https://vimeo.com/api/v2/video/'.escape($vid).'.php',5);
if (strpos($httpstatus,'200 OK')!==false)
{
$t = unserialize($data);
pageCache::purgeCache(); // Purge page cache shared by sessions.
}
+try {
+ mergeDeprecatedConfig($GLOBALS, isLoggedIn());
+} catch(Exception $e) {
+ error_log(
+ 'ERROR while merging deprecated options.php file.' . PHP_EOL .
+ $e->getMessage()
+ );
+}
+
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database.
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; }
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; }
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=dailyrss')) { showDailyRSS(); exit; }
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=daily')) { showDaily(); exit; }
-if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI)
if (!isset($_SESSION['LINKS_PER_PAGE'])) $_SESSION['LINKS_PER_PAGE']=$GLOBALS['config']['LINKS_PER_PAGE'];
renderPage();
?>