]> git.immae.eu Git - github/shaarli/Shaarli.git/blobdiff - index.php
Version 0.0.36 beta:
[github/shaarli/Shaarli.git] / index.php
index e78733bf3e432c000efee23d09f9ade06eb187e6..9dd6483eee9750f81e653cdc4c6edad4be18b487 100644 (file)
--- a/index.php
+++ b/index.php
@@ -1,5 +1,5 @@
 <?php
-// Shaarli 0.0.32 beta - Shaare your links...
+// Shaarli 0.0.36 beta - Shaare your links...
 // The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net
 // http://sebsauvage.net/wiki/doku.php?id=php:shaarli
 // Licence: http://www.opensource.org/licenses/zlib-license.php
@@ -58,7 +58,7 @@ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
 header("Cache-Control: no-store, no-cache, must-revalidate");
 header("Cache-Control: post-check=0, pre-check=0", false);
 header("Pragma: no-cache");
-define('shaarli_version','0.0.32 beta');
+define('shaarli_version','0.0.36 beta');
 if (!is_dir($GLOBALS['config']['DATADIR'])) { mkdir($GLOBALS['config']['DATADIR'],0705); chmod($GLOBALS['config']['DATADIR'],0705); }
 if (!is_file($GLOBALS['config']['DATADIR'].'/.htaccess')) { file_put_contents($GLOBALS['config']['DATADIR'].'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
 if ($GLOBALS['config']['ENABLE_LOCALCACHE'])
@@ -117,6 +117,12 @@ function logm($message)
     file_put_contents($GLOBALS['config']['DATADIR'].'/log.txt',$t,FILE_APPEND);
 }
 
+// Same as nl2br(), but escapes < and >
+function nl2br_escaped($html)
+{
+    return str_replace('>','&gt;',str_replace('<','&lt;',nl2br($html)));
+}
+
 /* Returns the small hash of a string
    eg. smallHash('20111006_131924') --> yZH23w
    Small hashes:
@@ -135,11 +141,12 @@ function smallHash($text)
     return $t;
 }
 
+// In a string, converts urls to clickable links.
 // Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
 function text2clickable($url)
 {
     $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'];
-    return preg_replace('!((?:https?|ftp)://\S+[[:alnum:]]/?)!si','<a href="'.$redir.'$1" rel="nofollow">$1</a>',$url);
+    return preg_replace('!(((?:https?|ftp|file)://|apt:)\S+[[:alnum:]]/?)!si','<a href="'.$redir.'$1" rel="nofollow">$1</a>',$url);
 }
 
 // This function inserts &nbsp; where relevant so that multiple spaces are properly displayed in HTML
@@ -330,13 +337,15 @@ if (isset($_POST['login']))
 // You can append $_SERVER['SCRIPT_NAME'] to get the current script URL.
 function serverUrl()
 {
-        $serverport = ($_SERVER["SERVER_PORT"]=='80' || (!empty($_SERVER['HTTPS']) && $_SERVER["SERVER_PORT"]=='443') ? '' : ':'.$_SERVER["SERVER_PORT"]);
-        return 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.$_SERVER["SERVER_NAME"].$serverport;
+        $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.
 function indexUrl()
 {
-        return serverUrl() . ($_SERVER["SCRIPT_NAME"] == '/index.php' ? '' : $_SERVER["SCRIPT_NAME"]);
+        return serverUrl() . ($_SERVER["SCRIPT_NAME"] == '/index.php' ? '/' : $_SERVER["SCRIPT_NAME"]);
 }
 
 // Convert post_max_size/upload_max_filesize (eg.'16M') parameters to bytes.
@@ -515,11 +524,12 @@ class pageBuilder
         $this->tpl->assign('linkcount',count($LINKSDB));
         $this->tpl->assign('feedurl',htmlspecialchars(indexUrl()));
         $searchcrits=''; // Search criteria
-        if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.$_GET['searchtags'];
-        elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.$_GET['searchterm'];
+        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');
         if (!empty($GLOBALS['title'])) $this->tpl->assign('pagetitle',$GLOBALS['title']);
         if (!empty($GLOBALS['pagetitle'])) $this->tpl->assign('pagetitle',$GLOBALS['pagetitle']);
@@ -551,6 +561,14 @@ class pageBuilder
       echo $mylinks['20110826_161819']['title'];
       foreach($mylinks as $link)
          echo $link['title'].' at url '.$link['url'].' ; description:'.$link['description'];
+   
+   Available keys:
+       title : Title of the link
+       url : URL of the link. Can be absolute or relative. Relative URLs are permalinks (eg.'?m-ukcw')
+       description : description of the entry
+       private : Is this link private ? 0=no, other value=yes
+       linkdate : date of the creation of this entry, in the form YYYYMMDD_HHMMSS (eg.'20110914_192317')
+       tags : tags attached to this entry (separated by spaces)                   
 
    We implement 3 interfaces:
      - ArrayAccess so that this object behaves like an associative array.
@@ -688,6 +706,19 @@ class linkdb implements Iterator, Countable, ArrayAccess
         return $filtered;
     }
 
+    // Filter by day. Day must be in the form 'YYYYMMDD' (eg. '20120125')
+    // Sort order is: older articles first.
+    // eg. print_r($mydb->filterDay('20120125'));
+    public function filterDay($day)
+    {
+        $filtered=array();
+        foreach($this->links as $l)
+        {
+            if (startsWith($l['linkdate'],$day)) $filtered[$l['linkdate']] = $l;
+        }
+        ksort($filtered);
+        return $filtered;
+    }
     // Filter by smallHash.
     // Only 1 article is returned.
     public function filterSmallHash($smallHash)
@@ -791,7 +822,7 @@ function showATOM()
         if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl;  // make permalink URL absolute
         $entries.='<entry><title>'.htmlspecialchars($link['title']).'</title><link href="'.$absurl.'" /><id>'.$guid.'</id>';
         if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $entries.='<updated>'.htmlspecialchars($iso8601date).'</updated>';
-        $entries.='<summary>'.nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))))."</summary>\n";
+        $entries.='<content type="html">'.htmlspecialchars(nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description'])))))."</content>\n";
         if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification)
         {
             foreach(explode(' ',$link['tags']) as $tag)
@@ -803,14 +834,14 @@ function showATOM()
     $feed='<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom">';
     $feed.='<title>'.htmlspecialchars($GLOBALS['title']).'</title>';
     if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $feed.='<updated>'.htmlspecialchars($latestDate).'</updated>';
-    $feed.='<link rel="self" href="'.htmlspecialchars($pageaddr).'" />';
+    $feed.='<link rel="self" href="'.htmlspecialchars(serverUrl().$_SERVER["REQUEST_URI"]).'" />';
     if (!empty($GLOBALS['config']['PUBSUBHUB_URL']))
     {
         $feed.='<!-- PubSubHubbub Discovery -->';
         $feed.='<link rel="hub" href="'.htmlspecialchars($GLOBALS['config']['PUBSUBHUB_URL']).'" />';
         $feed.='<!-- End Of PubSubHubbub Discovery -->';
     }
-    $feed.='<author><uri>'.htmlspecialchars($pageaddr).'</uri></author>';
+    $feed.='<author><name>'.htmlspecialchars($pageaddr).'</name><uri>'.htmlspecialchars($pageaddr).'</uri></author>';
     $feed.='<id>'.htmlspecialchars($pageaddr).'</id>'."\n\n"; // Yes, I know I should use a real IRI (RFC3987), but the site URL will do.
     $feed.=$entries;
     $feed.='</feed>';
@@ -928,7 +959,57 @@ function renderPage()
         header('Location: '.(empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER']));
         exit;
     }
-
+    
+    // --------- Daily (all links form a specific day) ----------------------
+    if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=daily'))
+    { 
+        $day=Date('Ymd',strtotime('-1 day')); // Yesterday, in format YYYYMMDD.
+        if (isset($_GET['day'])) $day=$_GET['day'];
+
+        $previousday = Date('Ymd',strtotime('-1 day',strtotime($day))); 
+        $nextday = Date('Ymd',strtotime('+1 day',strtotime($day))); 
+        
+        $linksToDisplay=$LINKSDB->filterDay($day);
+        // We pre-format some fields for proper output.
+        foreach($linksToDisplay as $key=>$link)
+        {
+            $linksToDisplay[$key]['taglist']=explode(' ',$link['tags']);
+            $linksToDisplay[$key]['formatedDescription']=nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))));
+            $linksToDisplay[$key]['thumbnail'] = thumbnail($link['url']);            
+        }
+        
+        /* We need to spread the articles on 3 columns.
+           I did not want to use a javascript lib like http://masonry.desandro.com/
+           so I manually spread entries with a simple method: I roughly evaluate the 
+           height of a div according to title and description length.
+        */
+        $columns=array(array(),array(),array()); // Entries to display, for each column.
+        $fill=array(0,0,0);  // Rough estimate of columns fill.
+        foreach($linksToDisplay as $key=>$link)
+        {
+            // Roughly estimate length of entry (by counting characters)
+            // Title: 30 chars = 1 line. 1 line is 30 pixels height.
+            // Description: 836 characters gives roughly 342 pixel height.
+            // This is not perfect, but it's usually ok.
+            $length=strlen($link['title'])+(342*strlen($link['description']))/836;
+            if ($link['thumbnail']) $length +=100; // 1 thumbnails roughly takes 100 pixels height.
+            // Then put in column which is the less filled:
+            $smallest=min($fill); // find smallest value in array.
+            $index=array_search($smallest,$fill); // find index of this smallest value.
+            array_push($columns[$index],$link); // Put entry in this column.
+            $fill[$index]+=$length;
+        }
+        $PAGE = new pageBuilder;
+           $PAGE->assign('linksToDisplay',$linksToDisplay);
+        $PAGE->assign('col1',$columns[0]);
+        $PAGE->assign('col2',$columns[1]);
+        $PAGE->assign('col3',$columns[2]);
+        $PAGE->assign('day',utf8_encode(strftime('%A %d, %B %Y',linkdate2timestamp($day.'_000000'))));
+        $PAGE->assign('previousday',$previousday);
+        $PAGE->assign('nextday',$nextday);    
+        $PAGE->renderPage('daily');
+        exit;
+    }
 
     // -------- Handle other actions allowed for non-logged in users:
     if (!isLoggedIn())
