]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - index.php
Merge remote-tracking branch 'ArthurHoaro/daily-date' into next
[github/shaarli/Shaarli.git] / index.php
index d7140409520cf846d36ded93bbd58fdd2133f4f5..5aa7116fe087404ff2048a0cc2068c8ac71d7107 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,5 +1,5 @@
 <?php
-// Shaarli 0.0.43beta - 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
@@ -34,12 +34,13 @@ $GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency
                                           // 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.43beta');
+define('shaarli_version','0.0.45beta');
 define('PHPPREFIX','<?php /* '); // Prefix to encapsulate data in PHP code.
 define('PHPSUFFIX',' */ ?>'); // Suffix to encapsulate data in PHP code.
 // http://server.com/x/shaarli --> /shaarli/
@@ -67,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
@@ -267,21 +272,6 @@ function nl2br_escaped($html)
     return str_replace('>','&gt;',str_replace('<','&lt;',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)
@@ -302,12 +292,18 @@ function keepMultipleSpaces($text)
 // (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.
 }
 
 // ------------------------------------------------------------------------------------------
@@ -529,28 +525,17 @@ 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. */
 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.
@@ -567,16 +552,6 @@ 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 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 )
 {
@@ -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,13 +702,14 @@ 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']); // 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.
     {
@@ -1015,14 +777,15 @@ 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']);  // Read links from database (and filter private links if used 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.
     {
@@ -1096,7 +859,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']);  // 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).
@@ -1142,7 +905,7 @@ function showDailyRSS()
             $l = $LINKSDB[$linkdate];
             $l['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($l['description']))));
             $l['thumbnail'] = thumbnail($l['url']);
-            $l['localdate']=linkdate2locale($l['linkdate']);
+            $l['timestamp'] = linkdate2timestamp($l['linkdate']);
             if (startsWith($l['url'],'?')) $l['url']=indexUrl().$l['url'];  // make permalink URL absolute
             $links[$linkdate]=$l;
         }
@@ -1164,7 +927,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']);  // Read links from database (and filter private links if used it not logged in).
 
 
     $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
@@ -1190,7 +953,7 @@ function showDaily()
         $linksToDisplay[$key]['taglist']=$taglist;
         $linksToDisplay[$key]['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))));
         $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']);
-        $linksToDisplay[$key]['localdate'] = linkdate2locale($link['linkdate']);
+        $linksToDisplay[$key]['timestamp'] = linkdate2timestamp($link['linkdate']);
     }
 
     /* We need to spread the articles on 3 columns.
@@ -1218,7 +981,7 @@ function showDaily()
     $PAGE->assign('linksToDisplay',$linksToDisplay);
     $PAGE->assign('linkcount',count($LINKSDB));
     $PAGE->assign('cols', $columns);
-    $PAGE->assign('day',utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))));
+    $PAGE->assign('day',linkdate2timestamp($day.'_000000'));
     $PAGE->assign('previousday',$previousday);
     $PAGE->assign('nextday',$nextday);
     $PAGE->renderPage('daily');
@@ -1230,7 +993,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']);  // Read links from database (and filter private links if used it not logged in).
 
     // -------- Display login form.
     if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login'))
@@ -1260,6 +1023,7 @@ function renderPage()
         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();
 
@@ -1271,10 +1035,10 @@ function renderPage()
             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);
@@ -1286,6 +1050,7 @@ function renderPage()
     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);
@@ -1464,6 +1229,7 @@ function renderPage()
             $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
             $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
             $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
+            $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = !empty($_POST['hidePublicLinks']);
             writeConfig();
             echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
             exit;
@@ -1491,6 +1257,7 @@ function renderPage()
             $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->assign('token',getToken());
+            $PAGE->assign('tags', $LINKSDB->allTags());
             $PAGE->renderPage('changetag');
             exit;
         }
@@ -1547,7 +1314,7 @@ function renderPage()
         $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces.
         $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));
@@ -1557,9 +1324,10 @@ function renderPage()
         pubsubhub();
 
         // If we are called from the bookmarklet, we must close the popup:
-        if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script>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($linkdate);  // Scroll to the link which has been edited.
+        if (strstr($returnurl, "do=addlink")) { $returnurl = '?'; } //if we come from ?do=addlink, set returnurl to homepage instead
         header('Location: '.$returnurl); // After saving the link, redirect to the page the user was on.
         exit;
     }
@@ -1568,7 +1336,7 @@ function renderPage()
     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>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.
         header('Location: '.$returnurl); // After canceling, redirect to the page the user was on.
@@ -1587,7 +1355,7 @@ function renderPage()
         $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>self.close();</script>'; exit; }
+        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
@@ -1595,7 +1363,7 @@ function renderPage()
         // Cases:
         //    - /             : nothing in $_GET, redirect to self
         //    - /?page        : redirect to self
-        //    - /?searchterm  : redirect to self (there might be other links) 
+        //    - /?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)
@@ -1633,6 +1401,7 @@ function renderPage()
         $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('tags', $LINKSDB->allTags());
         $PAGE->renderPage('editlink');
         exit;
     }
@@ -1642,15 +1411,26 @@ function renderPage()
     {
         $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);
-        $i=strpos($url,'?fb_'); if ($i!==false) $url=substr($url,0,$i);
-       $i=strpos($url,'?__scoop'); if ($i!==false) $url=substr($url,0,$i);
-       $i=strpos($url,'#tk.rss_all?'); if ($i!==false) $url=substr($url,0,$i);
-       $i=strpos($url,'?utm_campaign='); if ($i!==false) $url=substr($url,0,$i);
-       $i=strpos($url,'?utm_medium='); 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)
@@ -1706,6 +1486,8 @@ function renderPage()
         $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;
     }
@@ -1779,7 +1561,6 @@ HTML;
 
     // -------- 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;
@@ -1790,7 +1571,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']);   // Read links from database (and filter private links if used it not logged in).
     $filename=$_FILES['filetoupload']['name'];
     $filesize=$_FILES['filetoupload']['size'];
     $data=file_get_contents($_FILES['filetoupload']['tmp_name']);
@@ -1906,6 +1687,7 @@ function buildLinkList($PAGE,$LINKSDB)
     else
         $linksToDisplay = $LINKSDB;  // Otherwise, display without filtering.
 
+
     // Option: Show only private links
     if (!empty($_SESSION['privateonly']))
     {
@@ -1943,10 +1725,16 @@ function buildLinkList($PAGE,$LINKSDB)
         $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++;
     }
@@ -1972,6 +1760,7 @@ function buildLinkList($PAGE,$LINKSDB)
     $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;
 }
 
@@ -2130,11 +1919,8 @@ function lazyThumbnail($url,$href=false)
 
     $html='<a href="'.htmlspecialchars($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="'.htmlspecialchars($t['src']).'"';
 
     if (!empty($t['width']))  $html.=' width="'.htmlspecialchars($t['width']).'"';
     if (!empty($t['height'])) $html.=' height="'.htmlspecialchars($t['height']).'"';
@@ -2313,45 +2099,6 @@ if (!function_exists('json_encode')) {
     }
 }
 
-// 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.
@@ -2368,6 +2115,7 @@ function writeConfig()
     $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
     $config .= '$GLOBALS[\'config\'][\'ENABLE_RSS_PERMALINKS\']='.var_export($GLOBALS['config']['ENABLE_RSS_PERMALINKS'], true).'; ';
     $config .= '$GLOBALS[\'config\'][\'ENABLE_UPDATECHECK\']='.var_export($GLOBALS['config']['ENABLE_UPDATECHECK'], true).'; ';
+    $config .= '$GLOBALS[\'config\'][\'HIDE_PUBLIC_LINKS\']='.var_export($GLOBALS['config']['HIDE_PUBLIC_LINKS'], true).'; ';
     $config .= ' ?>';
     if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
     {
@@ -2608,7 +2356,6 @@ if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=r
 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();
 ?>