From f4aba1ac2f3730ae8b73c98aa0fc612fe9d099c5 Mon Sep 17 00:00:00 2001 From: Seb Sauvage Date: Fri, 23 Sep 2011 15:10:18 +0200 Subject: [PATCH] Version 0.0.17 beta: - New: Change password screen added (based on a patch by killruana). - New: Autocomplete in the tag search form. - New: You can rename or delete a tag in all links (very handy if you misspelled a tag or want to merge tags). - New: When you click the RSS feed, the feed will be filtered with the same filters as the page you were viewing. - Changed: CSS adjustments by jerrywham. - Changed: Minor corrections. --- index.php | 189 ++++++++++++++++++++++++++++++++++++++++++---------- shaarli.css | 7 +- 2 files changed, 158 insertions(+), 38 deletions(-) diff --git a/index.php b/index.php index b5915ec3..af76ed9b 100644 --- a/index.php +++ b/index.php @@ -1,5 +1,5 @@ links=(file_exists(DATASTORE) ? unserialize(gzinflate(base64_decode(substr(file_get_contents(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) @@ -505,7 +505,7 @@ 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->filterTags('hollandais')); + // eg. print_r($mydb->filterFulltext('hollandais')); public function filterFulltext($searchterms) { // FIXME: explode(' ',$searchterms) and perform a AND search. @@ -524,14 +524,14 @@ class linkdb implements Iterator, Countable, ArrayAccess // 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')); - public function filterTags($tags) + public function filterTags($tags,$casesensitive=false) { - $t = str_replace(',',' ',strtolower($tags)); + $t = str_replace(',',' ',($casesensitive?$tags:strtolower($tags))); $searchtags=explode(' ',$t); $filtered=array(); foreach($this->links as $l) { - $linktags = explode(' ',strtolower($l['tags'])); + $linktags = explode(' ',($casesensitive?$l['tags']:strtolower($l['tags']))); if (count(array_intersect($linktags,$searchtags)) == count($searchtags)) $filtered[$l['linkdate']] = $l; } @@ -549,8 +549,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; - } - + } } // ------------------------------------------------------------------------------------------ @@ -562,7 +561,7 @@ function showRSS() // Optionnaly filter the results: $linksToDisplay=array(); if (!empty($_GET['searchterm'])) $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); - elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags($_GET['searchtags']); + elseif (!empty($_GET['searchtags'])) $linksToDisplay = $LINKSDB->filterTags(trim($_GET['searchtags'])); else $linksToDisplay = $LINKSDB; header('Content-Type: application/xhtml+xml; charset=utf-8'); @@ -627,7 +626,7 @@ function renderPage() if (startswith($_SERVER["QUERY_STRING"],'do=tagcloud')) { $tags= $LINKSDB->allTags(); - // We sort tags alphabetically, when choose a font size according to count. + // 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); @@ -651,7 +650,7 @@ function renderPage() // Get previous URL (http_referer) and add the tag to the searchtags parameters in query. if (empty($_SERVER['HTTP_REFERER'])) { header('Location: ?searchtags='.urlencode($_GET['addtag'])); exit; } // In case browser does not send HTTP_REFERER parse_str(parse_url($_SERVER['HTTP_REFERER'],PHP_URL_QUERY), $params); - $params['searchtags'] = (empty($params['searchtags']) ? trim($_GET['addtag']) : trim($params['searchtags'].' '.urlencode($_GET['addtag']))); + $params['searchtags'] = (empty($params['searchtags']) ? trim($_GET['addtag']) : trim($params['searchtags']).' '.urlencode(trim($_GET['addtag']))); unset($params['page']); // We also remove page (keeping the same page has no sense, since the results are different) header('Location: ?'.http_build_query($params)); exit; @@ -698,10 +697,10 @@ function renderPage() $searchform=<<
-
+
HTML; - $onload = 'document.searchform.searchterm.focus();'; + $onload = 'onload="document.searchform.searchterm.focus();"'; $data = array('pageheader'=>$searchform,'body'=>templateLinkList(),'onload'=>$onload); templatePage($data); exit; // Never remove this one ! @@ -714,8 +713,11 @@ HTML; { $pageabsaddr=serverUrl().$_SERVER["SCRIPT_NAME"]; // Why doesn't php have a built-in function for that ? // The javascript code for the bookmarklet: + $changepwd = (OPEN_SHAARLI ? '' : 'Change password - Change your password.

' ); $toolbar= <<
+ {$changepwd} + 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.

@@ -725,11 +727,107 @@ HTML; templatePage($data); exit; } + + // -------- User wants to change his/her password. + if (startswith($_SERVER["QUERY_STRING"],'do=changepasswd')) + { + if (OPEN_SHAARLI) die('You are not supposed to change a password on an Open Shaarli.'); + if (!empty($_POST['setpassword']) && !empty($_POST['oldpassword'])) + { + if (!tokenOk($_POST['token'])) die('Wrong token.'); // Go away ! + + // Make sure old password is correct. + $oldhash = sha1($_POST['oldpassword'].$GLOBALS['login'].$GLOBALS['salt']); + if ($oldhash!=$GLOBALS['hash']) { echo ''; exit; } + + // Save new password + $salt=sha1(uniqid('',true).'_'.mt_rand()); // Salt renders rainbow-tables attacks useless. + $hash = sha1($_POST['setpassword'].$GLOBALS['login'].$salt); + $config=''; + if (!file_put_contents(CONFIG_FILE,$config) || strcmp(file_get_contents(CONFIG_FILE),$config)!=0) + { + echo ''; + exit; + } + echo ''; + exit; + } + else + { + $token = getToken(); + $changepwdform= << +Old password:     +New password: + + +HTML; + $data = array('pageheader'=>$changepwdform,'body'=>'','onload'=>'onload="document.changepasswordform.oldpassword.focus();"'); + templatePage($data); + exit; + } + } + + // -------- User wants to rename a tag or delete it + if (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; + } + if (!tokenOk($_POST['token'])) die('Wrong token.'); + + if (!empty($_POST['deletetag']) && !empty($_POST['fromtag'])) + { + $needle=trim($_POST['fromtag']); + $linksToAlter = $LINKSDB->filterTags($needle,true); // true for case-sensitive tag search. + foreach($linksToAlter as $key=>$value) + { + $tags = explode(' ',trim($value['tags'])); + unset($tags[array_search($needle,$tags)]); // Remove tag. + $value['tags']=trim(implode(' ',$tags)); + $LINKSDB[$key]=$value; + } + $LINKSDB->savedb(); // save to disk + invalidateCaches(); + echo ''; + exit; + } + + // Rename a tag: + if (!empty($_POST['renametag']) && !empty($_POST['fromtag']) && !empty($_POST['totag'])) + { + $needle=trim($_POST['fromtag']); + $linksToAlter = $LINKSDB->filterTags($needle,true); // true for case-sensitive tag search. + foreach($linksToAlter as $key=>$value) + { + $tags = explode(' ',trim($value['tags'])); + $tags[array_search($needle,$tags)] = trim($_POST['totag']); // Remplace tags value. + $value['tags']=trim(implode(' ',$tags)); + $LINKSDB[$key]=$value; + } + $LINKSDB->savedb(); // save to disk + invalidateCaches(); + echo ''; + exit; + } + } // -------- User wants to add a link without using the bookmarklet: show form. if (startswith($_SERVER["QUERY_STRING"],'do=addlink')) { - $onload = 'document.addform.post.focus();'; + $onload = 'onload="document.addform.post.focus();"'; $addform= '
'; $data = array('pageheader'=>$addform,'body'=>'','onload'=>$onload); templatePage($data); @@ -899,12 +997,12 @@ HTML;
Import Netscape html bookmarks (as exported from Firefox/Chrome/Opera/delicious/diigo...) (Max: {$maxfilesize} bytes). - +
-  Import all links as private
-  Overwrite existing links +  Import all links as private
+  Overwrite existing links
HTML; @@ -917,10 +1015,10 @@ HTML; $searchform=<<
-
+
HTML; - $onload = 'document.searchform.searchterm.focus();'; + $onload = 'onload="document.searchform.searchterm.focus();"'; $data = array('pageheader'=>$searchform,'body'=>templateLinkList(),'onload'=>$onload); templatePage($data); exit; @@ -1044,13 +1142,13 @@ function templateLinkList() $searched=''; if (!empty($_GET['searchterm'])) // Fulltext search { - $linksToDisplay = $LINKSDB->filterFulltext($_GET['searchterm']); - $searched=' '.count($linksToDisplay).' results for '.htmlspecialchars($_GET['searchterm']).':'; + $linksToDisplay = $LINKSDB->filterFulltext(trim($_GET['searchterm'])); + $searched=' '.count($linksToDisplay).' results for '.htmlspecialchars(trim($_GET['searchterm'])).':'; } elseif (!empty($_GET['searchtags'])) // Search by tag { - $linksToDisplay = $LINKSDB->filterTags($_GET['searchtags']); - $tagshtml=''; foreach(explode(' ',$_GET['searchtags']) as $tag) $tagshtml.=''.htmlspecialchars($tag).' x '; + $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.':'; } else @@ -1128,7 +1226,8 @@ function templatePage($data) $open='Open '; } else - $menu=(isLoggedIn() ? ' Logout  Tools  Add link' : ' Login'); + $menu=(isLoggedIn() ? ' Logout  Tools  Add link' : ' Login'); + 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]=''; @@ -1137,17 +1236,22 @@ function templatePage($data) if (OPEN_SHAARLI || isLoggedIn()) { $jsincludes=''; - $source = serverUrl().$_SERVER['SCRIPT_NAME'].'?ws=tags'; + $source = serverUrl().$_SERVER['SCRIPT_NAME']; $jsincludes_bottom = << + + $('#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; } - $feedurl=htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME'].'?do=rss'); + $feedurl=htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME'].'?do=rss'); + if (!empty($_GET['searchtags'])) $feedurl.='&searchtags='.$_GET['searchtags']; + elseif (!empty($_GET['searchterm'])) $feedurl.='&searchterm='.$_GET['searchterm']; + echo << @@ -1223,7 +1327,7 @@ function processWS() global $LINKSDB; header('Content-Type: application/json; charset=utf-8'); - // Search in tags + // Search in tags (case insentitive, cumulative search) if ($_GET['ws']=='tags') { $tags=explode(' ',$term); $last = array_pop($tags); // Get the last term ("a b c d" ==> "a b c", "d") @@ -1238,6 +1342,19 @@ function processWS() 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(); + foreach($_SESSION['tags'] as $key=>$value) + { + if (startsWith($key,$term,$case=true)) $suggested[$key]=0; + } + echo json_encode(array_keys($suggested)); + exit; + } } // Invalidate caches when the database is changed or the user logs out. diff --git a/shaarli.css b/shaarli.css index 0b261852..8dd8b14a 100644 --- a/shaarli.css +++ b/shaarli.css @@ -30,7 +30,8 @@ background-image: -ms-linear-gradient(top, #6A6A6A, #303030); /* IE10 */ background-image: -o-linear-gradient(top, #6A6A6A, #303030); /* Opera 11.10+ */ background-image: linear-gradient(top, #6A6A6A, #303030); filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#6A6A6A', EndColorStr='#303030'); /* IE6-IE9 */ -padding-bottom: 5px; +padding: 0 10 5 10; +margin: auto; } #pageheader a:link { color:#bbb; text-decoration:none;} #pageheader a:visited { color:#bbb; text-decoration:none;} @@ -41,7 +42,7 @@ padding-bottom: 5px; .paging a:visited { color:#ccc; } .paging a:hover { color:#FFFFC9; } .paging a:active { color:#fff; } -#headerform { padding:5 5 5 15; } +#headerform { padding:5 5 5 5; } #editlinkform { padding:5 5 5 15px; width:80%; } #linklist li { padding:4 10 8 20; border-bottom: 1px solid #bbb;} #linklist li.private { background-color: #ccc; border-left:8px solid #888; } @@ -64,3 +65,5 @@ border-bottom:1px solid #aaa; border-right:1px solid #aaa; } /* Minimal customisation for jQuery widgets */ .ui-autocomplete { background-color:#fff; padding-left:5px;} .ui-state-hover { background-color: #604dff; color:#fff; } + +} \ No newline at end of file -- 2.41.0