@@ -1418,6 +1499,7 @@ function buildLinkList($PAGE)
 // Understands various services (youtube.com...)
 // Input: $url = url for which the thumbnail must be found.
 //        $href = if provided, this URL will be followed instead of $url
+// Returns '' if no thumbnail available.
 function thumbnail($url,$href=false)
 {
     if (!$GLOBALS['config']['ENABLE_THUMBNAILS']) return '';
@@ -1431,32 +1513,38 @@ function thumbnail($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 '<a href="'.htmlspecialchars($href).'"><img src="http://img.youtube.com/vi/'.htmlspecialchars($params['v']).'/default.jpg" width="120" height="90"></a>';
+        if (!empty($params['v'])) return '<a href="'.htmlspecialchars($href).'"><img src="http://img.youtube.com/vi/'.htmlspecialchars($params['v']).'/default.jpg" width="120" height="90" alt="YouTube thumbnail"></a>';
     }
     if ($domain=='youtu.be') // Youtube short links
     {
         $path = parse_url($url,PHP_URL_PATH);
-        return '<a href="'.htmlspecialchars($href).'"><img src="http://img.youtube.com/vi'.htmlspecialchars($path).'/default.jpg" width="120" height="90"></a>';
+        return '<a href="'.htmlspecialchars($href).'"><img src="http://img.youtube.com/vi'.htmlspecialchars($path).'/default.jpg" width="120" height="90"  alt="YouTube thumbnail"></a>';
     }
+    if ($domain=='pix.toile-libre.org') // pix.toile-libre.org image hosting
+    {
+        parse_str(parse_url($url,PHP_URL_QUERY), $params); // Extract image filename.
+        if (!empty($params) && !empty($params['img'])) return '<a href="'.htmlspecialchars($href).'"><img src="http://pix.toile-libre.org/upload/thumb/'.urlencode($params['img']).'" style:"max-width:120px; max-height:150px" alt="pix.toile-libre.org thumbnail"></a>';
+    }    
+    
     if ($domain=='imgur.com')
     {
         $path = parse_url($url,PHP_URL_PATH);
         if (startsWith($path,'/a/')) return ''; // Thumbnails for albums are not available.
-        if (startsWith($path,'/r/')) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars(basename($path)).'s.jpg" width="90" height="90"></a>';
-        if (startsWith($path,'/gallery/')) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com'.htmlspecialchars(substr($path,8)).'s.jpg" width="90" height="90"></a>';
-        if (substr_count($path,'/')==1) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars(substr($path,1)).'s.jpg" width="90" height="90"></a>';
+        if (startsWith($path,'/r/')) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars(basename($path)).'s.jpg" width="90" height="90" alt="imgur.com thumbnail"></a>';
+        if (startsWith($path,'/gallery/')) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com'.htmlspecialchars(substr($path,8)).'s.jpg" width="90" height="90" alt="imgur.com thumbnail"></a>';
+        if (substr_count($path,'/')==1) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars(substr($path,1)).'s.jpg" width="90" height="90" alt="imgur.com thumbnail"></a>';
     }
     if ($domain=='i.imgur.com')
     {
         $pi = pathinfo(parse_url($url,PHP_URL_PATH));
-        if (!empty($pi['filename'])) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars($pi['filename']).'s.jpg" width="90" height="90"></a>';
+        if (!empty($pi['filename'])) return '<a href="'.htmlspecialchars($href).'"><img src="http://i.imgur.com/'.htmlspecialchars($pi['filename']).'s.jpg" width="90" height="90" alt="imgur.com thumbnail"></a>';
     }
     if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com')
     {
         if (strpos($url,'dailymotion.com/video/')!==false)
         {
             $thumburl=str_replace('dailymotion.com/video/','dailymotion.com/thumbnail/video/',$url);
-            return '<a href="'.htmlspecialchars($href).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;"></a>';
+            return '<a href="'.htmlspecialchars($href).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;" alt="DailyMotion thumbnail"></a>';
         }
     }
     if (endsWith($domain,'.imageshack.us'))
@@ -1465,7 +1553,7 @@ function thumbnail($url,$href=false)
         if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
         {
             $thumburl = substr($url,0,strlen($url)-strlen($ext)).'th.'.$ext;
-            return '<a href="'.htmlspecialchars($href).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;"></a>';
+            return '<a href="'.htmlspecialchars($href).'"><img src="'.htmlspecialchars($thumburl).'" width="120" style="height:auto;" alt="imageshack.us thumbnail"></a>';
         }
     }
 
