]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - index.php
Handle errors raised by plugins in template. fixes #370
[github/shaarli/Shaarli.git] / index.php
index a093a283de74fa787732161f763828f7d1532a13..ea7bf4d105eb12d2c397868a9c3f0584852a39f9 100755 (executable)
--- a/index.php
+++ b/index.php
@@ -1,10 +1,20 @@
 <?php
-// Shaarli 0.5.1 - Shaare your links...
-// The personal, minimalist, super-fast, no-database Delicious clone. By sebsauvage.net
-// http://sebsauvage.net/wiki/doku.php?id=php:shaarli
-// Licence: http://www.opensource.org/licenses/zlib-license.php
-// Requires: PHP 5.3.x
-// -----------------------------------------------------------------------------------------------
+/**
+ * Shaarli v0.5.4 - Shaare your links...
+ *
+ * The personal, minimalist, super-fast, no-database Delicious clone.
+ *
+ * Friendly fork by the Shaarli community:
+ *  - https://github.com/shaarli/Shaarli
+ *
+ * Original project by sebsauvage.net:
+ *  - http://sebsauvage.net/wiki/doku.php?id=php:shaarli
+ *  - https://github.com/sebsauvage/Shaarli
+ *
+ * Licence: http://www.opensource.org/licenses/zlib-license.php
+ *
+ * Requires: PHP 5.3.x
+ */
 
 // Set 'UTC' as the default timezone if it is not defined in php.ini
 // See http://php.net/manual/en/datetime.configuration.php#ini.date.timezone
@@ -35,11 +45,19 @@ $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.
 $GLOBALS['config']['HIDE_PUBLIC_LINKS'] = false;
+//$GLOBALS['config']['ENABLED_PLUGINS'] = array(
+//    'qrcode', 'archiveorg', 'readityourself', 'demo_plugin', 'playvideos',
+//    'wallabag', 'markdown', 'addlink_toolbar',
+//);
+// Warning: order matters.
+$GLOBALS['config']['ENABLED_PLUGINS'] = array('qrcode');
+
+// Default plugins, default config - will be overriden by config.php and then plugin's config.php file.
+//$GLOBALS['plugins']['WALLABAG_URL'] = 'https://demo.wallabag.org/';
 // -----------------------------------------------------------------------------------------------
-define('shaarli_version','0.5.1');
+define('shaarli_version', '0.5.4');
 // http://server.com/x/shaarli --> /shaarli/
 define('WEB_PATH', substr($_SERVER["REQUEST_URI"], 0, 1+strrpos($_SERVER["REQUEST_URI"], '/', 0)));
 
