|
@@ -68,6 +68,10 @@ checkphpversion();
|
|
|
error_reporting(E_ALL^E_WARNING); // See all error except warnings.
|
|
|
//error_reporting(-1); // See all errors (for debugging only)
|
|
|
|
|
|
+// Shaarli library
|
|
|
+require_once 'application/LinkDB.php';
|
|
|
+require_once 'application/Utils.php';
|
|
|
+
|
|
|
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
|
|
@@ -268,21 +272,6 @@ 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)
|
|
@@ -536,20 +525,6 @@ function getMaxFileSize()
|
|
|
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. */
|
|
@@ -710,220 +685,6 @@ class pageBuilder
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// ------------------------------------------------------------------------------------------
|
|
|
-/* 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"?
|
|
|
- // Using mb_convert_case($val, MB_CASE_LOWER, 'UTF-8') allows us to perform searches on
|
|
|
- // Unicode text. See https://github.com/shaarli/Shaarli/issues/75 for examples.
|
|
|
- $filtered=array();
|
|
|
- $s = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
|
|
|
- foreach($this->links as $l)
|
|
|
- {
|
|
|
- $found= (strpos(mb_convert_case($l['title'], MB_CASE_LOWER, 'UTF-8'),$s) !== false)
|
|
|
- || (strpos(mb_convert_case($l['description'], MB_CASE_LOWER, 'UTF-8'),$s) !== false)
|
|
|
- || (strpos(mb_convert_case($l['url'], MB_CASE_LOWER, 'UTF-8'),$s) !== false)
|
|
|
- || (strpos(mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8'),$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)
|
|
|
- {
|
|
|
- // Same as above, we use UTF-8 conversion to handle various graphemes (i.e. cyrillic, or greek)
|
|
|
- // TODO: is $casesensitive ever true ?
|
|
|
- $t = str_replace(',',' ',($casesensitive?$tags:mb_convert_case($tags, MB_CASE_LOWER, 'UTF-8')));
|
|
|
- $searchtags=explode(' ',$t);
|
|
|
- $filtered=array();
|
|
|
- foreach($this->links as $l)
|
|
|
- {
|
|
|
- $linktags = explode(' ',($casesensitive?$l['tags']:mb_convert_case($l['tags'], MB_CASE_LOWER, 'UTF-8')));
|
|
|
- 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()
|
|
@@ -941,7 +702,7 @@ function showRSS()
|
|
|
$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(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
|
|
|
// Optionally filter the results:
|
|
|
$linksToDisplay=array();
|
|
@@ -1019,7 +780,7 @@ function showATOM()
|
|
|
$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).
|
|
|
+ $LINKSDB = new LinkDB(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
|
|
|
|
|
|
// Optionally filter the results:
|
|
@@ -1104,7 +865,7 @@ function showDailyRSS()
|
|
|
$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).
|
|
|
+ $LINKSDB = new LinkDB(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
|
|
|
/* Some Shaarlies may have very few links, so we need to look
|
|
|
back in time (rsort()) until we have enough days ($nb_of_days).
|
|
@@ -1172,7 +933,7 @@ function showDailyRSS()
|
|
|
// "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(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
|
|
|
|
|
|
$day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
|
|
@@ -1240,7 +1001,7 @@ function showDaily()
|
|
|
// 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(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
|
|
|
// -------- Display login form.
|
|
|
if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login'))
|
|
@@ -1822,7 +1583,7 @@ HTML;
|
|
|
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(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']);
|
|
|
$filename=$_FILES['filetoupload']['name'];
|
|
|
$filesize=$_FILES['filetoupload']['size'];
|
|
|
$data=file_get_contents($_FILES['filetoupload']['tmp_name']);
|