]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - index.php
bump version to 0.0.45beta
[github/shaarli/Shaarli.git] / index.php
index c91ae23b4992f7e9f05a8241745eb83bbe7a2788..0d3c33011d146b69481766e649a7c5fdeb1fcbe3 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,5 +1,5 @@
 <?php
-// Shaarli 0.0.4beta - 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
@@ -11,7 +11,7 @@
 date_default_timezone_set('UTC');
 
 // -----------------------------------------------------------------------------------------------
-// Hardcoded parameter (These parameters can be overwritten by creating the file /config/options.php)
+// Hardcoded parameter (These parameters can be overwritten by creating the file /data/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.
@@ -32,12 +32,14 @@ $GLOBALS['config']['RAINTPL_TPL'] = 'tpl/' ; // Raintpl template directory (keep
 $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']['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.
 // -----------------------------------------------------------------------------------------------
 // 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.4beta');
+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/
@@ -46,7 +48,7 @@ define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUES
 // Force cookie path (but do not change lifetime)
 $cookie=session_get_cookie_params();
 $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['HTTP_HOST']); // Set default cookie expiration and path.
+session_set_cookie_params($cookie['lifetime'],$cookiedir,$_SERVER['SERVER_NAME']); // Set default cookie expiration and path.
 
 // Set session parameters on server side.
 define('INACTIVITY_TIMEOUT',3600); // (in seconds). If the user does not access any page within this time, his/her session is considered expired.
@@ -88,7 +90,7 @@ header("Cache-Control: post-check=0, pre-check=0", false);
 header("Pragma: no-cache");
 
 // Directories creations (Note that your web host may require different rights than 705.)
-if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory ('.realpath(dirname(__FILE__)).').</pre>');
+if (!is_writable(realpath(dirname(__FILE__)))) die('<pre>ERROR: Shaarli does not have the right to write in its own directory.</pre>');
 
 // Handling of old config file which do not have the new parameters.
 if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.htmlspecialchars(indexUrl());
@@ -111,13 +113,60 @@ define('STAY_SIGNED_IN_TOKEN', sha1($GLOBALS['hash'].$_SERVER["REMOTE_ADDR"].$GL
 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.
 
+//==================================================================================================
+// Checking session state (i.e. is the user still logged in)
+//==================================================================================================
+
+function setup_login_state() {
+       $userIsLoggedIn = false; // By default, we do not consider the user as logged in;
+       $loginFailure = false; // If set to true, every attempt to authenticate the user will fail. This indicates that an important condition isn't met.
+       if ($GLOBALS['config']['OPEN_SHAARLI']) {
+           $userIsLoggedIn = true;
+       }
+       if (!isset($GLOBALS['login'])) {
+           $userIsLoggedIn = false;  // Shaarli is not configured yet.
+           $loginFailure = true;
+       }
+       if (isset($_COOKIE['shaarli_staySignedIn']) &&
+           $_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN &&
+           !$loginFailure)
+       {
+           fillSessionInfo();
+           $userIsLoggedIn = true;
+       }
+       // If session does not exist on server side, or IP address has changed, or session has expired, logout.
+       if (empty($_SESSION['uid']) ||
+           ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) ||
+           time() >= $_SESSION['expires_on'])
+       {
+           logout();
+           $userIsLoggedIn = false;
+           $loginFailure = true;
+       }
+       if (!empty($_SESSION['longlastingsession'])) {
+           $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
+       }
+       else {
+           $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
+       }
+       if (!$loginFailure) {
+           $userIsLoggedIn = true;
+       }
+
+       return $userIsLoggedIn;
+}
+//==================================================================================================
+$userIsLoggedIn = setup_login_state();
+//==================================================================================================
+//==================================================================================================
+
 // 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.';
+        echo 'Your PHP version is obsolete! Shaarli requires at least php 5.1.0, and thus cannot run. Sorry. Your PHP version has known security vulnerabilities and should be updated as soon as possible.';
         exit;
     }
 }