@@ -59,11 +77,14 @@ if (is_file($GLOBALS['config']['CONFIG_FILE'])) {
 // Shaarli library
 require_once 'application/Cache.php';
 require_once 'application/CachedPage.php';
+require_once 'application/HttpUtils.php';
 require_once 'application/LinkDB.php';
 require_once 'application/TimeZone.php';
 require_once 'application/Url.php';
 require_once 'application/Utils.php';
 require_once 'application/Config.php';
+require_once 'application/PluginManager.php';
+require_once 'application/Router.php';
 
 // Ensure the PHP version is supported
 try {
@@ -92,20 +113,25 @@ ini_set('session.use_only_cookies', 1);
 // Prevent PHP form using sessionID in URL if cookies are disabled.
 ini_set('session.use_trans_sid', false);
 
-// Regenerate session id if invalid or not defined in cookie.
-if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
-    $_COOKIE['shaarli'] = uniqid();
-}
 session_name('shaarli');
 // Start session if needed (Some server auto-start sessions).
 if (session_id() == '') {
     session_start();
 }
 
+// Regenerate session ID if invalid or not defined in cookie.
+if (isset($_COOKIE['shaarli']) && !is_session_id_valid($_COOKIE['shaarli'])) {
+    session_regenerate_id(true);
+    $_COOKIE['shaarli'] = session_id();
+}
+
 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
 
+$pluginManager = PluginManager::getInstance();
+$pluginManager->load($GLOBALS['config']['ENABLED_PLUGINS']);
+
 ob_start();  // Output buffering for the page cache.
 
 
@@ -128,7 +154,7 @@ header("Pragma: no-cache");
 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 '.escape(indexUrl());
+if (empty($GLOBALS['title'])) $GLOBALS['title']='Shared links on '.escape(index_url($_SERVER));
 if (empty($GLOBALS['timezone'])) $GLOBALS['timezone']=date_default_timezone_get();
 if (empty($GLOBALS['redirector'])) $GLOBALS['redirector']='';
 if (empty($GLOBALS['disablesessionprotection'])) $GLOBALS['disablesessionprotection']=false;
@@ -207,9 +233,11 @@ function checkUpdate()
     // 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.php',2);
-        if (strpos($httpstatus,'200 OK')!==false) $version=str_replace(' */ ?>','',str_replace('<?php /* ','',$data));
+        $version = shaarli_version;
+        list($headers, $data) = get_http_url('https://raw.githubusercontent.com/shaarli/Shaarli/master/shaarli_version.php', 2);
+        if (strpos($headers[0], '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
     }
@@ -272,8 +300,8 @@ function pubsubhub()
     {
        $p = new Publisher($GLOBALS['config']['PUBSUBHUB_URL']);
        $topic_url = array (
-                       indexUrl().'?do=atom',
-                       indexUrl().'?do=rss'
+                       index_url($_SERVER).'?do=atom',
+                       index_url($_SERVER).'?do=rss'
                     );
        $p->publish_update($topic_url);
     }
@@ -453,34 +481,6 @@ if (isset($_POST['login']))
 // ------------------------------------------------------------------------------------------
 // Misc utility functions:
 
-// Returns the server URL (including port and http/https), without path.
-// e.g. "http://myserver.com:8080"
-// You can append $_SERVER['SCRIPT_NAME'] to get the current script URL.
-function serverUrl()
-{
-    $https = (!empty($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS'])=='on')) || $_SERVER["SERVER_PORT"]=='443'; // HTTPS detection.
-    $serverport = ($_SERVER["SERVER_PORT"]=='80' || ($https && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]);
-    return 'http'.($https?'s':'').'://'.$_SERVER['SERVER_NAME'].$serverport;
-}
-
-// Returns the absolute URL of current script, without the query.
-// (e.g. http://sebsauvage.net/links/)
-function indexUrl()
-{
-    $scriptname = $_SERVER["SCRIPT_NAME"];
-    // If the script is named 'index.php', we remove it (for better looking URLs,
-    // e.g. http://mysite.com/shaarli/?abcde instead of http://mysite.com/shaarli/index.php?abcde)
-    if (endswith($scriptname,'index.php')) $scriptname = substr($scriptname,0,strlen($scriptname)-9);
-    return serverUrl() . $scriptname;
-}
-
-// Returns the absolute URL of current script, WITH the query.
-// (e.g. http://sebsauvage.net/links/?toto=titi&spamspamspam=humbug)
-function pageUrl()
-{
-    return indexUrl().(!empty($_SERVER["QUERY_STRING"]) ? '?'.$_SERVER["QUERY_STRING"] : '');
-}
-
 // Convert post_max_size/upload_max_filesize (e.g. '16M') parameters to bytes.
 function return_bytes($val)
 {
@@ -533,53 +533,6 @@ function linkdate2iso8601($linkdate)
     return date('c',linkdate2timestamp($linkdate)); // 'c' is for ISO 8601 date format.
 }
 
-// Parse HTTP response headers and return an associative array.
-function http_parse_headers_shaarli( $headers )
-{
-    $res=array();
-    foreach($headers as $header)
-    {
-        $i = strpos($header,': ');
-        if ($i!==false)
-        {
-            $key=substr($header,0,$i);
-            $value=substr($header,$i+2,strlen($header)-$i-2);
-            $res[$key]=$value;
-        }
-    }
-    return $res;
-}
-
-/* GET an URL.
-   Input: $url : URL to get (http://...)
-          $timeout : Network timeout (will wait this many seconds for an anwser before giving up).
-   Output: An array.  [0] = HTTP status message (e.g. "HTTP/1.1 200 OK") or error message
-                      [1] = associative array containing HTTP response headers (e.g. echo getHTTP($url)[1]['Content-Type'])
-                      [2] = data
-    Example: list($httpstatus,$headers,$data) = getHTTP('http://sebauvage.net/');
-             if (strpos($httpstatus,'200 OK')!==false)
-                 echo 'Data type: '.htmlspecialchars($headers['Content-Type']);
-             else
-                 echo 'There was an error: '.htmlspecialchars($httpstatus)
-*/
-function getHTTP($url,$timeout=30)
-{
-    try
-    {
-        $options = array('http'=>array('method'=>'GET','timeout' => $timeout, 'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:23.0) Gecko/20100101 Firefox/23.0')); // Force network timeout
-        $context = stream_context_create($options);
-        $data=file_get_contents($url,false,$context,-1, 4000000); // We download at most 4 Mb from source.
-        if (!$data) { return array('HTTP Error',array(),''); }
-        $httpStatus=$http_response_header[0]; // e.g. "HTTP/1.1 200 OK"
-        $responseHeaders=http_parse_headers_shaarli($http_response_header);
-        return array($httpStatus,$responseHeaders,$data);
-    }
-    catch (Exception $e)  // getHTTP *can* fail silently (we don't care if the title cannot be fetched)
-    {
-        return array($e->getMessage(),'','');
-    }
-}
-
 // Extract title from an HTML document.
 // (Returns an empty string if not found.)
 function html_extract_title($html)
@@ -626,28 +579,43 @@ class pageBuilder
 
     function __construct()
     {
-        $this->tpl=false;
+        $this->tpl = false;
     }
 
+    /**
+     * Initialize all default tpl tags.
+     */
     private function initialize()
     {
         $this->tpl = new RainTPL;
-        $this->tpl->assign('newversion',escape(checkUpdate()));
-        $this->tpl->assign('feedurl',escape(indexUrl()));
-        $searchcrits=''; // Search criteria
-        if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.urlencode($_GET['searchtags']);
-        elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.urlencode($_GET['searchterm']);
-        $this->tpl->assign('searchcrits',$searchcrits);
-        $this->tpl->assign('source',indexUrl());
-        $this->tpl->assign('version',shaarli_version);
-        $this->tpl->assign('scripturl',indexUrl());
-        $this->tpl->assign('pagetitle','Shaarli');
-        $this->tpl->assign('privateonly',!empty($_SESSION['privateonly'])); // Show only private links?
-        if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']);
-        if (!empty($GLOBALS['titleLink'])) $this->tpl->assign('titleLink',$GLOBALS['titleLink']);
-        if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']);
-        $this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] );
-        return;
+        $this->tpl->assign('newversion', escape(checkUpdate()));
+        $this->tpl->assign('feedurl', escape(index_url($_SERVER)));
+        $searchcrits = ''; // Search criteria
+        if (!empty($_GET['searchtags'])) {
+            $searchcrits .= '&searchtags=' . urlencode($_GET['searchtags']);
+        }
+        elseif (!empty($_GET['searchterm'])) {
+            $searchcrits .= '&searchterm=' . urlencode($_GET['searchterm']);
+        }
+        $this->tpl->assign('searchcrits', $searchcrits);
+        $this->tpl->assign('source', index_url($_SERVER));
+        $this->tpl->assign('version', shaarli_version);
+        $this->tpl->assign('scripturl', index_url($_SERVER));
+        $this->tpl->assign('pagetitle', 'Shaarli');
+        $this->tpl->assign('privateonly', !empty($_SESSION['privateonly'])); // Show only private links?
+        if (!empty($GLOBALS['title'])) {
+            $this->tpl->assign('pagetitle', $GLOBALS['title']);
+        }
+        if (!empty($GLOBALS['titleLink'])) {
+            $this->tpl->assign('titleLink', $GLOBALS['titleLink']);
+        }
+        if (!empty($GLOBALS['pagetitle'])) {
+            $this->tpl->assign('pagetitle', $GLOBALS['pagetitle']);
+        }
+        $this->tpl->assign('shaarlititle', empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title']);
+        if (!empty($GLOBALS['plugins']['errors'])) {
+            $this->tpl->assign('plugin_errors', $GLOBALS['plugins']['errors']);
+        }
     }
 
     // The following assign() method is basically the same as RainTPL (except that it's lazy)
@@ -681,7 +649,7 @@ function showRSS()
     $query = $_SERVER["QUERY_STRING"];
     $cache = new CachedPage(
         $GLOBALS['config']['PAGECACHE'],
-        pageUrl(),
+        page_url($_SERVER),
         startsWith($query,'do=rss') && !isLoggedIn()
     );
     $cached = $cache->cachedVersion();
@@ -710,7 +678,7 @@ function showRSS()
         $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
     }
 
-    $pageaddr=escape(indexUrl());
+    $pageaddr=escape(index_url($_SERVER));
     echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">';
     echo '<channel><title>'.$GLOBALS['title'].'</title><link>'.$pageaddr.'</link>';
     echo '<description>Shared links</description><language>en-en</language><copyright>'.$pageaddr.'</copyright>'."\n\n";
@@ -748,7 +716,7 @@ function showRSS()
         echo '<description><![CDATA['.nl2br(keepMultipleSpaces(text2clickable($link['description']))).$descriptionlink.']]></description>'."\n</item>\n";
         $i++;
     }
-    echo '</channel></rss><!-- Cached version of '.escape(pageUrl()).' -->';
+    echo '</channel></rss><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
 
     $cache->cache(ob_get_contents());
     ob_end_flush();
@@ -769,7 +737,7 @@ function showATOM()
     $query = $_SERVER["QUERY_STRING"];
     $cache = new CachedPage(
         $GLOBALS['config']['PAGECACHE'],
-        pageUrl(),
+        page_url($_SERVER),
         startsWith($query,'do=atom') && !isLoggedIn()
     );
     $cached = $cache->cachedVersion();
@@ -798,7 +766,7 @@ function showATOM()
         $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ;
     }
 
