From: Seb Sauvage Date: Fri, 16 Dec 2011 20:27:16 +0000 (+0100) Subject: Version 0.0.32 beta: X-Git-Url: https://git.immae.eu/?p=github%2Fshaarli%2FShaarli.git;a=commitdiff_plain;h=3433e5e8a84e3952502b79296f945e6dde2a7d75 Version 0.0.32 beta: - Changed: HTML generation moved to RainTPL templates (in the tpl/ directory). - Added: Better check on URL parameters (patch by gege2061). - Changed: Better detection of HTTPS (patch by gege2061). - Changed: In RSS/ATOM feeds, the GUID is now the permalink instead of the final URL (patch by gege2061). - Changed: Jerrywham CSS patch included. - Changed: Multiple spaces are now respected in description. Thus you can use Shaarli as a personal pastebin (for posting source code, for example). I also added a max-height and overflow:auto so that content can be scrolled if too large. - Corrected: Tab order changed in login screen. - Corrected: Permalinks now work even if additional parameters have been added (eg. /?E8Yj2Q&utm_source=blablabla…) - Corrected: user.css is included only if the file is present (This prevent a useless CSS include which makes a harmless but useless 404 error.). - Removed: Page time generation was removed. --- diff --git a/jquery-ui.custom.min.js b/inc/jquery-ui.custom.min.js similarity index 100% rename from jquery-ui.custom.min.js rename to inc/jquery-ui.custom.min.js diff --git a/jquery.min.js b/inc/jquery.min.js similarity index 100% rename from jquery.min.js rename to inc/jquery.min.js diff --git a/inc/rain.tpl.class.php b/inc/rain.tpl.class.php new file mode 100644 index 00000000..30b6deb8 --- /dev/null +++ b/inc/rain.tpl.class.php @@ -0,0 +1,1011 @@ +), stylesheet (), script ('; // Redirect to login screen. - exit; + exit; } -} +} // ------------------------------------------------------------------------------------------ // Misc utility functions: @@ -318,15 +330,20 @@ if (isset($_POST['login'])) // You can append $_SERVER['SCRIPT_NAME'] to get the current script URL. function serverUrl() { - $serverport = ($_SERVER["SERVER_PORT"]!='80' ? ':'.$_SERVER["SERVER_PORT"] : ''); + $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; } +function indexUrl() +{ + return serverUrl() . ($_SERVER["SCRIPT_NAME"] == '/index.php' ? '' : $_SERVER["SCRIPT_NAME"]); +} + // Convert post_max_size/upload_max_filesize (eg.'16M') parameters to bytes. -function return_bytes($val) +function return_bytes($val) { $val = trim($val); $last=strtolower($val[strlen($val)-1]); - switch($last) + switch($last) { case 'g': $val *= 1024; case 'm': $val *= 1024; @@ -396,7 +413,7 @@ function linkdate2locale($linkdate) } // Parse HTTP response headers and return an associative array. -function http_parse_headers_shaarli( $headers ) +function http_parse_headers_shaarli( $headers ) { $res=array(); foreach($headers as $header) @@ -444,9 +461,9 @@ function getHTTP($url,$timeout=30) // Extract title from an HTML document. // (Returns an empty string if not found.) -function html_extract_title($html) +function html_extract_title($html) { - return preg_match('!(.*?)!is', $html, $matches) ? trim(str_replace("\n",' ', $matches[1])) : '' ; + return preg_match('!(.*?)!is', $html, $matches) ? trim(str_replace("\n",' ', $matches[1])) : '' ; } // ------------------------------------------------------------------------------------------ @@ -459,11 +476,11 @@ function getToken() { $rnd = sha1(uniqid('',true).'_'.mt_rand()); // We generate a random string. $_SESSION['tokens'][$rnd]=1; // Store it on the server side. - return $rnd; + return $rnd; } // Tells if a token is ok. Using this function will destroy the token. -// true=token is ok. +// true=token is ok. function tokenOk($token) { if (isset($_SESSION['tokens'][$token])) @@ -474,6 +491,58 @@ function tokenOk($token) return false; // Wrong token, or already used. } +// ------------------------------------------------------------------------------------------ +/* This class is in charge of building the final page. + (This is basically a wrapper around RainTPL which pre-fills some fields.) + p = new pageBuilder; + p.assign('myfield','myvalue'); + p.renderPage('mytemplate'); + +*/ +class pageBuilder +{ + private $tpl; // RainTPL template + function __construct() + { + $this->tpl=false; + } + + private function initialize() + { + global $LINKSDB; + $this->tpl = new RainTPL; + $this->tpl->assign('newversion',checkUpdate()); + $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']; + $this->tpl->assign('searchcrits',$searchcrits); + $this->tpl->assign('source',indexUrl()); + $this->tpl->assign('version',shaarli_version); + $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']); + $this->tpl->assign('shaarlititle',empty($GLOBALS['title']) ? 'Shaarli': $GLOBALS['title'] ); + return; + } + + // The following assign() method is basically the same as RainTPL (except that it's lazy) + public function assign($what,$where) + { + if ($this->tpl===false) $this->initialize(); // Lazy initialization + $this->tpl->assign($what,$where); + } + + // Render a specific page (using a template). + // eg. pb.renderPage('picwall') + public function renderPage($page) + { + if ($this->tpl===false) $this->initialize(); // Lazy initialization + $this->tpl->draw($page); + } +} + // ------------------------------------------------------------------------------------------ /* Data storage for links. This object behaves like an associative array. @@ -482,14 +551,13 @@ function tokenOk($token) echo $mylinks['20110826_161819']['title']; foreach($mylinks as $link) echo $link['title'].' at url '.$link['url'].' ; description:'.$link['description']; - + We implement 3 interfaces: - ArrayAccess so that this object behaves like an associative array. - Iterator so that this object can be used in foreach() loops. - Countable interface so that we can do a count() on this object. */ class linkdb implements Iterator, Countable, ArrayAccess - { private $links; // List of links (associative array. Key=linkdate (eg. "20110823_124546"), value= associative array (keys:title,description...) private $urls; // List of all recorded URLs (key=url, value=linkdate) for fast reserve search (url-->linkdate) @@ -504,8 +572,8 @@ class linkdb implements Iterator, Countable, ArrayAccess $this->loggedin = $isLoggedIn; $this->checkdb(); // Make sure data file exists. $this->readdb(); // Then read it. - } - + } + // ---- Countable interface implementation public function count() { return count($this->links); } @@ -516,17 +584,17 @@ class linkdb implements Iterator, Countable, ArrayAccess if (empty($value['linkdate']) || empty($value['url'])) die('Internal Error: A link should always have a linkdate and url.'); if (empty($offset)) die('You must specify a key.'); $this->links[$offset] = $value; - $this->urls[$value['url']]=$offset; + $this->urls[$value['url']]=$offset; } public function offsetExists($offset) { return array_key_exists($offset,$this->links); } public function offsetUnset($offset) - { + { if (!$this->loggedin) die('You are not authorized to delete a link.'); - $url = $this->links[$offset]['url']; unset($this->urls[$url]); - unset($this->links[$offset]); + $url = $this->links[$offset]['url']; unset($this->urls[$url]); + unset($this->links[$offset]); } public function offsetGet($offset) { return isset($this->links[$offset]) ? $this->links[$offset] : null; } - + // ---- Iterator interface implementation function rewind() { $this->keys=array_keys($this->links); rsort($this->keys); $this->position=0; } // Start over for iteration, ordered by date (latest first). function key() { return $this->keys[$this->position]; } // current key @@ -543,18 +611,18 @@ class linkdb implements Iterator, Countable, ArrayAccess $link = array('title'=>'Shaarli - sebsauvage.net','url'=>'http://sebsauvage.net/wiki/doku.php?id=php:shaarli','description'=>'Welcome to Shaarli ! This is a bookmark. To edit or delete me, you must first login.','private'=>0,'linkdate'=>'20110914_190000','tags'=>'opensource software'); $this->links[$link['linkdate']] = $link; $link = array('title'=>'My secret stuff... - Pastebin.com','url'=>'http://pastebin.com/smCEEeSn','description'=>'SShhhh!! I\'m a private link only YOU can see. You can delete me too.','private'=>1,'linkdate'=>'20110914_074522','tags'=>'secretstuff'); - $this->links[$link['linkdate']] = $link; + $this->links[$link['linkdate']] = $link; file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); // Write database to disk - } + } } - + // Read database from disk to memory - private function readdb() + private function readdb() { // Read data $this->links=(file_exists($GLOBALS['config']['DATASTORE']) ? unserialize(gzinflate(base64_decode(substr(file_get_contents($GLOBALS['config']['DATASTORE']),strlen(PHPPREFIX),-strlen(PHPSUFFIX))))) : array() ); // Note that gzinflate is faster than gzuncompress. See: http://www.php.net/manual/en/function.gzdeflate.php#96439 - + // If user is not logged in, filter private links. if (!$this->loggedin) { @@ -562,21 +630,21 @@ class linkdb implements Iterator, Countable, ArrayAccess foreach($this->links as $link) { if ($link['private']!=0) $toremove[]=$link['linkdate']; } foreach($toremove as $linkdate) { unset($this->links[$linkdate]); } } - + // Keep the list of the mapping URLs-->linkdate up-to-date. $this->urls=array(); foreach($this->links as $link) { $this->urls[$link['url']]=$link['linkdate']; } } - // Save database from memory to disk. - public function savedb() + // Save database from memory to disk. + public function savedb() { if (!$this->loggedin) die('You are not authorized to change the database.'); file_put_contents($GLOBALS['config']['DATASTORE'], PHPPREFIX.base64_encode(gzdeflate(serialize($this->links))).PHPSUFFIX); } - + // Returns the link for a given URL (if it exists). false it does not exist. - public function getLinkFromUrl($url) + public function getLinkFromUrl($url) { if (isset($this->urls[$url])) return $this->links[$this->urls[$url]]; return false; @@ -584,14 +652,14 @@ class linkdb implements Iterator, Countable, ArrayAccess // Case insentitive search among links (in url, title and description). Returns filtered list of links. // eg. print_r($mydb->filterFulltext('hollandais')); - public function filterFulltext($searchterms) + public function filterFulltext($searchterms) { // FIXME: explode(' ',$searchterms) and perform a AND search. // FIXME: accept double-quotes to search for a string "as is" ? $filtered=array(); $s = strtolower($searchterms); foreach($this->links as $l) - { + { $found= (strpos(strtolower($l['title']),$s)!==false) || (strpos(strtolower($l['description']),$s)!==false) || (strpos(strtolower($l['url']),$s)!==false) @@ -601,7 +669,7 @@ class linkdb implements Iterator, Countable, ArrayAccess krsort($filtered); return $filtered; } - + // Filter by tag. // You can specify one or more tags (tags can be separated by space or comma). // eg. print_r($mydb->filterTags('linux programming')); @@ -611,22 +679,22 @@ class linkdb implements Iterator, Countable, ArrayAccess $searchtags=explode(' ',$t); $filtered=array(); foreach($this->links as $l) - { + { $linktags = explode(' ',($casesensitive?$l['tags']:strtolower($l['tags']))); if (count(array_intersect($linktags,$searchtags)) == count($searchtags)) $filtered[$l['linkdate']] = $l; } krsort($filtered); return $filtered; - } - + } + // Filter by smallHash. // Only 1 article is returned. public function filterSmallHash($smallHash) { $filtered=array(); foreach($this->links as $l) - { + { if ($smallHash==smallHash($l['linkdate'])) // Yes, this is ugly and slow { $filtered[$l['linkdate']] = $l; @@ -634,7 +702,7 @@ class linkdb implements Iterator, Countable, ArrayAccess } } return $filtered; - } + } // Returns the list of all tags // Output: associative array key=tags, value=0 @@ -646,7 +714,7 @@ class linkdb implements Iterator, Countable, ArrayAccess if (!empty($tag)) $tags[$tag]=(empty($tags[$tag]) ? 1 : $tags[$tag]+1); arsort($tags); // Sort tags by usage (most used tag first) return $tags; - } + } } // ------------------------------------------------------------------------------------------ @@ -654,15 +722,15 @@ class linkdb implements Iterator, Countable, ArrayAccess function showRSS() { global $LINKSDB; - + // Optionnaly filter the results: $linksToDisplay=array(); if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); else $linksToDisplay = $LINKSDB; - + header('Content-Type: application/rss+xml; charset=utf-8'); - $pageaddr=htmlspecialchars(serverUrl().$_SERVER["SCRIPT_NAME"]); + $pageaddr=htmlspecialchars(indexUrl()); echo ''; echo ''.htmlspecialchars($GLOBALS['title']).''.$pageaddr.''; echo 'Shared linksen-en'.$pageaddr.''."\n\n"; @@ -678,16 +746,17 @@ function showRSS() while ($i<50 && $i'.htmlspecialchars($link['title']).''.$absurl.''.$absurl.''; + echo ''.htmlspecialchars($link['title']).''.$guid.''.$absurl.''; if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) echo ''.htmlspecialchars($rfc822date)."\n"; if ($link['tags']!='') // Adding tags to each RSS entry (as mentioned in RSS specification) - { + { foreach(explode(' ',$link['tags']) as $tag) { echo ''.htmlspecialchars($tag).''."\n"; } - } - echo ''."\n\n"; + } + echo ''."\n\n"; $i++; } echo ''; @@ -699,15 +768,15 @@ function showRSS() function showATOM() { global $LINKSDB; - + // Optionnaly filter the results: $linksToDisplay=array(); if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); else $linksToDisplay = $LINKSDB; - + header('Content-Type: application/atom+xml; charset=utf-8'); - $pageaddr=htmlspecialchars(serverUrl().$_SERVER["SCRIPT_NAME"]); + $pageaddr=htmlspecialchars(indexUrl()); $latestDate = ''; $entries=''; $i=0; @@ -715,19 +784,20 @@ function showATOM() while ($i<50 && $i'.$absurl.''; + if (startsWith($absurl,'?')) $absurl=$pageaddr.$absurl; // make permalink URL absolute + $entries.=''.htmlspecialchars($link['title']).''.$guid.''; if (!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()) $entries.=''.htmlspecialchars($iso8601date).''; - $entries.=''.nl2br(text2clickable(htmlspecialchars($link['description'])))."\n"; + $entries.=''.nl2br(keepMultipleSpaces(text2clickable(htmlspecialchars($link['description']))))."\n"; if ($link['tags']!='') // Adding tags to each ATOM entry (as mentioned in ATOM specification) - { + { foreach(explode(' ',$link['tags']) as $tag) { $entries.=''."\n"; } - } - $entries.="\n"; + } + $entries.="\n"; $i++; } $feed=''; @@ -749,92 +819,79 @@ function showATOM() } // ------------------------------------------------------------------------------------------ -// Render HTML page: +// Render HTML page (according to URL parameters and user rights) function renderPage() { - global $STARTTIME; global $LINKSDB; - - // Well... rendering the page would be 100x better with the excellent Smarty, but I don't want to tie this minimalist project to 100+ files. - // So I use a custom templating system. - + // -------- Display login form. - if (startswith($_SERVER["QUERY_STRING"],'do=login')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=login')) { if ($GLOBALS['config']['OPEN_SHAARLI']) { header('Location: ?'); exit; } // No need to login for open Shaarli - if (!ban_canLogin()) - { - $loginform='
You have been banned from login after too many failed attempts. Try later.
'; - $data = array('pageheader'=>$loginform,'body'=>'','onload'=>''); - templatePage($data); - exit; - } - $returnurl_html = (isset($_SERVER['HTTP_REFERER']) ? '' : ''); - $loginform='
Login:    Password :
'; - $loginform.=''.$returnurl_html.'
'; - $onload = 'onload="document.loginform.login.focus();"'; - $data = array('pageheader'=>$loginform,'body'=>'','onload'=>$onload); - templatePage($data); + $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']) ? $_SERVER['HTTP_REFERER']:'')); + $PAGE->renderPage('loginform'); exit; } // -------- User wants to logout. - if (startswith($_SERVER["QUERY_STRING"],'do=logout')) - { - invalidateCaches(); - logout(); - header('Location: ?'); - exit; - } + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=logout')) + { + invalidateCaches(); + logout(); + header('Location: ?'); + exit; + } // -------- Picture wall - if (startswith($_SERVER["QUERY_STRING"],'do=picwall')) - { + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=picwall')) + { // Optionnaly filter the results: - $linksToDisplay=array(); - if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); - elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); - else $linksToDisplay = $LINKSDB; + $links=array(); + if (!empty($_GET['searchterm'])) $links = $LINKSDB->filterFulltext($_GET['searchterm']); + elseif (!empty($_GET['searchtags'])) $links = $LINKSDB->filterTags(trim($_GET['searchtags'])); + else $links = $LINKSDB; $body=''; - - foreach($linksToDisplay as $link) + $linksToDisplay=array(); + + // Get only links which have a thumbnail. + foreach($links as $link) { - $href='?'.htmlspecialchars(smallhash($link['linkdate']),ENT_QUOTES); - $thumb=thumbnail($link['url'],$href); - if ($thumb!='') + $permalink='?'.htmlspecialchars(smallhash($link['linkdate']),ENT_QUOTES); + $thumb=thumbnail($link['url'],$permalink); + if ($thumb!='') // Only output links which have a thumbnail. { - $body.=''; - + $link['thumbnail']=$thumb; // Thumbnail HTML code. + $link['permalink']=$permalink; + $linksToDisplay[]=$link; // Add to array. } } - $body = '
'.$body.'
'; - $data = array('pageheader'=>'
 ','body'=>$body,'onload'=>''); - templatePage($data); - exit; - - } + $PAGE = new pageBuilder; + $PAGE->assign('linksToDisplay',$linksToDisplay); + $PAGE->renderPage('picwall'); + exit; + } // -------- Tag cloud - if (startswith($_SERVER["QUERY_STRING"],'do=tagcloud')) - { + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tagcloud')) + { $tags= $LINKSDB->allTags(); // We sort tags alphabetically, then choose a font size according to count. // First, find max value. $maxcount=0; foreach($tags as $key=>$value) $maxcount=max($maxcount,$value); ksort($tags); - $cloud=''; + $tagList=array(); foreach($tags as $key=>$value) { - $size = max(40*$value/$maxcount,8); // Minimum size 8. - $colorvalue = 128-ceil(127*$value/$maxcount); - $color='rgb('.$colorvalue.','.$colorvalue.','.$colorvalue.')'; - $cloud.= ''.$value.''.htmlspecialchars($key).' '; + $tagList[$key] = array('count'=>$value,'size'=>max(40*$value/$maxcount,8)); } - $cloud='
'.$cloud.'
'; - $data = array('pageheader'=>'','body'=>$cloud,'onload'=>''); - templatePage($data); - exit; - } - + $PAGE = new pageBuilder; + $PAGE->assign('tags',$tagList); + $PAGE->renderPage('tagcloud'); + exit; + } + // -------- User clicks on a tag in a link: The tag is added to the list of searched tags (searchtags=...) if (isset($_GET['addtag'])) { @@ -862,8 +919,8 @@ function renderPage() } header('Location: ?'.http_build_query($params)); exit; - } - + } + // -------- User wants to change the number of links per page (linksperpage=...) if (isset($_GET['linksperpage'])) { @@ -871,57 +928,37 @@ function renderPage() header('Location: '.(empty($_SERVER['HTTP_REFERER'])?'?':$_SERVER['HTTP_REFERER'])); exit; } - - + + // -------- Handle other actions allowed for non-logged in users: if (!isLoggedIn()) { // User tries to post new link but is not loggedin: // Show login screen, then redirect to ?post=... - if (isset($_GET['post'])) + if (isset($_GET['post'])) { header('Location: ?do=login&post='.urlencode($_GET['post']).(!empty($_GET['title'])?'&title='.urlencode($_GET['title']):'').(!empty($_GET['source'])?'&source='.urlencode($_GET['source']):'')); // Redirect to login page, then back to post link. exit; } - - // Show search form and display list of links. - $searchform=<< -
-
- -HTML; - $data = array('pageheader'=>$searchform,'body'=>templateLinkList(),'onload'=>''); - templatePage($data); - exit; // Never remove this one ! All operations below are reserved for logged in user. + $PAGE = new pageBuilder; + buildLinkList($PAGE); // 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 (startswith($_SERVER["QUERY_STRING"],'do=tools')) - { - $pageabsaddr=serverUrl().$_SERVER["SCRIPT_NAME"]; // Why doesn't php have a built-in function for that ? - // The javascript code for the bookmarklet: - $changepwd = ($GLOBALS['config']['OPEN_SHAARLI'] ? '' : 'Change password : Change your password.

' ); - $toolbar= << - {$changepwd} - Configure your Shaarli : Change Title, timezone...

- Rename/delete tags : Rename or delete a tag in all links

- Import : Import Netscape html bookmarks (as exported from Firefox, Chrome, Opera, delicious...)

- Export : Export Netscape html bookmarks (which can be imported in Firefox, Chrome, Opera, delicious...)

-Shaare link ⇐ Drag this link to your bookmarks toolbar (or right-click it and choose Bookmark This Link....). Then click "Shaare link" button in any page you want to share.

-
- -HTML; - $data = array('pageheader'=>$toolbar,'body'=>'','onload'=>''); - templatePage($data); + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=tools')) + { + $PAGE = new pageBuilder; + $PAGE->assign('pageabsaddr',indexUrl()); + $PAGE->renderPage('tools'); exit; } // -------- User wants to change his/her password. - if (startswith($_SERVER["QUERY_STRING"],'do=changepasswd')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changepasswd')) { 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'])) @@ -938,32 +975,25 @@ HTML; echo ''; exit; } - else + else // show the change password form. { - $token = getToken(); - $changepwdform= << -Old password:     -New password: - - -HTML; - $data = array('pageheader'=>$changepwdform,'body'=>'','onload'=>'onload="document.changepasswordform.oldpassword.focus();"'); - templatePage($data); - exit; + $PAGE = new pageBuilder; + $PAGE->assign('token',getToken()); + $PAGE->renderPage('changepassword'); + exit; } } - + // -------- User wants to change configuration - if (startswith($_SERVER["QUERY_STRING"],'do=configure')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=configure')) { if (!empty($_POST['title']) ) { - if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! + if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! $tz = 'UTC'; if (!empty($_POST['continent']) && !empty($_POST['city'])) if (isTZvalid($_POST['continent'],$_POST['city'])) - $tz = $_POST['continent'].'/'.$_POST['city']; + $tz = $_POST['continent'].'/'.$_POST['city']; $GLOBALS['timezone'] = $tz; $GLOBALS['title']=$_POST['title']; $GLOBALS['redirector']=$_POST['redirector']; @@ -971,49 +1001,33 @@ HTML; echo ''; exit; } - else + else // Show the configuration form. { - $token = getToken(); - $title = htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES); - $redirector = htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES); + $PAGE = new pageBuilder; + $PAGE->assign('token',getToken()); + $PAGE->assign('title',htmlspecialchars( empty($GLOBALS['title']) ? '' : $GLOBALS['title'] , ENT_QUOTES)); + $PAGE->assign('redirector',htmlspecialchars( empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector'] , ENT_QUOTES)); list($timezone_form,$timezone_js) = templateTZform($GLOBALS['timezone']); - $timezone_html=''; if ($timezone_form!='') $timezone_html='Timezone:'.$timezone_form.''; - $changepwdform= << - - -{$timezone_html} - - -
Page title:
Redirector
(e.g. http://anonym.to/? will mask the HTTP_REFERER)
- -HTML; - $data = array('pageheader'=>$changepwdform,'body'=>'','onload'=>'onload="document.configform.title.focus();"'); - templatePage($data); - exit; + $PAGE->assign('timezone_form',$timezone_form); // FIXME: put entire tz form generation in template ? + $PAGE->assign('timezone_js',$timezone_js); + $PAGE->renderPage('configure'); + exit; } - } - + } + // -------- User wants to rename a tag or delete it - if (startswith($_SERVER["QUERY_STRING"],'do=changetag')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=changetag')) { if (empty($_POST['fromtag'])) { - $token = getToken(); - $changetagform = << - -Tag: - -  or 
(Case sensitive) - -HTML; - $data = array('pageheader'=>$changetagform,'body'=>'','onload'=>'onload="document.changetag.fromtag.focus();"'); - templatePage($data); - exit; + $PAGE = new pageBuilder; + $PAGE->assign('token',getToken()); + $PAGE->renderPage('changetag'); + exit; } if (!tokenOk($_POST['token'])) die('Wrong token.'); - + + // Delete a tag: if (!empty($_POST['deletetag']) && !empty($_POST['fromtag'])) { $needle=trim($_POST['fromtag']); @@ -1047,19 +1061,17 @@ HTML; invalidateCaches(); echo ''; exit; - } + } } - + // -------- User wants to add a link without using the bookmarklet: show form. - if (startswith($_SERVER["QUERY_STRING"],'do=addlink')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=addlink')) { - $onload = 'onload="document.addform.post.focus();"'; - $addform= '
'; - $data = array('pageheader'=>$addform,'body'=>'','onload'=>$onload); - templatePage($data); + $PAGE = new pageBuilder; + $PAGE->renderPage('addlink'); exit; - } - + } + // -------- User clicked the "Save" button when editing a link: Save link to database. if (isset($_POST['save_edit'])) { @@ -1067,20 +1079,20 @@ HTML; $tags = trim(preg_replace('/\s\s+/',' ', $_POST['lf_tags'])); // Remove multiple spaces. $linkdate=$_POST['lf_linkdate']; $link = array('title'=>trim($_POST['lf_title']),'url'=>trim($_POST['lf_url']),'description'=>trim($_POST['lf_description']),'private'=>(isset($_POST['lf_private']) ? 1 : 0), - 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); + 'linkdate'=>$linkdate,'tags'=>str_replace(',',' ',$tags)); if ($link['title']=='') $link['title']=$link['url']; // If title is empty, use the URL as title. $LINKSDB[$linkdate] = $link; $LINKSDB->savedb(); // save to disk pubsubhub(); invalidateCaches(); - + // If we are called from the bookmarklet, we must close the popup: if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo ''; exit; } $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); header('Location: '.$returnurl); // After saving the link, redirect to the page the user was on. exit; - } - + } + // -------- User clicked the "Cancel" button when editing a link. if (isset($_POST['cancel_edit'])) { @@ -1088,7 +1100,7 @@ HTML; if (isset($_GET['source']) && $_GET['source']=='bookmarklet') { echo ''; exit; } $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); header('Location: '.$returnurl); // After canceling, redirect to the page the user was on. - exit; + exit; } // -------- User clicked the "Delete" button when editing a link : Delete link from database. @@ -1107,19 +1119,22 @@ HTML; $returnurl = ( isset($_POST['returnurl']) ? $_POST['returnurl'] : '?' ); header('Location: '.$returnurl); // After deleting the link, redirect to the page the user was on. exit; - } - + } + // -------- User clicked the "EDIT" button on a link: Display link edit form. - if (isset($_GET['edit_link'])) + if (isset($_GET['edit_link'])) { $link = $LINKSDB[$_GET['edit_link']]; // Read database if (!$link) { header('Location: ?'); exit; } // Link not found in database. - list($editform,$onload)=templateEditForm($link); - $data = array('pageheader'=>$editform,'body'=>'','onload'=>$onload); - templatePage($data); - exit; + $PAGE = new pageBuilder; + $PAGE->assign('link',$link); + $PAGE->assign('link_is_new',false); + $PAGE->assign('token',getToken()); // XSRF protection. + $PAGE->assign('http_referer',(isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '')); + $PAGE->renderPage('editlink'); + exit; } - + // -------- User want to post a new link: Display link edit form. if (isset($_GET['post'])) { @@ -1129,51 +1144,48 @@ HTML; $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); - + $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) - if (!$link) + if (!$link) { $link_is_new = true; // This is a new link $linkdate = strval(date('Ymd_His')); $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet). $description=''; $tags=''; $private=0; - if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$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 extact 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. - // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) in html + // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) in html if (strpos($status,'200 OK')!==false) $title=html_entity_decode(html_extract_title($data),ENT_QUOTES,'UTF-8'); } if ($url=='') $url='?'.smallHash($linkdate); // In case of empty URL, this is just a text (with a link that point to itself) - $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>0); + $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>0); } - list($editform,$onload)=templateEditForm($link,$link_is_new); - $data = array('pageheader'=>$editform,'body'=>'','onload'=>$onload); - templatePage($data); + + $PAGE = new pageBuilder; + $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->renderPage('editlink'); exit; } - + // -------- Export as Netscape Bookmarks HTML file. - if (startswith($_SERVER["QUERY_STRING"],'do=export')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=export')) { if (empty($_GET['what'])) { - $toolbar= << - Export all : Export all links

- Export public : Export public links only

- Export private : Export private links only

- -HTML; - $data = array('pageheader'=>$toolbar,'body'=>'','onload'=>''); - templatePage($data); - exit; + $PAGE = new pageBuilder; + $PAGE->renderPage('export'); + exit; } $exportWhat=$_GET['what']; if (!array_intersect(array('all','public','private'),array($exportWhat))) die('What are you trying to export ???'); - + header('Content-Type: text/html; charset=utf-8'); header('Content-disposition: attachment; filename=bookmarks_'.$exportWhat.'_'.strval(date('Ymd_His')).'.html'); $currentdate=date('Y/m/d H:i:s'); @@ -1182,7 +1194,7 @@ HTML; - + Bookmarks

Bookmarks

@@ -1200,10 +1212,10 @@ HTML; } } exit; - } + } // -------- User is uploading a file for import - if (startswith($_SERVER["QUERY_STRING"],'do=upload')) + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=upload')) { // If file is too big, some form field may be missing. if (!isset($_POST['token']) || (!isset($_FILES)) || (isset($_FILES['filetoupload']['size']) && $_FILES['filetoupload']['size']==0)) @@ -1211,47 +1223,28 @@ HTML; $returnurl = ( empty($_SERVER['HTTP_REFERER']) ? '?' : $_SERVER['HTTP_REFERER'] ); echo ''; exit; - } + } if (!tokenOk($_POST['token'])) die('Wrong token.'); importFile(); exit; - } - + } + // -------- Show upload/import dialog: - if (startswith($_SERVER["QUERY_STRING"],'do=import')) - { - $token = getToken(); - $maxfilesize=getMaxFileSize(); - $onload = 'onload="document.uploadform.filetoupload.focus();"'; - $uploadform=<< -Import Netscape html bookmarks (as exported from Firefox/Chrome/Opera/delicious/diigo...) (Max: {$maxfilesize} bytes). - - - - -
-  Import all links as private
-  Overwrite existing links - - -HTML; - $data = array('pageheader'=>$uploadform,'body'=>'','onload'=>$onload ); - templatePage($data); + if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=import')) + { + $PAGE = new pageBuilder; + $PAGE->assign('token',getToken()); + $PAGE->assign('maxfilesize',getMaxFileSize()); + $PAGE->renderPage('import'); exit; - } + } // -------- Otherwise, simply display search form and links: - $searchform=<< -
-
- -HTML; - $data = array('pageheader'=>$searchform,'body'=>templateLinkList(),'onload'=>''); - templatePage($data); + $PAGE = new pageBuilder; + buildLinkList($PAGE); // Compute list of links to display + $PAGE->renderPage('linklist'); exit; -} +} // ----------------------------------------------------------------------------------------------- // Process the import file form. @@ -1259,7 +1252,7 @@ function importFile() { global $LINKSDB; $filename=$_FILES['filetoupload']['name']; - $filesize=$_FILES['filetoupload']['size']; + $filesize=$_FILES['filetoupload']['size']; $data=file_get_contents($_FILES['filetoupload']['tmp_name']); $private = (empty($_POST['private']) ? 0 : 1); // Should the links be imported as private ? $overwrite = !empty($_POST['overwrite']) ; // Should the imported links overwrite existing ones ? @@ -1268,15 +1261,15 @@ function importFile() // Sniff file type: $type='unknown'; if (startsWith($data,'')) $type='netscape'; // Netscape bookmark file (aka Firefox). - + // Then import the bookmarks. if ($type=='netscape') { // This is a standard Netscape-style bookmark file. - // This format is supported by all browsers (except IE, of course), also delicious, diigo and others. + // This format is supported by all browsers (except IE, of course), also delicious, diigo and others. foreach(explode('
',$data) as $html) // explode is very fast { - $link = array('linkdate'=>'','title'=>'','url'=>'','description'=>'','tags'=>'','private'=>0); + $link = array('linkdate'=>'','title'=>'','url'=>'','description'=>'','tags'=>'','private'=>0); $d = explode('
',$html); if (startswith($d[0],'savedb(); invalidateCaches(); - echo ''; + echo ''; } else { echo ''; } -} +} // ----------------------------------------------------------------------------------------------- -/* Template for the edit link form - Input: $link : link to edit (assocative array item as returned by the LINKDB class) -Output: An array : (string) : The html code of the edit link form. - (string) : The proper onload to use in body. - Example: list($html,$onload)=templateEditForm($mylinkdb['20110805_124532']); - echo $html; -*/ -function templateEditForm($link,$link_is_new=false) +// Template for the list of links ( -HTML; - return array($editlinkform,$onload); -} - - -// ----------------------------------------------------------------------------------------------- -// Template for the list of links. -// Returns html code to show the list of link according to parameters passed in URL (search terms, page...) -function templateLinkList() -{ - global $LINKSDB; + global $LINKSDB; // Get the links database. - // Search according to entered search terms: + // ---- Filter link database according to parameters $linksToDisplay=array(); - $searched=''; + $search_type=''; + $search_crits=''; if (!empty($_GET['searchterm'])) // Fulltext search { $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm'])); - $searched=count($linksToDisplay).' results for '.htmlspecialchars(trim($_GET['searchterm'])).':'; + $search_crits=htmlspecialchars(trim($_GET['searchterm'])); + $search_type='fulltext'; } elseif (!empty($_GET['searchtags'])) // Search by tag { $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); - $tagshtml=''; foreach(explode(' ',trim($_GET['searchtags'])) as $tag) $tagshtml.=''.htmlspecialchars($tag).' x '; - $searched=''.count($linksToDisplay).' results for tags '.$tagshtml.':'; + $search_crits=explode(' ',trim($_GET['searchtags'])); + $search_type='tags'; } - elseif (preg_match('/[a-zA-Z0-9-_@]{6}/',$_SERVER["QUERY_STRING"])) // Detect smallHashes in URL + elseif (isset($_SERVER["QUERY_STRING"]) && preg_match('/[a-zA-Z0-9-_@]{6}(&.+?)?/',$_SERVER["QUERY_STRING"])) // Detect smallHashes in URL { - $linksToDisplay = $LINKSDB->filterSmallHash($_SERVER["QUERY_STRING"]); + $linksToDisplay = $LINKSDB->filterSmallHash(substr(trim($_SERVER["QUERY_STRING"], '/'),0,6)); + $search_type='permalink'; } else $linksToDisplay = $LINKSDB; // otherwise, display without filtering. - if ($searched!='') $searched='
'.$searched.'
'; - $linklist=''; - $actions=''; - - // Handle paging. + + + // ---- Handle paging. /* Can someone explain to me why you get the following error when using array_keys() on an object which implements the interface ArrayAccess ??? "Warning: array_keys() expects parameter 1 to be array, object given in ... " If my class implements ArrayAccess, why won't array_keys() accept it ? ( $keys=array_keys($linksToDisplay); ) @@ -1422,72 +1368,62 @@ function templateLinkList() // If there is only a single link, we change on-the-fly the title of the page. if (count($linksToDisplay)==1) $GLOBALS['pagetitle'] = $linksToDisplay[$keys[0]]['title'].' - '.$GLOBALS['title']; + // Select articles according to paging. $pagecount = ceil(count($keys)/$_SESSION['LINKS_PER_PAGE']); $pagecount = ($pagecount==0 ? 1 : $pagecount); $page=( empty($_GET['page']) ? 1 : intval($_GET['page'])); $page = ( $page<1 ? 1 : $page ); $page = ( $page>$pagecount ? $pagecount : $page ); $i = ($page-1)*$_SESSION['LINKS_PER_PAGE']; // Start index. - $end = $i+$_SESSION['LINKS_PER_PAGE']; - $redir = empty($GLOBALS['redirector']) ? '' : $GLOBALS['redirector']; // optional redirector URL - $token = ''; if (isLoggedIn()) $token=getToken(); - + $end = $i+$_SESSION['LINKS_PER_PAGE']; + $linkDisp=array(); // Links to display while ($i<$end && $i'; - $actions.='
'; - $actions.='
'; - } - $tags=''; - if ($link['tags']!='') - { - foreach(explode(' ',$link['tags']) as $tag) { $tags.=''.htmlspecialchars($tag).' '; } - $tags='
'.$tags.'
'; - } - $linklist.='
  • '.thumbnail($link['url']).'
    '; - $linklist.='
  • \n"; + $classLi = $i%2!=0 ? '' : 'publicLinkHightLight'; + $link['class'] = ($link['private']==0 ? $classLi : 'private'); + $link['localdate']=linkdate2locale($link['linkdate']); + $link['taglist']=explode(' ',$link['tags']); + $linkDisp[$keys[$i]] = $link; $i++; - } + } - // Show paging. + // Compute paging navigation $searchterm= ( empty($_GET['searchterm']) ? '' : '&searchterm='.$_GET['searchterm'] ); $searchtags= ( empty($_GET['searchtags']) ? '' : '&searchtags='.$_GET['searchtags'] ); - $paging=''; - if ($i!=count($keys)) $paging.='◄Older'; - $paging.= 'page '.$page.' / '.$pagecount.''; - if ($page>1) $paging.='Newer►'; - $linksperpage = << -Links per page: 20 50 100 -
    -HTML; - $paging = '
    '.$linksperpage.$paging.'
    '; - $linklist=''; - return $linklist; + $paging=''; + $previous_page_url=''; if ($i!=count($keys)) $previous_page_url='?page='.($page+1).$searchterm.$searchtags; + $next_page_url='';if ($page>1) $next_page_url='?page='.($page-1).$searchterm.$searchtags; + + $token = ''; if (isLoggedIn()) $token=getToken(); + + // Fill all template fields. + $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); + return; } // Returns the HTML code to display a thumbnail for a link // with a link to the original URL. // 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 +// $href = if provided, this URL will be followed instead of $url function thumbnail($url,$href=false) { if (!$GLOBALS['config']['ENABLE_THUMBNAILS']) return ''; - + if ($href==false) $href=$url; - + // For most hosts, the URL of the thumbnail can be easily deduced from the URL of the link. // (eg. http://www.youtube.com/watch?v=spVypYk4kto ---> http://img.youtube.com/vi/spVypYk4kto/default.jpg ) // ^^^^^^^^^^^ ^^^^^^^^^^^ @@ -1501,7 +1437,7 @@ function thumbnail($url,$href=false) { $path = parse_url($url,PHP_URL_PATH); return ''; - } + } if ($domain=='imgur.com') { $path = parse_url($url,PHP_URL_PATH); @@ -1514,7 +1450,7 @@ function thumbnail($url,$href=false) { $pi = pathinfo(parse_url($url,PHP_URL_PATH)); if (!empty($pi['filename'])) return ''; - } + } if ($domain=='dailymotion.com' || $domain=='www.dailymotion.com') { if (strpos($url,'dailymotion.com/video/')!==false) @@ -1522,7 +1458,7 @@ function thumbnail($url,$href=false) $thumburl=str_replace('dailymotion.com/video/','dailymotion.com/thumbnail/video/',$url); return ''; } - } + } if (endsWith($domain,'.imageshack.us')) { $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION)); @@ -1536,9 +1472,9 @@ function thumbnail($url,$href=false) // Some other hosts are SLOW AS HELL and usually require an extra HTTP request to get the thumbnail URL. // So we deport the thumbnail generation in order not to slow down page generation // (and we also cache the thumbnail) - + if (!$GLOBALS['config']['ENABLE_LOCALCACHE']) return ''; // If local cache is disabled, no thumbnails for services which require the use a local cache. - + if ($domain=='flickr.com' || endsWith($domain,'.flickr.com') || $domain=='vimeo.com' || $domain=='ted.com' || endsWith($domain,'.ted.com') @@ -1546,7 +1482,7 @@ function thumbnail($url,$href=false) { if ($domain=='vimeo.com') { // Make sure this vimeo url points to a video (/xxx... where xxx is numeric) - $path = parse_url($url,PHP_URL_PATH); + $path = parse_url($url,PHP_URL_PATH); if (!preg_match('!/\d+.+?!',$path)) return ''; // This is not a single video URL. } if ($domain=='ted.com' || endsWith($domain,'.ted.com')) @@ -1565,98 +1501,12 @@ function thumbnail($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 ''; + return ''; } return ''; // No thumbnail. } -// ----------------------------------------------------------------------------------------------- -// Template for the whole page. -/* Input: $data (associative array). - Keys: 'body' : body of HTML document - 'pageheader' : html code to show in page header (top of page) - 'onload' : optional onload javascript for the -*/ -function templatePage($data) -{ - global $STARTTIME; - global $LINKSDB; - $shaarli_version = shaarli_version; - - $newversion=checkUpdate(); - if ($newversion!='') $newversion='
    Shaarli '.htmlspecialchars($newversion).' is available.
    '; - $linkcount = count($LINKSDB); - - $feedurl=htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']); - $searchcrits=''; // Search criteria - if (!empty($_GET['searchtags'])) $searchcrits.='&searchtags='.$_GET['searchtags']; - elseif (!empty($_GET['searchterm'])) $searchcrits.='&searchterm='.$_GET['searchterm']; - $filtered_feed= ($searchcrits=='' ? '' : 'Filtered '); - - $open=''; - if ($GLOBALS['config']['OPEN_SHAARLI']) - { - $menu='ToolsAdd link'; - $open='Open '; - } - else - $menu=(isLoggedIn() ? 'LogoutToolsAdd link' : ' Login'); - $menu='Home'.$menu.'RSS FeedATOM FeedTag cloudPicture wall'; - # If we are in the bookmarklet popup, do not display menu. - if (!empty($_GET['source']) && $_GET['source']=='bookmarklet') $menu=''; - - foreach(array('pageheader','body','onload') as $k) // make sure all required fields exist (put an empty string if not). - { - if (!array_key_exists($k,$data)) $data[$k]=''; - } - $jsincludes=''; $jsincludes_bottom = ''; - if ($GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn()) - { - $source = serverUrl().$_SERVER['SCRIPT_NAME']; - $jsincludes=''; - $jsincludes_bottom = << -$(document).ready(function() -{ - $('#lf_tags').autocomplete({source:'{$source}?ws=tags',minLength:1}); - $('#searchtags').autocomplete({source:'{$source}?ws=tags',minLength:1}); - $('#fromtag').autocomplete({source:'{$source}?ws=singletag',minLength:1}); -}); - -JS; - } - - $version=shaarli_version; - - $title = htmlspecialchars( $GLOBALS['title'] ); - $pagetitle = htmlspecialchars( empty($GLOBALS['pagetitle']) ? $title : $GLOBALS['pagetitle'] ); - - echo << - -{$pagetitle} - - - - - -{$jsincludes} - -{$newversion} - -{$data['body']} - -HTML; - $exectime = round(microtime(true)-$STARTTIME,4); - echo ''; - if (isLoggedIn()) echo ''; - echo $jsincludes_bottom.''; -} - // ----------------------------------------------------------------------------------------------- // Installation // This function should NEVER be called if the file data/config.php exists. @@ -1664,41 +1514,32 @@ function install() { // On free.fr host, make sure the /sessions directory exists, otherwise login will not work. if (endsWith($_SERVER['SERVER_NAME'],'.free.fr') && !is_dir($_SERVER['DOCUMENT_ROOT'].'/sessions')) mkdir($_SERVER['DOCUMENT_ROOT'].'/sessions',0705); - + if (!empty($_POST['setlogin']) && !empty($_POST['setpassword'])) { $tz = 'UTC'; if (!empty($_POST['continent']) && !empty($_POST['city'])) if (isTZvalid($_POST['continent'],$_POST['city'])) $tz = $_POST['continent'].'/'.$_POST['city']; - $GLOBALS['timezone'] = $tz; + $GLOBALS['timezone'] = $tz; // Everything is ok, let's create config file. $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 '.htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']) : $_POST['title'] ); + $GLOBALS['title'] = (empty($_POST['title']) ? 'Shared links on '.htmlspecialchars(indexUrl()) : $_POST['title'] ); writeConfig(); - echo ''; - exit; + echo ''; + exit; } - - // Display config form: + + // Display config form: list($timezone_form,$timezone_js) = templateTZform(); $timezone_html=''; if ($timezone_form!='') $timezone_html='Timezone:'.$timezone_form.''; - echo <<Shaarli - Configuration${timezone_js} -

    Shaarli - Shaare your links...

    -It looks like it's the first time you run Shaarli. Please configure it:
    -
    - - - -{$timezone_html} - - -
    Login:
    Password:
    Page title:
    -
    -HTML; + + $PAGE = new pageBuilder; + $PAGE->assign('timezone_html',$timezone_html); + $PAGE->assign('timezone_js',$timezone_js); + $PAGE->renderPage('install'); exit; } @@ -1714,7 +1555,7 @@ function templateTZform($ptz=false) // Try to split the provided timezone. if ($ptz==false) { $l=timezone_identifiers_list(); $ptz=$l[0]; } $spos=strpos($ptz,'/'); $pcontinent=substr($ptz,0,$spos); $pcity=substr($ptz,$spos+1); - + // Display config form: $timezone_form = ''; $timezone_js = ''; @@ -1722,7 +1563,7 @@ function templateTZform($ptz=false) // We split the list in continents/cities. $continents = array(); $cities = array(); - foreach(timezone_identifiers_list() as $tz) + foreach(timezone_identifiers_list() as $tz) { if ($tz=='UTC') $tz='UTC/UTC'; $spos = strpos($tz,'/'); @@ -1737,12 +1578,12 @@ function templateTZform($ptz=false) $continents_html = ''; $continents = array_keys($continents); foreach($continents as $continent) - $continents_html.=''; + $continents_html.=''; $cities_html = $cities[$pcontinent]; $timezone_form = "Continent:

    "; - $timezone_form .= "City:

    "; + $timezone_form .= "City:

    "; $timezone_js = "" ; return array($timezone_form,$timezone_js); @@ -1776,32 +1617,32 @@ function processWS() // Search in tags (case insentitive, 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(); + 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 sentitive, 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(); + 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. @@ -1815,7 +1656,7 @@ function writeConfig() $config=''; + $config .= ' ?>'; if (!file_put_contents($GLOBALS['config']['CONFIG_FILE'],$config) || strcmp(file_get_contents($GLOBALS['config']['CONFIG_FILE']),$config)!=0) { echo ''; @@ -1836,14 +1677,14 @@ function genThumbnail() { // Make sure the parameters in the URL were generated by us. $sign = hash_hmac('sha256', $_GET['url'], $GLOBALS['salt']); - if ($sign!=$_GET['hmac']) die('Naughty boy !'); - + if ($sign!=$_GET['hmac']) die('Naughty boy !'); + // Let's see if we don't already have the image for this URL in the cache. $thumbname=hash('sha1',$_GET['url']).'.jpg'; if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$thumbname)) { // We have the thumbnail, just serve it: header('Content-Type: image/jpeg'); - echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname); + echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$thumbname); return; } // We may also serve a blank image (if service did not respond) @@ -1851,16 +1692,16 @@ function genThumbnail() if (is_file($GLOBALS['config']['CACHEDIR'].'/'.$blankname)) { header('Content-Type: image/gif'); - echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname); + echo file_get_contents($GLOBALS['config']['CACHEDIR'].'/'.$blankname); return; - } - + } + // Otherwise, generate the thumbnail. $url = $_GET['url']; $domain = parse_url($url,PHP_URL_HOST); if ($domain=='flickr.com' || endsWith($domain,'.flickr.com')) - { + { // WTF ? I need a flickr API key to get a fucking thumbnail ? No way. // I'll extract the thumbnail URL myself. First, we have to get the flickr HTML page. // All images in Flickr are in the form: @@ -1875,7 +1716,7 @@ function genThumbnail() if (endswith(parse_url($url,PHP_URL_PATH),'.jpg')) { // This is a direct link to an image. eg. http://farm1.static.flickr.com/5/5921913_ac83ed27bd_o.jpg preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches); - if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; + if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; } else // this is a flickr page (html) { @@ -1883,7 +1724,7 @@ function genThumbnail() if (strpos($httpstatus,'200 OK')!==false) { preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)[^s].jpg!',$data,$matches); - if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; + if (!empty($matches[1])) $imageurl=$matches[1].'m.jpg'; } } if ($imageurl!='') @@ -1896,7 +1737,7 @@ function genThumbnail() echo $data; return; } - } + } } elseif ($domain=='vimeo.com' ) @@ -1917,8 +1758,8 @@ function genThumbnail() header('Content-Type: image/jpeg'); echo $data; return; - } - } + } + } } elseif ($domain=='ted.com' || endsWith($domain,'.ted.com')) @@ -1942,14 +1783,14 @@ function genThumbnail() if (resizeImage($filepath)) { header('Content-Type: image/jpeg'); - echo file_get_contents($filepath); + echo file_get_contents($filepath); return; } } } } } - + else { // For all other domains, we try to download the image and make a thumbnail. @@ -1961,7 +1802,7 @@ function genThumbnail() if (resizeImage($filepath)) { header('Content-Type: image/jpeg'); - echo file_get_contents($filepath); + echo file_get_contents($filepath); return; } } @@ -2014,11 +1855,11 @@ function invalidateCaches() unset($_SESSION['tags']); } -if (startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. +if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=genthumbnail')) { genThumbnail(); exit; } // Thumbnail generation/cache does not need the link database. $LINKSDB=new linkdb(isLoggedIn() || $GLOBALS['config']['OPEN_SHAARLI']); // Read links from database (and filter private links if used it not logged in). -if (startswith($_SERVER["QUERY_STRING"],'ws=')) { processWS(); exit; } // Webservices (for jQuery/jQueryUI) +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']; -if (startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } -if (startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } +if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=rss')) { showRSS(); exit; } +if (isset($_SERVER["QUERY_STRING"]) && startswith($_SERVER["QUERY_STRING"],'do=atom')) { showATOM(); exit; } renderPage(); -?> \ No newline at end of file +?> diff --git a/tpl/addlink.html b/tpl/addlink.html new file mode 100644 index 00000000..7dd767cc --- /dev/null +++ b/tpl/addlink.html @@ -0,0 +1,15 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/changepassword.html b/tpl/changepassword.html new file mode 100644 index 00000000..acd61f55 --- /dev/null +++ b/tpl/changepassword.html @@ -0,0 +1,14 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/changetag.html b/tpl/changetag.html new file mode 100644 index 00000000..a8ecfb4f --- /dev/null +++ b/tpl/changetag.html @@ -0,0 +1,15 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/configure.html b/tpl/configure.html new file mode 100644 index 00000000..f13ecb9a --- /dev/null +++ b/tpl/configure.html @@ -0,0 +1,19 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/editlink.html b/tpl/editlink.html new file mode 100644 index 00000000..2225fbab --- /dev/null +++ b/tpl/editlink.html @@ -0,0 +1,27 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/export.html b/tpl/export.html new file mode 100644 index 00000000..ccfcfcf1 --- /dev/null +++ b/tpl/export.html @@ -0,0 +1,14 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/import.html b/tpl/import.html new file mode 100644 index 00000000..95df723e --- /dev/null +++ b/tpl/import.html @@ -0,0 +1,20 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/includes.html b/tpl/includes.html new file mode 100644 index 00000000..c70b44e3 --- /dev/null +++ b/tpl/includes.html @@ -0,0 +1,7 @@ +{$pagetitle} + + + + +{if condition="is_file('inc/user.css')"}{/if} + diff --git a/tpl/install.html b/tpl/install.html new file mode 100644 index 00000000..1e48a185 --- /dev/null +++ b/tpl/install.html @@ -0,0 +1,20 @@ + +{include="includes"}{$timezone_js} + +
    +

    Shaarli

    +It looks like it's the first time you run Shaarli. Please configure it:
    +
    +
    + + + +{$timezone_html} + + +
    Login:
    Password:
    Page title:
    +
    +
    +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/linklist.html b/tpl/linklist.html new file mode 100644 index 00000000..c7de5b87 --- /dev/null +++ b/tpl/linklist.html @@ -0,0 +1,61 @@ + +{include="includes"} + + + + + + {include="page.footer"} + + \ No newline at end of file diff --git a/tpl/linklist.paging.html b/tpl/linklist.paging.html new file mode 100644 index 00000000..27e7440d --- /dev/null +++ b/tpl/linklist.paging.html @@ -0,0 +1,9 @@ +
    +
    + Links per page: 20 50 100 +
    +
    + {if="$previous_page_url"} ◄Older {/if} + page {$page_current} / {$page_max} + {if="$next_page_url"} Newer► {/if} +
    \ No newline at end of file diff --git a/tpl/loginform.html b/tpl/loginform.html new file mode 100644 index 00000000..5a81fde1 --- /dev/null +++ b/tpl/loginform.html @@ -0,0 +1,25 @@ + +{include="includes"} + + + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/page.footer.html b/tpl/page.footer.html new file mode 100644 index 00000000..80a000bb --- /dev/null +++ b/tpl/page.footer.html @@ -0,0 +1,20 @@ + +{if="$newversion"} +
    Shaarli {$newversion|htmlspecialchars} is available.
    +{/if} +{if="isLoggedIn()"} + +{/if} + +{if="$GLOBALS['config']['OPEN_SHAARLI'] || isLoggedIn()"} + +{/if} diff --git a/tpl/page.header.html b/tpl/page.header.html new file mode 100644 index 00000000..72965a87 --- /dev/null +++ b/tpl/page.header.html @@ -0,0 +1,24 @@ + + +
    Shaare your links...
    {$linkcount} links
    + {$shaarlititle} + +{if="!empty($_GET['source']) && $_GET['source']=='bookmarklet'"} + {ignore} When called as a popup from bookmarklet, do not display menu. {/ignore} +{else} + Home + {if="isLoggedIn()"} + LogoutToolsAdd link + {elseif="$GLOBALS['config']['OPEN_SHAARLI']"} + ToolsAdd link + {else} + Login + {/if} + RSS Feed + ATOM Feed + Tag cloud + Picture wall +{/if} +
    + + diff --git a/tpl/page.html b/tpl/page.html new file mode 100644 index 00000000..2601169e --- /dev/null +++ b/tpl/page.html @@ -0,0 +1,8 @@ + +{include="includes"} + + + You body goes here... + {include="page.footer"} + + \ No newline at end of file diff --git a/tpl/picwall.html b/tpl/picwall.html new file mode 100644 index 00000000..48a1acf1 --- /dev/null +++ b/tpl/picwall.html @@ -0,0 +1,16 @@ + +{include="includes"} + + +
    +
    + {loop="linksToDisplay"} +
    + {$value.thumbnail}{$value.title|htmlspecialchars} +
    + {/loop} +
    +
    +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/picwall2.html b/tpl/picwall2.html new file mode 100644 index 00000000..765cf439 --- /dev/null +++ b/tpl/picwall2.html @@ -0,0 +1,18 @@ + +{include="includes"} + + +
    + {loop="linksToDisplay"} +
    +
    {$value.thumbnail}
    + {$value.title|htmlspecialchars}
    + {$value.description|htmlspecialchars} +
    +

    + {/loop} +
    + +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/readme.txt b/tpl/readme.txt new file mode 100644 index 00000000..d57080ea --- /dev/null +++ b/tpl/readme.txt @@ -0,0 +1,42 @@ +===== Shaarli template organisation ===== + +Any Shaarli page should conform to this RainTPL template: + +----------------------------------------------------- + +{include="includes"} + + + You body goes here... + {include="page.footer"} + + +----------------------------------------------------- + +If you want to also add something in the page header (in the dark area), do it here: + + + + +Example: "Add new link" form: +----------------------------------------------------- + +{include="includes"} + + +{include="page.footer"} + + +----------------------------------------------------- + + + + diff --git a/tpl/tagcloud.html b/tpl/tagcloud.html new file mode 100644 index 00000000..4bd78115 --- /dev/null +++ b/tpl/tagcloud.html @@ -0,0 +1,14 @@ + +{include="includes"} + + +
    +
    + {loop="tags"} + {$value.count}{$key|htmlspecialchars} + {/loop} +
    +
    +{include="page.footer"} + + \ No newline at end of file diff --git a/tpl/tools.html b/tpl/tools.html new file mode 100644 index 00000000..bf70021c --- /dev/null +++ b/tpl/tools.html @@ -0,0 +1,18 @@ + +{include="includes"} + + +{include="page.footer"} + + \ No newline at end of file