@@ -129,13 +178,14 @@ function checkphpversion()
 function checkUpdate()
 {
     if (!isLoggedIn()) return ''; // Do not check versions for visitors.
+    if (empty($GLOBALS['config']['ENABLE_UPDATECHECK'])) return ''; // Do not check if the user doesn't want to.
 
     // Get latest version number at most once a day.
     if (!is_file($GLOBALS['config']['UPDATECHECK_FILENAME']) || (filemtime($GLOBALS['config']['UPDATECHECK_FILENAME'])<time()-($GLOBALS['config']['UPDATECHECK_INTERVAL'])))
     {
         $version=shaarli_version;
-        list($httpstatus,$headers,$data) = getHTTP('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.txt',2);
-        if (strpos($httpstatus,'200 OK')!==false) $version=$data;
+        list($httpstatus,$headers,$data) = getHTTP('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.php',2);
+        if (strpos($httpstatus,'200 OK')!==false) $version=str_replace(' */ ?>','',str_replace('<?php /* ','',$data));
         // If failed, never mind. We don't want to bother the user with that.
         file_put_contents($GLOBALS['config']['UPDATECHECK_FILENAME'],$version); // touch file date
     }
@@ -314,30 +364,19 @@ function check_auth($login,$password)
 // Returns true if the user is logged in.
 function isLoggedIn()
 {
-    if ($GLOBALS['config']['OPEN_SHAARLI']) return true;
-
-    if (!isset($GLOBALS['login'])) return false;  // Shaarli is not configured yet.
-
-       if (@$_COOKIE['shaarli_staySignedIn']===STAY_SIGNED_IN_TOKEN)
-       {
-               fillSessionInfo();
-               return true;
-       }
-    // If session does not exist on server side, or IP address has changed, or session has expired, logout.
-    if (empty($_SESSION['uid']) || ($GLOBALS['disablesessionprotection']==false && $_SESSION['ip']!=allIPs()) || time()>=$_SESSION['expires_on'])
-    {
-        logout();
-        return false;
-    }
-    if (!empty($_SESSION['longlastingsession']))  $_SESSION['expires_on']=time()+$_SESSION['longlastingsession']; // In case of "Stay signed in" checked.
-    else $_SESSION['expires_on']=time()+INACTIVITY_TIMEOUT; // Standard session expiration date.
-
-    return true;
+    global $userIsLoggedIn;
+    return $userIsLoggedIn;
 }
 
 // Force logout.
-function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); }  
-setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
+function logout() {
+    if (isset($_SESSION)) {
+        unset($_SESSION['uid']);
+        unset($_SESSION['ip']);
+        unset($_SESSION['username']);
+        unset($_SESSION['privateonly']);
+    }
+    setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH);
 }
 
 
@@ -405,14 +444,14 @@ if (isset($_POST['login']))
             $_SESSION['expires_on']=time()+$_SESSION['longlastingsession'];  // Set session expiration on server-side.
 
             $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-            session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['HTTP_HOST']); // Set session cookie expiration on client side
+            session_set_cookie_params($_SESSION['longlastingsession'],$cookiedir,$_SERVER['SERVER_NAME']); // Set session cookie expiration on client side
             // Note: Never forget the trailing slash on the cookie path!
             session_regenerate_id(true);  // Send cookie with new expiration date to browser.
         }
         else // Standard session expiration (=when browser closes)
         {
             $cookiedir = ''; if(dirname($_SERVER['SCRIPT_NAME'])!='/') $cookiedir=dirname($_SERVER["SCRIPT_NAME"]).'/';
-            session_set_cookie_params(0,$cookiedir,$_SERVER['HTTP_HOST']); // 0 means "When browser closes"
+            session_set_cookie_params(0,$cookiedir,$_SERVER['SERVER_NAME']); // 0 means "When browser closes"
             session_regenerate_id(true);
         }
         // Optional redirect after login:
@@ -429,7 +468,7 @@ if (isset($_POST['login']))
         ban_loginFailed();
         $redir = '';
         if (isset($_GET['post'])) { $redir = '&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):''); }
-        echo '<script language="JavaScript">alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
+        echo '<script>alert("Wrong login/password.");document.location=\'?do=login'.$redir.'\';</script>'; // Redirect to login screen.
         exit;
     }
 }