-    $pageaddr=escape(indexUrl());
+    $pageaddr=escape(index_url($_SERVER));
     $latestDate = '';
     $entries='';
     $i=0;
@@ -836,7 +804,7 @@ function showATOM()
     $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
     $feed.='<title>'.$GLOBALS['title'].'</title>';
     if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.escape($latestDate).'</updated>';
-    $feed.='<link rel="self" href="'.escape(serverUrl().$_SERVER["REQUEST_URI"]).'" />';
+    $feed.='<link rel="self" href="'.escape(server_url($_SERVER).$_SERVER["REQUEST_URI"]).'" />';
     if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
     {
         $feed.='<!-- PubSubHubbub Discovery -->';
@@ -846,7 +814,7 @@ function showATOM()
     $feed.='<author><name>'.$pageaddr.'</name><uri>'.$pageaddr.'</uri></author>';
     $feed.='<id>'.$pageaddr.'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
     $feed.=$entries;
-    $feed.='</feed><!-- Cached version of '.escape(pageUrl()).' -->';
+    $feed.='</feed><!-- Cached version of '.escape(page_url($_SERVER)).' -->';
     echo $feed;
 
     $cache->cache(ob_get_contents());
@@ -863,7 +831,7 @@ function showDailyRSS() {
     $query = $_SERVER["QUERY_STRING"];
     $cache = new CachedPage(
         $GLOBALS['config']['PAGECACHE'],
-        pageUrl(),
+        page_url($_SERVER),
         startsWith($query,'do=dailyrss') && !isLoggedIn()
     );
     $cached = $cache->cachedVersion();
@@ -908,7 +876,7 @@ function showDailyRSS() {
 
     // Build the RSS feed.
     header('Content-Type: application/rss+xml; charset=utf-8');
-    $pageaddr = escape(indexUrl());
+    $pageaddr = escape(index_url($_SERVER));
     echo '<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">';
     echo '<channel>';
     echo '<title>Daily - '. $GLOBALS['title'] . '</title>';
@@ -921,7 +889,7 @@ function showDailyRSS() {
     foreach ($days as $day => $linkdates) {
         $daydate = linkdate2timestamp($day.'_000000'); // Full text date
         $rfc822date = linkdate2rfc822($day.'_000000');
-        $absurl = escape(indexUrl().'?do=daily&day='.$day);  // Absolute URL of the corresponding "Daily" page.
+        $absurl = escape(index_url($_SERVER).'?do=daily&day='.$day);  // Absolute URL of the corresponding "Daily" page.
 
         // Build the HTML body of this RSS entry.
         $html = '';
@@ -935,7 +903,7 @@ function showDailyRSS() {
             $l['thumbnail'] = thumbnail($l['url']);
             $l['timestamp'] = linkdate2timestamp($l['linkdate']);
             if (startsWith($l['url'], '?')) {
-                $l['url'] = indexUrl() . $l['url'];  // make permalink URL absolute
+                $l['url'] = index_url($_SERVER) . $l['url'];  // make permalink URL absolute
             }
             $links[$linkdate] = $l;
         }
@@ -951,7 +919,7 @@ function showDailyRSS() {
 
         echo $html . PHP_EOL;
     }
-    echo '</channel></rss><!-- Cached version of '. escape(pageUrl()) .' -->';
+    echo '</channel></rss><!-- Cached version of '. escape(page_url($_SERVER)) .' -->';
 
     $cache->cache(ob_get_contents());
     ob_end_flush();
@@ -1022,16 +990,31 @@ function showDaily()
         $fill[$index]+=$length;
     }
     $PAGE = new pageBuilder;
-    $PAGE->assign('linksToDisplay',$linksToDisplay);
-    $PAGE->assign('linkcount',count($LINKSDB));
-    $PAGE->assign('cols', $columns);
-    $PAGE->assign('day',linkdate2timestamp($day.'_000000'));
-    $PAGE->assign('previousday',$previousday);
-    $PAGE->assign('nextday',$nextday);
+    $data = array(
+        'linksToDisplay' => $linksToDisplay,
+        'linkcount' => count($LINKSDB),
+        'cols' => $columns,
+        'day' => linkdate2timestamp($day.'_000000'),
+        'previousday' => $previousday,
+        'nextday' => $nextday,
+    );
+    $pluginManager = PluginManager::getInstance();
+    $pluginManager->executeHooks('render_daily', $data, array('loggedin' => isLoggedIn()));
+
+    foreach ($data as $key => $value) {
+        $PAGE->assign($key, $value);
+    }
+
     $PAGE->renderPage('daily');
     exit;
 }
 
+// Renders the linklist
+function showLinkList($PAGE, $LINKSDB) {
+    buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
+    $PAGE->renderPage('linklist');
+}
+
 
 // ------------------------------------------------------------------------------------------
 // Render HTML page (according to URL parameters and user rights)
@@ -1043,12 +1026,36 @@ function renderPage()
         $GLOBALS['config']['HIDE_PUBLIC_LINKS']
     );
 
+    $PAGE = new pageBuilder;
+
+    // Determine which page will be rendered.
+    $query = (isset($_SERVER['QUERY_STRING'])) ? $_SERVER['QUERY_STRING'] : '';
+    $targetPage = Router::findPage($query, $_GET, isLoggedIn());
+
+    // Call plugin hooks for header, footer and includes, specifying which page will be rendered.
+    // Then assign generated data to RainTPL.
+    $common_hooks = array(
+        'header',
+        'footer',
+        'includes',
+    );
+    $pluginManager = PluginManager::getInstance();
+    foreach($common_hooks as $name) {
+        $plugin_data = array();
+        $pluginManager->executeHooks('render_' . $name, $plugin_data,
+            array(
+                'target' => $targetPage,
+                'loggedin' => isLoggedIn()
+            )
+        );
+        $PAGE->assign('plugins_' . $name, $plugin_data);
+    }
+
     // -------- Display login form.
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login'))
+    if ($targetPage == Router::$PAGE_LOGIN)
     {
         if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; }  // No need to login for open Shaarli
         $token=''; if (ban_canLogin()) $token=getToken(); // Do not waste token generation if not useful.
-        $PAGE = new pageBuilder;
         $PAGE->assign('token',$token);
         $PAGE->assign('returnurl',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']):''));
         $PAGE->renderPage('loginform');
@@ -1064,7 +1071,7 @@ function renderPage()
     }
 
     // -------- Picture wall
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall'))
+    if ($targetPage == Router::$PAGE_PICWALL)
     {
         // Optionally filter the results:
         $links=array();
@@ -1087,15 +1094,22 @@ function renderPage()
             }
         }
 
-        $PAGE = new pageBuilder;
-        $PAGE->assign('linkcount',count($LINKSDB));
-        $PAGE->assign('linksToDisplay',$linksToDisplay);
+        $data = array(
+            'linkcount' => count($LINKSDB),
+            'linksToDisplay' => $linksToDisplay,
+        );
+        $pluginManager->executeHooks('render_picwall', $data, array('loggedin' => isLoggedIn()));
+
+        foreach ($data as $key => $value) {
+            $PAGE->assign($key, $value);
+        }
+
         $PAGE->renderPage('picwall');
         exit;
     }
 
     // -------- Tag cloud
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tagcloud'))
+    if ($targetPage == Router::$PAGE_TAGCLOUD)
     {
         $tags= $LINKSDB->allTags();
 
@@ -1109,9 +1123,17 @@ function renderPage()
         {
             $tagList[$key] = array('count'=>$value,'size'=>log($value, 15) / log($maxcount, 30) * (22-6) + 6);
         }
-        $PAGE = new pageBuilder;
-        $PAGE->assign('linkcount',count($LINKSDB));
-        $PAGE->assign('tags',$tagList);
+
+        $data = array(
+            'linkcount' => count($LINKSDB),
+            'tags' => $tagList,
+        );
+        $pluginManager->executeHooks('render_tagcloud', $data, array('loggedin' => isLoggedIn()));
+
+        foreach ($data as $key => $value) {
+            $PAGE->assign($key, $value);
+        }
+
         $PAGE->renderPage('tagcloud');
         exit;
     }