@@ -1478,6 +1566,7 @@ function thumbnail($url,$href=false)
     if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')
         || $domain=='vimeo.com'
         || $domain=='ted.com' || endsWith($domain,'.ted.com')
+        || $domain=='xkcd.com' || endsWith($domain,'.xkcd.com')
     )
     {
         if ($domain=='vimeo.com')
@@ -1485,13 +1574,18 @@ function thumbnail($url,$href=false)
             $path = parse_url($url,PHP_URL_PATH);
             if (!preg_match('!/\d+.+?!',$path)) return ''; // This is not a single video URL.
         }
+        if ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
+        {   // Make sure this url points to a single comic (/xxx... where xxx is numeric)
+            $path = parse_url($url,PHP_URL_PATH);
+            if (!preg_match('!/\d+.+?!',$path)) return '';
+        }
         if ($domain=='ted.com' || endsWith($domain,'.ted.com'))
         {   // Make sure this TED url points to a video (/talks/...)
             $path = parse_url($url,PHP_URL_PATH);
             if ("/talks/" !== substr($path,0,7)) return ''; // 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 '<a href="'.htmlspecialchars($href).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;"></a>';
+        return '<a href="'.htmlspecialchars($href).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;" alt="thumbnail"></a>';
     }
 
     // For all other, we try to make a thumbnail of links ending with .jpg/jpeg/png/gif
@@ -1790,6 +1884,35 @@ function genThumbnail()
             }
         }
     }
+    
+    elseif ($domain=='xkcd.com' || endsWith($domain,'.xkcd.com'))
+    {
+        // 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)
+        {
+            // 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)
+                {
+                    $filepath=$GLOBALS['config']['CACHEDIR'].'/'.$thumbname;
+                    file_put_contents($filepath,$data); // Save image to cache.
+                    if (resizeImage($filepath))
+                    {
+                        header('Content-Type: image/jpeg');
+                        echo file_get_contents($filepath);
+                        return;
+                    }
+                }
+            }
+        }
+    }    
 
     else
     {