@@ -444,7 +483,7 @@ function serverUrl()
 {
     $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection.
     $serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]);
-    return 'http'.($https?'s':'').'://'.$_SERVER['HTTP_HOST'].$serverport;
+    return 'http'.($https?'s':'').'://'.$_SERVER['SERVER_NAME'].$serverport;
 }
 
 // Returns the absolute URL of current script, without the query.
@@ -793,14 +832,16 @@ class linkdb implements Iterator, Countable, ArrayAccess
     {
         // 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 = strtolower($searchterms);
+        $s = mb_convert_case($searchterms, MB_CASE_LOWER, 'UTF-8');
         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);
+            $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);
@@ -812,12 +853,14 @@ class linkdb implements Iterator, Countable, ArrayAccess
     // e.g. print_r($mydb->filterTags('linux programming'));
     public function filterTags($tags,$casesensitive=false)
     {
-        $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags)));
+        // 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']:strtolower($l['tags'])));
+            $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;
         }
@@ -889,7 +932,8 @@ function showRSS()
 
     // $usepermalink : If true, use permalink instead of final link.
     // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=rss&permalinks
-    $usepermalinks = isset($_GET['permalinks']);
+    // Also enabled through a config option
+    $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
 
     // Cache system
     $query = $_SERVER["QUERY_STRING"];
@@ -906,7 +950,7 @@ function showRSS()
     else $linksToDisplay = $LINKSDB;
     $nblinksToDisplay = 50;  // Number of links to display.
     if (!empty($_GET['nb']))  // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    { 
+    {
         $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
     }
 
@@ -931,7 +975,7 @@ function showRSS()
         $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>';
+            echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid isPermaLink="true">'.$guid.'</guid><link>'.$guid.'</link>';
         else
             echo '<item><title>'.htmlspecialchars($link['title']).'</title><guid isPermaLink="false">'.$guid.'</guid><link>'.$absurl.'</link>';
         if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) echo '<pubDate>'.htmlspecialchars($rfc822date)."</pubDate>\n";
@@ -963,7 +1007,7 @@ function showATOM()
 
     // $usepermalink : If true, use permalink instead of final link.
     // User just has to add 'permalink' in URL parameters. e.g. http://mysite.com/shaarli/?do=atom&permalinks
-    $usepermalinks = isset($_GET['permalinks']);
+    $usepermalinks = isset($_GET['permalinks']) || !$GLOBALS['config']['ENABLE_RSS_PERMALINKS'];
 
     // Cache system
     $query = $_SERVER["QUERY_STRING"];
@@ -981,7 +1025,7 @@ function showATOM()
     else $linksToDisplay = $LINKSDB;
     $nblinksToDisplay = 50;  // Number of links to display.
     if (!empty($_GET['nb']))  // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links.