@@ -1224,32 +1246,36 @@ function renderPage()
                        header('Location: ?do=login&post=');
                        exit;
                }
-
+        showLinkList($PAGE, $LINKSDB);
         if (isset($_GET['edit_link'])) {
             header('Location: ?do=login&edit_link='. escape($_GET['edit_link']));
             exit;
         }
 
-        $PAGE = new pageBuilder;
-        buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
-        $PAGE->renderPage('linklist');
         exit; // Never remove this one! All operations below are reserved for logged in user.
     }
 
     // -------- All other functions are reserved for the registered user:
 
     // -------- Display the Tools menu if requested (import/export/bookmarklet...)
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tools'))
+    if ($targetPage == Router::$PAGE_TOOLS)
     {
-        $PAGE = new pageBuilder;
-        $PAGE->assign('linkcount',count($LINKSDB));
-        $PAGE->assign('pageabsaddr',indexUrl());
+        $data = array(
+            'linkcount' => count($LINKSDB),
+            'pageabsaddr' => index_url($_SERVER),
+        );
+        $pluginManager->executeHooks('render_tools', $data);
+
+        foreach ($data as $key => $value) {
+            $PAGE->assign($key, $value);
+        }
+
         $PAGE->renderPage('tools');
         exit;
     }
 
     // -------- User wants to change his/her password.
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changepasswd'))
+    if ($targetPage == Router::$PAGE_CHANGEPASSWORD)
     {
         if ($GLOBALS['config']['OPEN_SHAARLI']) die('You are not supposed to change a password on an Open Shaarli.');
         if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword']))
@@ -1280,7 +1306,6 @@ function renderPage()
         }
         else // show the change password form.
         {
-            $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->assign('token',getToken());
             $PAGE->renderPage('changepassword');
@@ -1289,7 +1314,7 @@ function renderPage()
     }
 
     // -------- User wants to change configuration
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=configure'))
+    if ($targetPage == Router::$PAGE_CONFIGURE)
     {
         if (!empty($_POST['title']) )
         {
@@ -1325,7 +1350,6 @@ function renderPage()
         }
         else // Show the configuration form.
         {
-            $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->assign('token',getToken());
             $PAGE->assign('title', empty($GLOBALS['title']) ? '' : $GLOBALS['title'] );
@@ -1339,11 +1363,10 @@ function renderPage()
     }
 
     // -------- User wants to rename a tag or delete it
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changetag'))
+    if ($targetPage == Router::$PAGE_CHANGETAG)
     {
         if (empty($_POST['fromtag']))
         {
-            $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->assign('token',getToken());
             $PAGE->assign('tags', $LINKSDB->allTags());
@@ -1388,9 +1411,8 @@ function renderPage()
     }
 
     // -------- User wants to add a link without using the bookmarklet: Show form.
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=addlink'))
+    if ($targetPage == Router::$PAGE_ADDLINK)
     {
-        $PAGE = new pageBuilder;
         $PAGE->assign('linkcount',count($LINKSDB));
         $PAGE->renderPage('addlink');
         exit;
@@ -1409,6 +1431,9 @@ function renderPage()
         $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));
         if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title.
+
+        $pluginManager->executeHooks('save_link', $link);
+
         $LINKSDB[$linkdate] = $link;
         $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // Save to disk.
         pubsubhub();
@@ -1442,6 +1467,9 @@ function renderPage()
         // - confirmation is handled by JavaScript
         // - we are protected from XSRF by the token.
         $linkdate=$_POST['lf_linkdate'];
+
+        $pluginManager->executeHooks('delete_link', $LINKSDB[$linkdate]);
+
         unset($LINKSDB[$linkdate]);
         $LINKSDB->savedb($GLOBALS['config']['PAGECACHE']); // save to disk
 
@@ -1483,88 +1511,107 @@ function renderPage()
     {
         $link = $LINKSDB[$_GET['edit_link']];  // Read database
         if (!$link) { header('Location: ?'); exit; } // Link not found in database.
-        $PAGE = new pageBuilder;
-        $PAGE->assign('linkcount',count($LINKSDB));
-        $PAGE->assign('link',$link);
-        $PAGE->assign('link_is_new',false);
-        $PAGE->assign('token',getToken()); // XSRF protection.
-        $PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''));
-        $PAGE->assign('tags', $LINKSDB->allTags());
+        $data = array(
+            'linkcount' => count($LINKSDB),
+            'link' => $link,
+            'link_is_new' => false,
+            'token' => getToken(),
+            'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
+            'tags' => $LINKSDB->allTags(),
+        );
+        $pluginManager->executeHooks('render_editlink', $data);
+
+        foreach ($data as $key => $value) {
+            $PAGE->assign($key, $value);
+        }
+
         $PAGE->renderPage('editlink');
         exit;
     }
 
     // -------- User want to post a new link: Display link edit form.
     if (isset($_GET['post'])) {
-        $url = new Url($_GET['post']);
-        $url->cleanup();
+        $url = cleanup_url($_GET['post']);
 
         $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)
