/index.php
PHP | 2423 lines | 1783 code | 221 blank | 419 comment | 428 complexity | c2b639aa2b3dc7db5a9c7f92d3a9ed63 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- <?php
- // Shaarli 0.0.41 beta - 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)
- // -----------------------------------------------------------------------------------------------
- // NEVER TRUST IN PHP.INI
- // Some hosts do not define a default timezone in php.ini,
- // so we have to do this for avoid the strict standard error.
- date_default_timezone_set('UTC');
- // -----------------------------------------------------------------------------------------------
- // Hardcoded parameter (These parameters can be overwritten by creating the file /config/options.php)
- $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']['LINKS_PER_PAGE'] = 20; // Default links per page.
- $GLOBALS['config']['IPBANS_FILENAME'] = $GLOBALS['config']['DATADIR'].'/ipbans.php'; // File storage for failures and bans.
- $GLOBALS['config']['BAN_AFTER'] = 4; // Ban IP after this many failures.
- $GLOBALS['config']['BAN_DURATION'] = 1800; // Ban duration for IP address after login failures (in seconds) (1800 sec. = 30 minutes)
- $GLOBALS['config']['OPEN_SHAARLI'] = false; // If true, anyone can add/edit/delete links without having to login
- $GLOBALS['config']['HIDE_TIMESTAMPS'] = false; // If true, the moment when links were saved are not shown to users that are not logged in.
- $GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links.
- $GLOBALS['config']['CACHEDIR'] = 'cache'; // Cache directory for thumbnails for SLOW services (like flickr)
- $GLOBALS['config']['PAGECACHE'] = 'pagecache'; // Page cache directory.
- $GLOBALS['config']['ENABLE_LOCALCACHE'] = true; // Enable Shaarli to store thumbnail in a local cache. Disable to reduce webspace usage.
- $GLOBALS['config']['PUBSUBHUB_URL'] = ''; // PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable.
- $GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli.
- $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']['ACTUAL_SERVER_PORT'] = $_SERVER["SERVER_PORT"] ; //Override port used in generated URLs.
- // -----------------------------------------------------------------------------------------------
- // You should not touch below (or at your own risks !)
- // Optionnal config file.
- if (is_file($GLOBALS['config']['DATADIR'].'/options.php')) require($GLOBALS['config']['DATADIR'].'/options.php');
- define('shaarli_version','0.0.41 beta');
- define('PHPPREFIX','<?php /* '); // Prefix to encapsulate data in php code.
- define('PHPSUFFIX',' */ ?>'); // Suffix to encapsulate data in php code.
- // 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['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('session.use_cookies', 1); // Use cookies to store session.
- ini_set('session.use_only_cookies', 1); // Force cookies for session (phpsessionID forbidden in URL)
- ini_set('session.use_trans_sid', false); // Prevent php to use sessionID in URL if cookies are disabled.
- session_name('shaarli');
- if (session_id() == '') session_start(); // Start session if needed (Some server auto-start sessions).
- // PHP Settings
- ini_set('max_input_time','60'); // High execution time in case of problematic imports/exports.
- 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)
- include "inc/rain.tpl.class.php"; //include Rain TPL
- raintpl::$tpl_dir = "tpl/"; // template directory
- if (!is_dir('tmp')) { mkdir('tmp',0705); chmod('tmp',0705); }
- raintpl::$cache_dir = "tmp/"; // cache directory
- ob_start(); // Output buffering for the page cache.
- // In case stupid admin has left magic_quotes enabled in php.ini:
- if (get_magic_quotes_gpc())
- {
- function stripslashes_deep($value) { $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value); return $value; }
- $_POST = array_map('stripslashes_deep', $_POST);
- $_GET = array_map('stripslashes_deep', $_GET);
- $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
- }
- // Prevent caching on client side or proxy: (yes, it's ugly)
- header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
- header("Cache-Control: no-store, no-cache, must-revalidate");
- header("Cache-Control: post-check=0, pre-check=0", false);
- header("Pragma: no-cache");
- // Directories creations (Note that your web host may require differents 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_dir($GLOBALS['config']['DATADIR'])) { mkdir($GLOBALS['config']['DATADIR'],0705); chmod($GLOBALS['config']['DATADIR'],0705); }
- if (!is_dir('tmp')) { mkdir('tmp',0705); chmod('tmp',0705); } // For RainTPL temporary files.
- if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['DATADIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
- // Second check to see if Shaarli can write in its directory, because on some hosts is_writable() is not reliable.
- if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) die('<pre>ERROR: Shaarli does not have the right to write in its own directory ('.realpath(dirname(__FILE__)).').</pre>');
- if ($GLOBALS['config']['ENABLE_LOCALCACHE'])
- {
- if (!is_dir($GLOBALS['config']['CACHEDIR'])) { mkdir($GLOBALS['config']['CACHEDIR'],0705); chmod($GLOBALS['config']['CACHEDIR'],0705); }
- if (!is_file($GLOBALS['config']['CACHEDIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['CACHEDIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
- }
- // 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['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;
- // 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();
- require $GLOBALS['config']['CONFIG_FILE']; // Read login/password hash into $GLOBALS.
- 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;
- }
- }
- // Checks if an update is available for Shaarli.
- // (at most once a day, and only for registered user.)
- // Output: '' = no new version.
- // other= the available version.
- function checkUpdate()
- {
- if (!isLoggedIn()) return ''; // Do not check versions for visitors.
- // 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('http://sebsauvage.net/files/shaarli_version.txt',2);
- if (strpos($httpstatus,'200 OK')!==false) $version=$data;
- // If failed, nevermind. We don't want to bother the user with that.
- file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
- }
- // Compare versions:
- $newestversion=file_get_contents($GLOBALS['config']['UPDATECHECK_FILENAME']);
- if (version_compare($newestversion,shaarli_version)==1) return $newestversion;
- return '';
- }
- // -----------------------------------------------------------------------------------------------
- // Simple cache system (mainly for the RSS/ATOM feeds).
- class pageCache
- {
- private $url; // Full URL of the page to cache (typically the value returned by pageUrl())
- private $shouldBeCached; // boolean: Should this url be cached ?
- private $filename; // Name of the cache file for this url
- /*
- $url = url (typically the value returned by pageUrl())
- $shouldBeCached = boolean. If false, the cache will be disabled.
- */
- public function __construct($url,$shouldBeCached)
- {
- $this->url = $url;
- $this->filename = $GLOBALS['config']['PAGECACHE'].'/'.sha1($url).'.cache';
- $this->shouldBeCached = $shouldBeCached;
- }
- // If the page should be cached and a cached version exists,
- // returns the cached version (otherwise, return null).
- public function cachedVersion()
- {
- if (!$this->shouldBeCached) return null;
- if (is_file($this->filename)) { return file_get_contents($this->filename); exit; }
- return null;
- }
- // Put a page in the cache.
- public function cache($page)
- {
- if (!$this->shouldBeCached) return;
- if (!is_dir($GLOBALS['config']['PAGECACHE'])) { mkdir($GLOBALS['config']['PAGECACHE'],0705); chmod($GLOBALS['config']['PAGECACHE'],0705); }
- file_put_contents($this->filename,$page);
- }
- // Purge the whole cache.
- // (call with pageCache::purgeCache())
- public static function purgeCache()
- {
- if (is_dir($GLOBALS['config']['PAGECACHE']))
- {
- $handler = opendir($GLOBALS['config']['PAGECACHE']);
- if ($handler!==false)
- {
- while (($filename = readdir($handler))!==false)
- {
- if (endsWith($filename,'.cache')) { unlink($GLOBALS['config']['PAGECACHE'].'/'.$filename); }
- }
- closedir($handler);
- }
- }
- }
- }
- // -----------------------------------------------------------------------------------------------
- // Log to text file
- function logm($message)
- {
- $t = strval(date('Y/m/d_H:i:s')).' - '.$_SERVER["REMOTE_ADDR"].' - '.strval($message)."\n";
- 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
- eg. 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)),'=');
- $t = str_replace('+','-',$t); // Get rid of characters which need encoding in URLs.
- $t = str_replace('/','_',$t);
- $t = str_replace('=','@',$t);
- return $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)
- {
- $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'];
- return preg_replace('!(((?:https?|ftp|file)://|apt:|magnet:)\S+[[:alnum:]]/?)!si','<a href="'.$redir.'$1" rel="nofollow">$1</a>',$url);
- }
- // This function inserts where relevant so that multiple spaces are properly displayed in HTML
- // even in the absence of <pre> (This is used in description to keep text formatting)
- function keepMultipleSpaces($text)
- {
- return str_replace(' ',' ',$text);
- }
- // ------------------------------------------------------------------------------------------
- // Sniff browser language to display dates in the right format automatically.
- // (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
- if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) // eg. "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. Prefered language is always presented first.)
- if (preg_match('/([a-z]{2}(-[a-z]{2})?)/i',$_SERVER['HTTP_ACCEPT_LANGUAGE'],$matches)) $loc=$matches[1];
- }
- setlocale(LC_TIME,$loc); // LC_TIME = Set local for date/time format only.
- }
- // ------------------------------------------------------------------------------------------
- // PubSubHubbub protocol support (if enabled) [UNTESTED]
- // (Source: http://aldarone.fr/les-flux-rss-shaarli-et-pubsubhubbub/ )
- if (!empty($GLOBALS['config']['PUBSUBHUB_URL'])) include './publisher.php';
- function pubsubhub()
- {
- if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
- {
- $p = new Publisher($GLOBALS['config']['PUBSUBHUB_URL']);
- $topic_url = array (
- indexUrl().'?do=atom',
- indexUrl().'?do=rss'
- );
- $p->publish_update($topic_url);
- }
- }
- // ------------------------------------------------------------------------------------------
- // Session management
- // Returns the IP address of the client (Used to prevent session cookie hijacking.)
- function allIPs()
- {
- $ip = $_SERVER["REMOTE_ADDR"];
- // Then we use more HTTP headers to prevent session hijacking from users behind the same proxy.
- if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $ip=$ip.'_'.$_SERVER['HTTP_X_FORWARDED_FOR']; }
- if (isset($_SERVER['HTTP_CLIENT_IP'])) { $ip=$ip.'_'.$_SERVER['HTTP_CLIENT_IP']; }
- return $ip;
- }
- // Check that user/password is correct.
- function check_auth($login,$password)
- {
- $hash = sha1($password.$login.$GLOBALS['salt']);
- if ($login==$GLOBALS['login'] && $hash==$GLOBALS['hash'])
- { // Login/password is correct.
- $_SESSION['uid'] = sha1(uniqid('',true).'_'.mt_rand()); // generate unique random number (different than phpsessionid)
- $_SESSION['ip']=allIPs(); // We store IP address(es) of the client to make sure session is not hijacked.
- $_SESSION['username']=$login;
- $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Set session expiration.
- logm('Login successful');
- return True;
- }
- logm('Login failed for user '.$login);
- return False;
- }
- // 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 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;
- }
- // Force logout.
- function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); } }
- // ------------------------------------------------------------------------------------------
- // Brute force protection system
- // Several consecutive failed logins will ban the IP address for 30 minutes.
- 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?>");
- include $GLOBALS['config']['IPBANS_FILENAME'];
- // Signal a failed login. Will ban the IP if too many failures:
- function ban_loginFailed()
- {
- $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
- if (!isset($gb['FAILURES'][$ip])) $gb['FAILURES'][$ip]=0;
- $gb['FAILURES'][$ip]++;
- if ($gb['FAILURES'][$ip]>($GLOBALS['config']['BAN_AFTER']-1))
- {
- $gb['BANS'][$ip]=time()+$GLOBALS['config']['BAN_DURATION'];
- logm('IP address banned from login');
- }
- $GLOBALS['IPBANS'] = $gb;
- file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
- }
- // Signals a successful login. Resets failed login counter.
- function ban_loginOk()
- {
- $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
- unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
- $GLOBALS['IPBANS'] = $gb;
- file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
- }
- // Checks if the user CAN login. If 'true', the user can try to login.
- function ban_canLogin()
- {
- $ip=$_SERVER["REMOTE_ADDR"]; $gb=$GLOBALS['IPBANS'];
- if (isset($gb['BANS'][$ip]))
- {
- // User is banned. Check if the ban has expired:
- if ($gb['BANS'][$ip]<=time())
- { // Ban expired, user can try to login again.
- logm('Ban lifted.');
- unset($gb['FAILURES'][$ip]); unset($gb['BANS'][$ip]);
- file_put_contents($GLOBALS['config']['IPBANS_FILENAME'], "<?php\n\$GLOBALS['IPBANS']=".var_export($gb,true).";\n?>");
- return true; // Ban has expired, user can login.
- }
- return false; // User is banned.
- }
- return true; // User is not banned.
- }
- // ------------------------------------------------------------------------------------------
- // Process login form: Check if login/password is correct.
- if (isset($_POST['login']))
- {
- if (!ban_canLogin()) die('I said: NO. You are banned for the moment. Go away.');
- if (isset($_POST['password']) && tokenOk($_POST['token']) && (check_auth($_POST['login'], $_POST['password'])))
- { // Login/password is ok.
- ban_loginOk();
- // If user wants to keep the session cookie even after the browser closes:
- if (!empty($_POST['longlastingsession']))
- {
- $_SESSION['longlastingsession']=31536000; // (31536000 seconds = 1 year)
- $_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['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['SERVER_NAME']); // 0 means "When browser closes"
- session_regenerate_id(true);
- }
- // Optional redirect after login:
- if (isset($_GET['post'])) { header('Location: ?post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); exit; }
- if (isset($_POST['returnurl']))
- {
- if (endsWith($_POST['returnurl'],'?do=login')) { header('Location: ?'); exit; } // Prevent loops over login screen.
- header('Location: '.$_POST['returnurl']); exit;
- }
- header('Location: ?'); exit;
- }
- else
- {
- ban_loginFailed();
- $redir = '';
- if (isset($_GET['post'])) { $redir = '&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!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.
- exit;
- }
- }
- // ------------------------------------------------------------------------------------------
- // Misc utility functions:
- // Returns the server URL (including port and http/https), without path.
- // eg. "http://myserver.com:8080"
- // You can append $_SERVER['SCRIPT_NAME'] to get the current script URL.
- function serverUrl()
- {
- $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $GLOBALS['config']['ACTUAL_SERVER_PORT'] =='443'; // HTTPS detection.
- $serverport = ($GLOBALS['config']['ACTUAL_SERVER_PORT'] =='80' || ($https && $GLOBALS['config']['ACTUAL_SERVER_PORT'] =='443') ? '' : ':'.$GLOBALS['config']['ACTUAL_SERVER_PORT'] );
- return 'http'.($https?'s':'').'://'.$_SERVER["SERVER_NAME"].$serverport;
- }
- // Returns the absolute URL of current script, without the query.
- // (eg. http://sebsauvage.net/links/)
- function indexUrl()
- {
- $scriptname = $_SERVER["SCRIPT_NAME"];
- // If the script is named 'index.php', we remove it (for better looking URLs,
- // eg. http://mysite.com/shaarli/?abcde instead of http://mysite.com/shaarli/index.php?abcde)
- if (endswith($scriptname,'index.php')) $scriptname = substr($scriptname,0,strlen($scriptname)-9);
- return serverUrl() . $scriptname;
- }
- // Returns the absolute URL of current script, WITH the query.
- // (eg. http://sebsauvage.net/links/?toto=titi&spamspamspam=humbug)
- function pageUrl()
- {
- return indexUrl().(!empty($_SERVER["QUERY_STRING"]) ? '?'.$_SERVER["QUERY_STRING"] : '');
- }
- // Convert post_max_size/upload_max_filesize (eg.'16M') parameters to bytes.
- function return_bytes($val)
- {
- $val = trim($val); $last=strtolower($val[strlen($val)-1]);
- switch($last)
- {
- case 'g': $val *= 1024;
- case 'm': $val *= 1024;
- case 'k': $val *= 1024;
- }
- return $val;
- }
- // Try to determine max file size for uploads (POST).
- // Returns an integer (in bytes)
- function getMaxFileSize()
- {
- $size1 = return_bytes(ini_get('post_max_size'));
- $size2 = return_bytes(ini_get('upload_max_filesize'));
- // Return the smaller of two:
- $maxsize = min($size1,$size2);
- // FIXME: Then convert back to readable notations ? (eg. 2M instead of 2000000)
- 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);
- }
- /* Converts a linkdate time (YYYYMMDD_HHMMSS) of an article to a RFC822 date.
- (used to build the pubDate attribute in RSS feed.) */
- function linkdate2rfc822($linkdate)
- {
- return date('r',linkdate2timestamp($linkdate)); // 'r' is for RFC822 date format.
- }
- /* Converts a linkdate time (YYYYMMDD_HHMMSS) of an article to a ISO 8601 date.
- (used to build the updated tags in ATOM feed.) */
- function linkdate2iso8601($linkdate)
- {
- 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 local 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 )
- {
- $res=array();
- foreach($headers as $header)
- {
- $i = strpos($header,': ');
- if ($i!==false)
- {
- $key=substr($header,0,$i);
- $value=substr($header,$i+2,strlen($header)-$i-2);
- $res[$key]=$value;
- }
- }
- return $res;
- }
- /* GET an URL.
- Input: $url : url to get (http://...)
- $timeout : Network timeout (will wait this many seconds for an anwser before giving up).
- Output: An array. [0] = HTTP status message (eg. "HTTP/1.1 200 OK") or error message
- [1] = associative array containing HTTP response headers (eg. echo getHTTP($url)[1]['Content-Type'])
- [2] = data
- Example: list($httpstatus,$headers,$data) = getHTTP('http://sebauvage.net/');
- if (strpos($httpstatus,'200 OK')!==false)
- echo 'Data type: '.htmlspecialchars($headers['Content-Type']);
- else
- echo 'There was an error: '.htmlspecialchars($httpstatus)
- */
- function getHTTP($url,$timeout=30)
- {
- try
- {
- $options = array('http'=>array('method'=>'GET','timeout' => $timeout)); // Force network timeout
- $context = stream_context_create($options);
- $data=file_get_contents($url,false,$context,-1, 4000000); // We download at most 4 Mb from source.
- if (!$data) { return array('HTTP Error',array(),''); }
- $httpStatus=$http_response_header[0]; // eg. "HTTP/1.1 200 OK"
- $responseHeaders=http_parse_headers_shaarli($http_response_header);
- return array($httpStatus,$responseHeaders,$data);
- }
- catch (Exception $e) // getHTTP *can* fail silentely (we don't care if the title cannot be fetched)
- {
- return array($e->getMessage(),'','');
- }
- }
- // Extract title from an HTML document.
- // (Returns an empty string if not found.)
- function html_extract_title($html)
- {
- return preg_match('!<title>(.*?)</title>!is', $html, $matches) ? trim(str_replace("\n",' ', $matches[1])) : '' ;
- }
- // ------------------------------------------------------------------------------------------
- // Token management for XSRF protection
- // Token should be used in any form which acts on data (create,update,delete,import...).
- if (!isset($_SESSION['tokens'])) $_SESSION['tokens']=array(); // Token are attached to the session.
- // Returns a token.
- function getToken()
- {
- $rnd = sha1(uniqid('',true).'_'.mt_rand().$GLOBALS['salt']); // We generate a random string.
- $_SESSION['tokens'][$rnd]=1; // Store it on the server side.
- return $rnd;
- }
- // Tells if a token is ok. Using this function will destroy the token.
- // true=token is ok.
- function tokenOk($token)
- {
- if (isset($_SESSION['tokens'][$token]))
- {
- unset($_SESSION['tokens'][$token]); // Token is used: destroy it.
- return true; // Token is ok.
- }
- return false; // Wrong token, or already used.
- }
- // ------------------------------------------------------------------------------------------
- /* This class is in charge of building the final page.
- (This is basically a wrapper around RainTPL which pre-fills some fields.)
- p = new pageBuilder;
- p.assign('myfield','myvalue');
- p.renderPage('mytemplate');
- */
- class pageBuilder
- {
- private $tpl; // RainTPL template
- function __construct()
- {
- $this->tpl=false;
- }
- private function initialize()
- {
- $this->tpl = new RainTPL;
- $this->tpl->assign('newversion',checkUpdate());
- $this->tpl->assign('feedurl',htmlspecialchars(indexUrl()));
- $searchcrits=''; // Search criteria
- if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.urlencode($_GET['searchtags']);
- elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.urlencode($_GET['searchterm']);
- $this->tpl->assign('searchcrits',$searchcrits);
- $this->tpl->assign('source',indexUrl());
- $this->tpl->assign('version',shaarli_version);
- $this->tpl->assign('scripturl',indexUrl());
- $this->tpl->assign('pagetitle','Shaarli');
- $this->tpl->assign('privateonly',!empty($_SESSION['privateonly'])); // Show only private links ?
- if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']);
- if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']);
- $this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] );
- return;
- }
- // The following assign() method is basically the same as RainTPL (except that it's lazy)
- public function assign($what,$where)
- {
- if ($this->tpl===false) $this->initialize(); // Lazy initialization
- $this->tpl->assign($what,$where);
- }
- // Render a specific page (using a template).
- // eg. pb.renderPage('picwall')
- public function renderPage($page)
- {
- if ($this->tpl===false) $this->initialize(); // Lazy initialization
- $this->tpl->draw($page);
- }
- }
- // ------------------------------------------------------------------------------------------
- /* 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 (eg.'?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 (eg.'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 (eg. "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 used logged in ? (used to filter private links)
- // Constructor:
- function __construct($isLoggedIn)
- // Input : $isLoggedIn : is the used 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://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');
- $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 it does not exist.
- public function getLinkFromUrl($url)
- {
- if (isset($this->urls[$url])) return $this->links[$this->urls[$url]];
- return false;
- }
- // Case insentitive search among links (in url, title and description). Returns filtered list of links.
- // eg. 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).
- // eg. 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' (eg. '20120125')
- // Sort order is: older articles first.
- // eg. 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;
- }
- }
- // ------------------------------------------------------------------------------------------
- // Ouput the last 50 links in RSS 2.0 format.
- function showRSS()
- {
- header('Content-Type: application/rss+xml; charset=utf-8');
- // $usepermalink : If true, use permalink instead of final link.
- // User just has to add 'permalink' in URL parameters. eg. http://mysite.com/shaarli/?do=rss&permalinks
- $usepermalinks = isset($_GET['permalinks']);
- // Cache system
- $query = $_SERVER["QUERY_STRING"];
- $cache = new pageCache(pageUrl(),startsWith($query,'do=rss') && !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).
- // Optionnaly 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 $linksToDisplay = $LINKSDB;
- $pageaddr=htmlspecialchars(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 '<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 '<!-- End Of PubSubHubbub Discovery -->';
- }
- $i=0;
- $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys().
- while ($i<50 && $i<count($keys))
- {
- $link = $linksToDisplay[$keys[$i]];
- $guid = $pageaddr.'?'.smallHash($link['linkdate']);
- $rfc822date = linkdate2rfc822($link['linkdate']);
- $absurl = htmlspecialchars($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>';
- 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";
- 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"; }
- }
- // Add permalink in description
- $descriptionlink = '(<a href="'.$guid.'">Permalink</a>)';
- // 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";
- $i++;
- }
- echo '</channel></rss><!-- Cached version of '.pageUrl().' -->';
- $cache->cache(ob_get_contents());
- ob_end_flush();
- exit;
- }
- // ------------------------------------------------------------------------------------------
- // Ouput the last 50 links in ATOM format.
- function showATOM()
- {
- header('Content-Type: application/atom+xml; charset=utf-8');
- // $usepermalink : If true, use permalink instead of final link.
- // User just has to add 'permalink' in URL parameters. eg. http://mysite.com/shaarli/?do=atom&permalinks
- $usepermalinks = isset($_GET['permalinks']);
- // Cache system
- $query = $_SERVER["QUERY_STRING"];
- $cache = new pageCache(pageUrl(),startsWith($query,'do=atom') && !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).
- // Optionnaly 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 $linksToDisplay = $LINKSDB;
- $pageaddr=htmlspecialchars(indexUrl());
- $latestDate = '';
- $entries='';
- $i=0;
- $keys=array(); foreach($linksToDisplay as $key=>$value) { $keys[]=$key; } // No, I can't use array_keys().
- while ($i<50 && $i<count($keys))
- {
- $link = $linksToDisplay[$keys[$i]];
- $guid = $pageaddr.'?'.smallHash($link['linkdate']);
- $iso8601date = linkdate2iso8601($link['linkdate']);
- $latestDate = max($latestDate,$iso8601date);
- $absurl = htmlspecialchars($link['url']);
- if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute
- $entries.='<entry><title>'.htmlspecialchars($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>';
- // Add permalink in description
- $descriptionlink = htmlspecialchars('(<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;
- $entries.='<content type="html">'.htmlspecialchars(nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($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.="</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"]).'" />';
- if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
- {
- $feed.='<!-- PubSubHubbub Discovery -->';
- $feed.='<link rel="hub" href="'.htmlspecialchars($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.=$entries;
- $feed.='</feed><!-- Cached version of '.pageUrl().' -->';
- echo $feed;
- $cache->cache(ob_get_contents());
- ob_end_flush();
- exit;
- }
- // ------------------------------------------------------------------------------------------
- // 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()
- {
- // Cache system
- $query = $_SERVER["QUERY_STRING"];
- $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).
- /* 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; }
- 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();
- $day…
Large files files are truncated, but you can click here to view the full file