-    { 
+    {
         $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
     }
 
@@ -1146,6 +1190,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']);
     }
 
     /* We need to spread the articles on 3 columns.
@@ -1172,10 +1217,7 @@ function showDaily()
     $PAGE = new pageBuilder;
     $PAGE->assign('linksToDisplay',$linksToDisplay);
     $PAGE->assign('linkcount',count($LINKSDB));
-    $PAGE->assign('col1',$columns[0]);
-    $PAGE->assign('col1',$columns[0]);
-    $PAGE->assign('col2',$columns[1]);
-    $PAGE->assign('col3',$columns[2]);
+    $PAGE->assign('cols', $columns);
     $PAGE->assign('day',utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))));
     $PAGE->assign('previousday',$previousday);
     $PAGE->assign('nextday',$nextday);
@@ -1229,7 +1271,6 @@ 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.
             }
         }
@@ -1250,8 +1291,9 @@ function renderPage()
         ksort($tags);
         $tagList=array();
         foreach($tags as $key=>$value)
+       // Tag font size scaling: default 15 and 30 logarithm bases affect scaling, 22 and 6 are arbitrary font sizes for max and min sizes.
         {
-            $tagList[$key] = array('count'=>$value,'size'=>max(40*$value/$maxcount,8));
+            $tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6);
         }
         $PAGE = new pageBuilder;
         $PAGE->assign('linkcount',count($LINKSDB));
@@ -1266,7 +1308,25 @@ function renderPage()
         // Get previous URL (http_referer) and add the tag to the searchtags parameters in query.
         if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER
         parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params);
-        $params['searchtags'] = (empty($params['searchtags']) ?  trim($_GET['addtag']) : trim($params['searchtags']).' '.trim($_GET['addtag']));
+
+        // Check if this tag is already in the search query and ignore it if it is.
+        // Each tag is always separated by a space
+        $current_tags = explode(' ', $params['searchtags']);
+        $addtag = true;
+        foreach ($current_tags as $value) {
+            if ($value === $_GET['addtag']) {
+                $addtag = false;
+                break;
+            }
+        }
+        // Append the tag if necessary
+        if (empty($params['searchtags'])) {
+            $params['searchtags'] = trim($_GET['addtag']);
+        }
+        else if ($addtag) {
+            $params['searchtags'] = trim($params['searchtags']).' '.trim($_GET['addtag']);
+        }
+
         unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different)
         header('Location: ?'.http_build_query($params));
         exit;
@@ -1330,6 +1390,14 @@ function renderPage()
             header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['description'])?'&description='.urlencode($_GET['description']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link.
             exit;
         }
+
+               // Same case as above except that user tried to access ?do=addlink without being logged in
+               // Note: passing empty parameters makes Shaarli generate default URLs and descriptions.
+               if (isset($_GET['do']) && $_GET['do'] === 'addlink') {
+                       header('Location: ?do=login&post=');
+                       exit;
+               }
+
         $PAGE = new pageBuilder;
         buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
         $PAGE->renderPage('linklist');
@@ -1358,12 +1426,12 @@ function renderPage()
 
             // Make sure old password is correct.
             $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']);
-            if ($oldhash!=$GLOBALS['hash']) { echo '<script language="JavaScript">alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
+            if ($oldhash!=$GLOBALS['hash']) { echo '<script>alert("The old password is not correct.");document.location=\'?do=changepasswd\';</script>'; exit; }
             // Save new password
             $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
             $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
             writeConfig();
-            echo '<script language="JavaScript">alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
+            echo '<script>alert("Your password has been changed.");document.location=\'?do=tools\';</script>';
             exit;
         }
         else // show the change password form.
@@ -1393,8 +1461,10 @@ function renderPage()
             $GLOBALS['disablesessionprotection']=!empty($_POST['disablesessionprotection']);
             $GLOBALS['disablejquery']=!empty($_POST['disablejquery']);
             $GLOBALS['privateLinkByDefault']=!empty($_POST['privateLinkByDefault']);
+            $GLOBALS['config']['ENABLE_RSS_PERMALINKS']= !empty($_POST['enableRssPermalinks']);
+            $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
             writeConfig();
-            echo '<script language="JavaScript">alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
+            echo '<script>alert("Configuration was saved.");document.location=\'?do=tools\';</script>';
             exit;
         }
         else // Show the configuration form.
@@ -1420,6 +1490,7 @@ function renderPage()
             $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->assign('token',getToken());
+            $PAGE->assign('tags', $LINKSDB->allTags());
             $PAGE->renderPage('changetag');
             exit;
         }
@@ -1438,7 +1509,7 @@ function renderPage()
                 $LINKSDB[$key]=$value;
             }
             $LINKSDB->savedb(); // Save to disk.
-            echo '<script language="JavaScript">alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
+            echo '<script>alert("Tag was removed from '.count($linksToAlter).' links.");document.location=\'?\';</script>';
             exit;
         }
 
@@ -1455,7 +1526,7 @@ function renderPage()
                 $LINKSDB[$key]=$value;
             }
             $LINKSDB->savedb(); // Save to disk.
-            echo '<script language="JavaScript">alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
+            echo '<script>alert("Tag was renamed in '.count($linksToAlter).' links.");document.location=\'?searchtags='.urlencode($_POST['totag']).'\';</script>';
             exit;
         }
     }
@@ -1476,7 +1547,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));
@@ -1486,9 +1557,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 language="JavaScript">self.close();</script>'; exit; }
+        if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { 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;
     }
@@ -1497,7 +1569,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 language="JavaScript">self.close();</script>'; exit; }
+        if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { 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.
@@ -1516,10 +1588,38 @@ 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 language="JavaScript">self.close();</script>'; exit; }
-        $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' );
-        if ($returnurl=='?') { $returnurl = (isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '?'); }
-        header('Location: '.$returnurl); // After deleting the link, redirect to the page the user was on.
+        if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo '<script>self.close();</script>'; exit; }
+        // Pick where we're going to redirect
+        // =============================================================
+        // Basically, we can't redirect to where we were previously if it was a permalink
+        // or an edit_link, because it would 404.
+        // Cases:
+        //    - /             : nothing in $_GET, redirect to self
+        //    - /?page        : redirect to self
+        //    - /?searchterm  : redirect to self (there might be other links) 
+        //    - /?searchtags  : redirect to self
+        //    - /permalink    : redirect to / (the link does not exist anymore)
+        //    - /?edit_link   : redirect to / (the link does not exist anymore)
+        // PHP treats the permalink as a $_GET variable, so we need to check if every condition for self
+        // redirect is not satisfied, and only then redirect to /
+        $location = "?";
+        // Self redirection
+        if (count($_GET) == 0    ||
+            isset($_GET['page']) ||
+            isset($_GET['searchterm']) ||
+            isset($_GET['searchtags'])) {
+
+            if (isset($_POST['returnurl'])) {
+                $location = $_POST['returnurl']; // Handle redirects given by the form
+            }
+
+            if ($location === "?" &&
+                isset($_SERVER['HTTP_REFERER'])) { // Handle HTTP_REFERER in case we're not coming from the same place.
+                $location = $_SERVER['HTTP_REFERER'];
+            }
+        }
+
+        header('Location: ' . $location); // After deleting the link, redirect to appropriate location
         exit;
     }
 
@@ -1534,6 +1634,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;
     }
@@ -1543,10 +1644,13 @@ 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);
+
+        // 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=[^&]*/');
+        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)
@@ -1557,7 +1661,7 @@ function renderPage()
             $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet).
             $description = (empty($_GET['description']) ? '' : $_GET['description']); // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
             $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] ); // Get tags if it was provided in URL