+        // Check if URL is not already in database (in this case, we will edit the existing link)
+        $link = $LINKSDB->getLinkFromUrl($url);
         if (!$link)
         {
-            $link_is_new = true;  // This is a new link
+            $link_is_new = true;
             $linkdate = strval(date('Ymd_His'));
-            $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
-            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')
-            {
-                list($status,$headers,$data) = getHTTP($url,4); // Short timeout to keep the application responsive.
+            // Get title if it was provided in URL (by the bookmarklet).
+            $title = (empty($_GET['title']) ? '' : $_GET['title'] );
+            // Get description if it was provided in URL (by the bookmarklet). [Bronco added that]
+            $description = (empty($_GET['description']) ? '' : $_GET['description']);
+            $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] );
+            $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0);
+            // If this is an HTTP(S) link, we try go get the page to extract the title (otherwise we will to straight to the edit form.)
+            if (empty($title) && strpos(get_url_scheme($url), 'http') !== false) {
+                // Short timeout to keep the application responsive
+                list($headers, $data) = get_http_url($url, 4);
                 // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) <head> in html
-                if (strpos($status,'200 OK')!==false)
-                                        {
-                        // Look for charset in html header.
-                                               preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
-
-                                               // If found, extract encoding.
-                                               if (!empty($meta[0]))
-                                               {
-                                                       // Get encoding specified in header.
-                                                       preg_match('#charset="?(.*)"#si', $meta[0], $enc);
-                                                       // If charset not found, use utf-8.
-                                                       $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))
-                                               {
-                                                       // Re-encode title in utf-8 if necessary.
-                                                       $title = ($html_charset == 'iso-8859-1') ? utf8_encode($title) : $title;
-                                               }
-                                       }
+                if (strpos($headers[0], '200 OK') !== false) {
+                    // Look for charset in html header.
+                    preg_match('#<meta .*charset=.*>#Usi', $data, $meta);
+
+                    // If found, extract encoding.
+                    if (!empty($meta[0])) {
+                        // Get encoding specified in header.
+                        preg_match('#charset="?(.*)"#si', $meta[0], $enc);
+                        // If charset not found, use utf-8.
+                        $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)) {
+                        // Re-encode title in utf-8 if necessary.
+                        $title = ($html_charset == 'iso-8859-1') ? utf8_encode($title) : $title;
+                    }
+                }
             }
-            if ($url=='') // In case of empty URL, this is just a text (with a link that points to itself)
-            {
-                $url='?'.smallHash($linkdate);
-                $title='Note: ';
+            if ($url == '') {
+                $url = '?' . smallHash($linkdate);
+                $title = 'Note: ';
             }
-            $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>$private);
+            $link = array(
+                'linkdate' => $linkdate,
+                'title' => $title,
+                'url' => $url,
+                'description' => $description,
+                'tags' => $tags,
+                'private' => $private
+            );
+        }
+
+        $data = array(
+            'linkcount' => count($LINKSDB),
+            'link' => $link,
+            'link_is_new' => $link_is_new,
+            'token' => getToken(), // XSRF protection.
+            'http_referer' => (isset($_SERVER['HTTP_REFERER']) ? escape($_SERVER['HTTP_REFERER']) : ''),
+            'source' => (isset($_GET['source']) ? $_GET['source'] : ''),
+            'tags' => $LINKSDB->allTags(),
+        );
+        $pluginManager->executeHooks('render_editlink', $data);
+
+        foreach ($data as $key => $value) {
+            $PAGE->assign($key, $value);
         }
 
-        $PAGE = new pageBuilder;
-        $PAGE->assign('linkcount',count($LINKSDB));
-        $PAGE->assign('link',$link);
-        $PAGE->assign('link_is_new',$link_is_new);
-        $PAGE->assign('token',getToken()); // XSRF protection.
-        $PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''));
-        $PAGE->assign('source',(isset($_GET['source']) ? $_GET['source'] : ''));
-        $PAGE->assign('tags', $LINKSDB->allTags());
         $PAGE->renderPage('editlink');
         exit;
     }
 
     // -------- Export as Netscape Bookmarks HTML file.
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=export'))
+    if ($targetPage == Router::$PAGE_EXPORT)
     {
         if (empty($_GET['what']))
         {
-            $PAGE = new pageBuilder;
             $PAGE->assign('linkcount',count($LINKSDB));
             $PAGE->renderPage('export');
             exit;
@@ -1616,9 +1663,8 @@ HTML;
     }
 
     // -------- Show upload/import dialog:
-    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=import'))
+    if ($targetPage == Router::$PAGE_IMPORT)
     {
-        $PAGE = new pageBuilder;
         $PAGE->assign('linkcount',count($LINKSDB));
         $PAGE->assign('token',getToken());
         $PAGE->assign('maxfilesize',getMaxFileSize());
@@ -1627,9 +1673,7 @@ HTML;
     }
 
     // -------- Otherwise, simply display search form and links:
-    $PAGE = new pageBuilder;
-    buildLinkList($PAGE,$LINKSDB); // Compute list of links to display
-    $PAGE->renderPage('linklist');
+    showLinkList($PAGE, $LINKSDB);
     exit;
 }
 