-            $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL 
+            $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL
             if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url;
             // If this is an HTTP link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
             if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http')
@@ -1568,7 +1672,7 @@ function renderPage()
                                         {
                         // Look for charset in html header.
                                                preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
+
                                                // If found, extract encoding.
                                                if (!empty($meta[0]))
                                                {
@@ -1578,7 +1682,7 @@ function renderPage()
                                                        $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8';
                                                }
                                                else { $html_charset = 'utf-8'; }
+
                                                // Extract title
                                                $title = html_extract_title($data);
                                                if (!empty($title))
@@ -1602,6 +1706,7 @@ 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('tags', $LINKSDB->allTags());
         $PAGE->renderPage('editlink');
         exit;
     }
@@ -1654,7 +1759,7 @@ HTML;
         if (!isset($_POST['token']) || (!isset($_FILES)) || (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size']==0))
         {
             $returnurl = ( empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER'] );
-            echo '<script language="JavaScript">alert("The file you are trying to upload is probably bigger than what this webserver can accept ('.getMaxFileSize().' bytes). Please upload in smaller chunks.");document.location=\''.htmlspecialchars($returnurl).'\';</script>';
+            echo '<script>alert("The file you are trying to upload is probably bigger than what this webserver can accept ('.getMaxFileSize().' bytes). Please upload in smaller chunks.");document.location=\''.htmlspecialchars($returnurl).'\';</script>';
             exit;
         }
         if (!tokenOk($_POST['token'])) die('Wrong token.');
@@ -1758,11 +1863,11 @@ function importFile()
         }
         $LINKSDB->savedb();
 
-        echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
+        echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) was successfully processed: '.$import_count.' links imported.");document.location=\'?\';</script>';
     }
     else
     {
-        echo '<script language="JavaScript">alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
+        echo '<script>alert("File '.json_encode($filename).' ('.$filesize.' bytes) has an unknown file format. Nothing was imported.");document.location=\'?\';</script>';
     }
 }
 