@@ -1800,10 +1844,10 @@ function buildLinkList($PAGE,$LINKSDB)
         $taglist = explode(' ',$link['tags']);
         uasort($taglist, 'strcasecmp');
         $link['taglist']=$taglist;
-
+        $link['shorturl'] = smallHash($link['linkdate']);
         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"];
+            $link["url"] = index_url($_SERVER) . $link["url"];
         }
 
         $linkDisp[$keys[$i]] = $link;
@@ -1820,18 +1864,28 @@ function buildLinkList($PAGE,$LINKSDB)
     $token = ''; if (isLoggedIn()) $token=getToken();
 
     // Fill all template fields.
-    $PAGE->assign('linkcount',count($LINKSDB));
-    $PAGE->assign('previous_page_url',$previous_page_url);
-    $PAGE->assign('next_page_url',$next_page_url);
-    $PAGE->assign('page_current',$page);
-    $PAGE->assign('page_max',$pagecount);
-    $PAGE->assign('result_count',count($linksToDisplay));
-    $PAGE->assign('search_type',$search_type);
-    $PAGE->assign('search_crits',$search_crits);
-    $PAGE->assign('redirector',empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']); // Optional redirector URL.
-    $PAGE->assign('token',$token);
-    $PAGE->assign('links',$linkDisp);
-    $PAGE->assign('tags', $LINKSDB->allTags());
+    $data = array(
+        'linkcount' => count($LINKSDB),
+        'previous_page_url' => $previous_page_url,
+        'next_page_url' => $next_page_url,
+        'page_current' => $page,
+        'page_max' => $pagecount,
+        'result_count' => count($linksToDisplay),
+        'search_type' => $search_type,
+        'search_crits' => $search_crits,
+        'redirector' => empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'],  // Optional redirector URL.
+        'token' => $token,
+        'links' => $linkDisp,
+        'tags' => $LINKSDB->allTags(),
+    );
+
+    $pluginManager = PluginManager::getInstance();
+    $pluginManager->executeHooks('render_linklist', $data, array('loggedin' => isLoggedIn()));
+
+    foreach ($data as $key => $value) {
+        $PAGE->assign($key, $value);
+    }
+
     return;
 }
 
@@ -1938,7 +1992,7 @@ function computeThumbnail($url,$href=false)
             if ("/talks/" !== substr($path,0,7)) return array(); // This is not a single video URL.
         }
         $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
-        return array('src'=>indexUrl().'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
+        return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
                      'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
     }
 
@@ -1949,7 +2003,7 @@ function computeThumbnail($url,$href=false)
     if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
     {
         $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
-        return array('src'=>indexUrl().'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
+        return array('src'=>index_url($_SERVER).'?do=genthumbnail&hmac='.$sign.'&url='.urlencode($url),
                      'href'=>$href,'width'=>'120','style'=>'height:auto;','alt'=>'thumbnail');
     }
     return array(); // No thumbnail.
@@ -2035,11 +2089,11 @@ function install()
     if (!isset($_SESSION['session_tested']))
     {   // Step 1 : Try to store data in session and reload page.
         $_SESSION['session_tested'] = 'Working';  // Try to set a variable in session.
-        header('Location: '.indexUrl().'?test_session');  // Redirect to check stored data.
+        header('Location: '.index_url($_SERVER).'?test_session');  // Redirect to check stored data.
     }
     if (isset($_GET['test_session']))
     {   // Step 3: Sessions are OK. Remove test parameter from URL.
-        header('Location: '.indexUrl());
+        header('Location: '.index_url($_SERVER));
     }
 
 
@@ -2056,7 +2110,7 @@ function install()
         $GLOBALS['login'] = $_POST['setlogin'];
         $GLOBALS['salt'] = sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless.
         $GLOBALS['hash'] = sha1($_POST['setpassword'].$GLOBALS['login'].$GLOBALS['salt']);
-        $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(indexUrl()) : $_POST['title'] );
+        $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.escape(index_url($_SERVER)) : $_POST['title'] );
         $GLOBALS['config']['ENABLE_UPDATECHECK'] = !empty($_POST['updateCheck']);
         try {
             writeConfig($GLOBALS, isLoggedIn());
@@ -2178,8 +2232,9 @@ function genThumbnail()
         }
         else // This is a flickr page (html)
         {
-            list($httpstatus,$headers,$data) = getHTTP($url,20); // Get the flickr html page.
-            if (strpos($httpstatus,'200 OK')!==false)
+            // Get the flickr html page.
+            list($headers, $data) = get_http_url($url, 20);
+            if (strpos($headers[0], '200 OK') !== false)
             {
                 // flickr now nicely provides the URL of the thumbnail in each flickr page.
                 preg_match('!<link rel=\"image_src\" href=\"(.+?)\"!',$data,$matches);
@@ -2198,9 +2253,9 @@ function genThumbnail()
 
         if ($imageurl!='')
         {   // Let's download the image.
-            list($httpstatus,$headers,$data) = getHTTP($imageurl,10); // Image is 240x120, so 10 seconds to download should be enough.
-            if (strpos($httpstatus,'200 OK')!==false)
-            {
+            // Image is 240x120, so 10 seconds to download should be enough.
+            list($headers, $data) = get_http_url($imageurl, 10);
+            if (strpos($headers[0], '200 OK') !== false) {
                 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname,$data); // Save image to cache.
                 header('Content-Type: image/jpeg');
                 echo $data;
@@ -2214,15 +2269,13 @@ 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('https://vimeo.com/api/v2/video/'.escape($vid).'.php',5);
-        if (strpos($httpstatus,'200 OK')!==false)
-        {
+        list($headers, $data) = get_http_url('https://vimeo.com/api/v2/video/'.escape($vid).'.php', 5);
+        if (strpos($headers[0], '200 OK') !== false) {
             $t = unserialize($data);
             $imageurl = $t[0]['thumbnail_medium'];
             // Then we download the image and serve it to our client.
-            list($httpstatus,$headers,$data) = getHTTP($imageurl,10);
-            if (strpos($httpstatus,'200 OK')!==false)
-            {
+            list($headers, $data) = get_http_url($imageurl, 10);
+            if (strpos($headers[0], '200 OK') !== false) {
                 file_put_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname,$data); // Save image to cache.
                 header('Content-Type: image/jpeg');
                 echo $data;
@@ -2236,17 +2289,16 @@ function genThumbnail()
         // The thumbnail for TED talks is located in the <link rel="image_src" [...]> tag on that page
         // http://www.ted.com/talks/mikko_hypponen_fighting_viruses_defending_the_net.html
         // <link rel="image_src" href="http://images.ted.com/images/ted/28bced335898ba54d4441809c5b1112ffaf36781_389x292.jpg" />
-        list($httpstatus,$headers,$data) = getHTTP($url,5);
-        if (strpos($httpstatus,'200 OK')!==false)
-        {
+        list($headers, $data) = get_http_url($url, 5);
+        if (strpos($headers[0], '200 OK') !== false) {
             // Extract the link to the thumbnail
             preg_match('!link rel="image_src" href="(http://images.ted.com/images/ted/.+_\d+x\d+\.jpg)"!',$data,$matches);
             if (!empty($matches[1]))
             {   // Let's download the image.
                 $imageurl=$matches[1];
-                list($httpstatus,$headers,$data) = getHTTP($imageurl,20); // No control on image size, so wait long enough.
-                if (strpos($httpstatus,'200 OK')!==false)
-                {
+                // No control on image size, so wait long enough
+                list($headers, $data) = get_http_url($imageurl, 20);
+                if (strpos($headers[0], '200 OK') !== false) {
                     $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname;
                     file_put_contents($filepath,$data); // Save image to cache.
                     if (resizeImage($filepath))
@@ -2265,17 +2317,16 @@ function genThumbnail()
         // There is no thumbnail available for xkcd comics, so download the whole image and resize it.
         // http://xkcd.com/327/
         // <img src="http://imgs.xkcd.com/comics/exploits_of_a_mom.png" title="<BLABLA>" alt="<BLABLA>" />
-        list($httpstatus,$headers,$data) = getHTTP($url,5);
-        if (strpos($httpstatus,'200 OK')!==false)
-        {
+        list($headers, $data) = get_http_url($url, 5);
+        if (strpos($headers[0], '200 OK') !== false) {
             // Extract the link to the thumbnail
             preg_match('!<img src="(http://imgs.xkcd.com/comics/.*)" title="[^s]!',$data,$matches);
             if (!empty($matches[1]))
             {   // Let's download the image.
                 $imageurl=$matches[1];
-                list($httpstatus,$headers,$data) = getHTTP($imageurl,20); // No control on image size, so wait long enough.
-                if (strpos($httpstatus,'200 OK')!==false)
-                {
+                // No control on image size, so wait long enough
+                list($headers, $data) = get_http_url($imageurl, 20);
+                if (strpos($headers[0], '200 OK') !== false) {
                     $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname;
                     file_put_contents($filepath,$data); // Save image to cache.
                     if (resizeImage($filepath))
@@ -2292,9 +2343,9 @@ function genThumbnail()
     else
     {
         // For all other domains, we try to download the image and make a thumbnail.
-        list($httpstatus,$headers,$data) = getHTTP($url,30);  // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
-        if (strpos($httpstatus,'200 OK')!==false)
-        {
+        // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
+        list($headers, $data) = get_http_url($url, 30);
+        if (strpos($headers[0], '200 OK') !== false) {
             $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname;
             file_put_contents($filepath,$data); // Save image to cache.
             if (resizeImage($filepath))