@@ -1892,13 +1997,13 @@ function computeThumbnail($url,$href=false)
     if ($domain=='youtube.com' || $domain=='www.youtube.com')
     {
         parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract video ID and get thumbnail
-        if (!empty($params['v'])) return array('src'=>'http://img.youtube.com/vi/'.$params['v'].'/default.jpg',
+        if (!empty($params['v'])) return array('src'=>'https://img.youtube.com/vi/'.$params['v'].'/default.jpg',
                                                'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
     }
     if ($domain=='youtu.be') // Youtube short links
     {
         $path = parse_url($url,PHP_URL_PATH);
-        return array('src'=>'http://img.youtube.com/vi'.$path.'/default.jpg',
+        return array('src'=>'https://img.youtube.com/vi'.$path.'/default.jpg',
                      'href'=>$href,'width'=>'120','height'=>'90','alt'=>'YouTube thumbnail');
     }
     if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting
@@ -1912,18 +2017,18 @@ function computeThumbnail($url,$href=false)
     {
         $path = parse_url($url,PHP_URL_PATH);
         if (startsWith($path,'/a/')) return array(); // Thumbnails for albums are not available.
-        if (startsWith($path,'/r/')) return array('src'=>'http://i.imgur.com/'.basename($path).'s.jpg',
+        if (startsWith($path,'/r/')) return array('src'=>'https://i.imgur.com/'.basename($path).'s.jpg',
                                                   'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
-        if (startsWith($path,'/gallery/')) return array('src'=>'http://i.imgur.com'.substr($path,8).'s.jpg',
+        if (startsWith($path,'/gallery/')) return array('src'=>'https://i.imgur.com'.substr($path,8).'s.jpg',
                                                         'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
 
-        if (substr_count($path,'/')==1) return array('src'=>'http://i.imgur.com/'.substr($path,1).'s.jpg',
+        if (substr_count($path,'/')==1) return array('src'=>'https://i.imgur.com/'.substr($path,1).'s.jpg',
                                                      'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
     }
     if ($domain=='i.imgur.com')
     {
         $pi = pathinfo(parse_url($url,PHP_URL_PATH));
-        if (!empty($pi['filename'])) return array('src'=>'http://i.imgur.com/'.$pi['filename'].'s.jpg',
+        if (!empty($pi['filename'])) return array('src'=>'https://i.imgur.com/'.$pi['filename'].'s.jpg',
                                                   'href'=>$href,'width'=>'90','height'=>'90','alt'=>'imgur.com thumbnail');
     }
     if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com')
@@ -2013,7 +2118,6 @@ function thumbnail($url,$href=false)
     return $html;
 }
 
-
 // Returns the HTML code to display a thumbnail for a link
 // for the picture wall (using lazy image loading)
 // Understands various services (youtube.com...)
@@ -2027,11 +2131,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']).'"';
@@ -2067,7 +2168,9 @@ function install()
     {   // Step 2: Check if data in session is correct.
         echo '<pre>Sessions do not seem to work correctly on your server.<br>';
         echo 'Make sure the variable session.save_path is set correctly in your php config, and that you have write access to it.<br>';
-        echo 'It currently points to '.session_save_path().'<br><br><a href="?">Click to try again.</a></pre>';
+        echo 'It currently points to '.session_save_path().'<br>';
+        echo 'Check that the hostname used to access Shaarli contains a dot. On some browsers, accessing your server via a hostname like \'localhost\' or any custom hostname without a dot causes cookie storage to fail. We recommend accessing your server via it\'s IP address or Fully Qualified Domain Name.<br>';
+        echo '<br><a href="?">Click to try again.</a></pre>';
         die;
     }
     if (!isset($_SESSION['session_tested']))
@@ -2093,14 +2196,15 @@ function install()
         $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
         $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
         $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.htmlspecialchars(indexUrl()) : $_POST['title'] );
+        $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
         writeConfig();
-        echo '<script language="JavaScript">alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
+        echo '<script>alert("Shaarli is now configured. Please enter your login/password and start shaaring your links!");document.location=\'?do=login\';</script>';
         exit;
     }
 
     // Display config form:
     list($timezone_form,$timezone_js) = templateTZform();
-    $timezone_html=''; if ($timezone_form!='') $timezone_html='<tr><td valign="top"><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
+    $timezone_html=''; if ($timezone_form!='') $timezone_html='<tr><td><b>Timezone:</b></td><td>'.$timezone_form.'</td></tr>';
 
     $PAGE = new pageBuilder;
     $PAGE->assign('timezone_html',$timezone_html);
@@ -2138,17 +2242,17 @@ function templateTZform($ptz=false)
                 $continent=substr($tz,0,$spos); $city=substr($tz,$spos+1);
                 $continents[$continent]=1;
                 if (!isset($cities[$continent])) $cities[$continent]='';
-                $cities[$continent].='<option value="'.$city.'"'.($pcity==$city?'selected':'').'>'.$city.'</option>';
+                $cities[$continent].='<option value="'.$city.'"'.($pcity==$city?' selected':'').'>'.$city.'</option>';
             }
         }
         $continents_html = '';
         $continents = array_keys($continents);
         foreach($continents as $continent)
-            $continents_html.='<option  value="'.$continent.'"'.($pcontinent==$continent?'selected':'').'>'.$continent.'</option>';
+            $continents_html.='<option  value="'.$continent.'"'.($pcontinent==$continent?' selected':'').'>'.$continent.'</option>';
         $cities_html = $cities[$pcontinent];
         $timezone_form = "Continent: <select name=\"continent\" id=\"continent\" onChange=\"onChangecontinent();\">${continents_html}</select>";
         $timezone_form .= "&nbsp;&nbsp;&nbsp;&nbsp;City: <select name=\"city\" id=\"city\">${cities[$pcontinent]}</select><br />";
-        $timezone_js = "<script language=\"JavaScript\">";
+        $timezone_js = "<script>";
         $timezone_js .= "function onChangecontinent(){document.getElementById(\"city\").innerHTML = citiescontinent[document.getElementById(\"continent\").value];}";
         $timezone_js .= "var citiescontinent = ".json_encode($cities).";" ;
         $timezone_js .= "</script>" ;
@@ -2207,45 +2311,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.
@@ -2260,10 +2325,12 @@ function writeConfig()
     $config .= '$GLOBALS[\'disablesessionprotection\']='.var_export($GLOBALS['disablesessionprotection'],true).'; ';
     $config .= '$GLOBALS[\'disablejquery\']='.var_export($GLOBALS['disablejquery'],true).'; ';
     $config .= '$GLOBALS[\'privateLinkByDefault\']='.var_export($GLOBALS['privateLinkByDefault'],true).'; ';
+    $config .= '$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 .= ' ?>';
     if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0)
     {
-        echo '<script language="JavaScript">alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>';
+        echo '<script>alert("Shaarli could not create the config file. Please make sure Shaarli has the right to write in the folder is it installed in.");document.location=\'?\';</script>';
         exit;
     }
 }
@@ -2354,7 +2421,7 @@ function genThumbnail()
         // This is more complex: we have to perform a HTTP request, then parse the result.
         // Maybe we should deport this to JavaScript ? Example: http://stackoverflow.com/questions/1361149/get-img-thumbnails-from-vimeo/4285098#4285098
         $vid = substr(parse_url($url,PHP_URL_PATH),1);
-        list($httpstatus,$headers,$data) = getHTTP('http://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5);
+        list($httpstatus,$headers,$data) = getHTTP('https://vimeo.com/api/v2/video/'.htmlspecialchars($vid).'.php',5);
         if (strpos($httpstatus,'200 OK')!==false)
         {
             $t = unserialize($data);
@@ -2500,7 +2567,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();
 ?>