aboutsummaryrefslogtreecommitdiffhomepage
path: root/index.php
diff options
context:
space:
mode:
authorSeb Sauvage <sebsauvage@sebsauvage.net>2011-10-06 16:56:16 +0200
committerEmilien Klein <emilien@klein.st>2011-10-06 16:56:16 +0200
commit99c9c9541b012eebdb84ee33a765bb18a62fb0d7 (patch)
tree75566579cb42c2febacfd52dd1e69ab8f2842b21 /index.php
parentdcd653d10e168e655ce080da6886ef9fc2c1d3e1 (diff)
downloadShaarli-99c9c9541b012eebdb84ee33a765bb18a62fb0d7.tar.gz
Shaarli-99c9c9541b012eebdb84ee33a765bb18a62fb0d7.tar.zst
Shaarli-99c9c9541b012eebdb84ee33a765bb18a62fb0d7.zip
Version 0.0.24 beta:
- Corrected: Now thumnails also work for imgur gallery links (/gallery/…) (Thanks to Accent Grave for the correction) - Corrected: Removed useless debugging information in log. - Corrected: The filter in RSS/ATOM feed now works again properly (it was broken in 0.0.17 beta) - New: As per request, you can now post an entry without a link. (You can use Shaarli as a kind of “personal twitter”). - New: Each Shaarli entry now has a short link (just click on the date of a link). Now you can send a link that points to a single entry in your Shaarli. - New: In descriptions, URLs are now clickable. - New: Thumbnails will be generated for all link pointing to .jpg/png/gif (as long as the images are less than 4 Mb and take less than 30 seconds to download).
Diffstat (limited to 'index.php')
-rw-r--r--index.php129
1 files changed, 114 insertions, 15 deletions
diff --git a/index.php b/index.php
index a9491ed3..6e7e638c 100644
--- a/index.php
+++ b/index.php
@@ -1,5 +1,5 @@
1<?php 1<?php
2// Shaarli 0.0.23 beta - Shaare your links... 2// Shaarli 0.0.24 beta - Shaare your links...
3// The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net 3// The personal, minimalist, super-fast, no-database delicious clone. By sebsauvage.net
4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli 4// http://sebsauvage.net/wiki/doku.php?id=php:shaarli
5// Licence: http://www.opensource.org/licenses/zlib-license.php 5// Licence: http://www.opensource.org/licenses/zlib-license.php
@@ -49,7 +49,7 @@ header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
49header("Cache-Control: no-store, no-cache, must-revalidate"); 49header("Cache-Control: no-store, no-cache, must-revalidate");
50header("Cache-Control: post-check=0, pre-check=0", false); 50header("Cache-Control: post-check=0, pre-check=0", false);
51header("Pragma: no-cache"); 51header("Pragma: no-cache");
52define('shaarli_version','0.0.23 beta'); 52define('shaarli_version','0.0.24 beta');
53if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); } 53if (!is_dir(DATADIR)) { mkdir(DATADIR,0705); chmod(DATADIR,0705); }
54if (!is_dir(CACHEDIR)) { mkdir(CACHEDIR,0705); chmod(CACHEDIR,0705); } 54if (!is_dir(CACHEDIR)) { mkdir(CACHEDIR,0705); chmod(CACHEDIR,0705); }
55if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files. 55if (!is_file(DATADIR.'/.htaccess')) { file_put_contents(DATADIR.'/.htaccess',"Allow from none\nDeny from all\n"); } // Protect data files.
@@ -105,6 +105,29 @@ function logm($message)
105 file_put_contents(DATADIR.'/log.txt',$t,FILE_APPEND); 105 file_put_contents(DATADIR.'/log.txt',$t,FILE_APPEND);
106} 106}
107 107
108/* Returns the small hash of a string
109 eg. smallHash('20111006_131924') --> yZH23w
110 Small hashes:
111 - are unique (well, as unique as crc32, at last)
112 - are always 6 characters long.
113 - only use the following characters: a-z A-Z 0-9 - _ @
114 - are NOT cryptographically secure (they CAN be forged)
115 In Shaarli, they are used as a tinyurl-like link to individual entries.
116*/
117function smallHash($text)
118{
119 $t = rtrim(base64_encode(hash('crc32',$text,true)),'=');
120 $t = str_replace('+','-',$t); // Get rid of characters which need encoding in URLs.
121 $t = str_replace('/','_',$t);
122 $t = str_replace('=','@',$t);
123 return $t;
124}
125
126// Function inspired from http://www.php.net/manual/en/function.preg-replace.php#85722
127function text2clickable($url)
128{
129 return preg_replace('!((?:https?|ftp)://\S+[[:alnum:]]/?)!si','<a href="$1" rel="nofollow">$1</a> ',$url);
130}
108// ------------------------------------------------------------------------------------------ 131// ------------------------------------------------------------------------------------------
109// Sniff browser language to display dates in the right format automatically. 132// Sniff browser language to display dates in the right format automatically.
110// (Note that is may not work on your server if the corresponding local is not installed.) 133// (Note that is may not work on your server if the corresponding local is not installed.)
@@ -381,7 +404,7 @@ function getHTTP($url,$timeout=30)
381 { 404 {
382 $options = array('http'=>array('method'=>'GET','timeout' => $timeout)); // Force network timeout 405 $options = array('http'=>array('method'=>'GET','timeout' => $timeout)); // Force network timeout
383 $context = stream_context_create($options); 406 $context = stream_context_create($options);
384 $data=file_get_contents($url,false,$context,-1, 2000000); // We download at most 2 Mb from source. 407 $data=file_get_contents($url,false,$context,-1, 4000000); // We download at most 4 Mb from source.
385 if (!$data) { $lasterror=error_get_last(); return array($lasterror['message'],array(),''); } 408 if (!$data) { $lasterror=error_get_last(); return array($lasterror['message'],array(),''); }
386 $httpStatus=$http_response_header[0]; // eg. "HTTP/1.1 200 OK" 409 $httpStatus=$http_response_header[0]; // eg. "HTTP/1.1 200 OK"
387 $responseHeaders=http_parse_headers($http_response_header); 410 $responseHeaders=http_parse_headers($http_response_header);
@@ -567,6 +590,22 @@ class linkdb implements Iterator, Countable, ArrayAccess
567 krsort($filtered); 590 krsort($filtered);
568 return $filtered; 591 return $filtered;
569 } 592 }
593
594 // Filter by smallHash.
595 // Only 1 article is returned.
596 public function filterSmallHash($smallHash)
597 {
598 $filtered=array();
599 foreach($this->links as $l)
600 {
601 if ($smallHash==smallHash($l['linkdate'])) // Yes, this is ugly and slow
602 {
603 $filtered[$l['linkdate']] = $l;
604 return $filtered;
605 }
606 }
607 return $filtered;
608 }
570 609
571 // Returns the list of all tags 610 // Returns the list of all tags
572 // Output: associative array key=tags, value=0 611 // Output: associative array key=tags, value=0
@@ -1011,7 +1050,7 @@ HTML;
1011 $linkdate = strval(date('Ymd_His')); 1050 $linkdate = strval(date('Ymd_His'));
1012 $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet). 1051 $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet).
1013 $description=''; $tags=''; $private=0; 1052 $description=''; $tags=''; $private=0;
1014 if (parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url; 1053 if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url;
1015 // 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.) 1054 // 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.)
1016 if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http') 1055 if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http')
1017 { 1056 {
@@ -1019,6 +1058,7 @@ HTML;
1019 // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) <head> in html 1058 // FIXME: Decode charset according to specified in either 1) HTTP response headers or 2) <head> in html
1020 if (strpos($status,'200 OK')) $title=html_entity_decode(html_extract_title($data),ENT_QUOTES,'UTF-8'); 1059 if (strpos($status,'200 OK')) $title=html_entity_decode(html_extract_title($data),ENT_QUOTES,'UTF-8');
1021 } 1060 }
1061 if ($url=='') $url='?'.smallHash($linkdate); // In case of empty URL, this is just a text (with a link that point to itself)
1022 $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>0); 1062 $link = array('linkdate'=>$linkdate,'title'=>$title,'url'=>$url,'description'=>$description,'tags'=>$tags,'private'=>0);
1023 } 1063 }
1024 list($editform,$onload)=templateEditForm($link,$link_is_new); 1064 list($editform,$onload)=templateEditForm($link,$link_is_new);
@@ -1236,7 +1276,7 @@ HTML;
1236function templateLinkList() 1276function templateLinkList()
1237{ 1277{
1238 global $LINKSDB; 1278 global $LINKSDB;
1239 1279
1240 // Search according to entered search terms: 1280 // Search according to entered search terms:
1241 $linksToDisplay=array(); 1281 $linksToDisplay=array();
1242 $searched=''; 1282 $searched='';
@@ -1251,6 +1291,10 @@ function templateLinkList()
1251 $tagshtml=''; foreach(explode(' ',trim($_GET['searchtags'])) as $tag) $tagshtml.='<span class="linktag" title="Remove tag"><a href="?removetag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).' <span style="border-left:1px solid #aaa; padding-left:5px; color:#6767A7;">x</span></a></span> '; 1291 $tagshtml=''; foreach(explode(' ',trim($_GET['searchtags'])) as $tag) $tagshtml.='<span class="linktag" title="Remove tag"><a href="?removetag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).' <span style="border-left:1px solid #aaa; padding-left:5px; color:#6767A7;">x</span></a></span> ';
1252 $searched='&nbsp;<b>'.count($linksToDisplay).' results for tags '.$tagshtml.':</b>'; 1292 $searched='&nbsp;<b>'.count($linksToDisplay).' results for tags '.$tagshtml.':</b>';
1253 } 1293 }
1294 elseif (preg_match('/[a-zA-Z0-9-_@]{6}/',$_SERVER["QUERY_STRING"])) // Detect smallHashes in URL
1295 {
1296 $linksToDisplay = $LINKSDB->filterSmallHash($_SERVER["QUERY_STRING"]);
1297 }
1254 else 1298 else
1255 $linksToDisplay = $LINKSDB; // otherwise, display without filtering. 1299 $linksToDisplay = $LINKSDB; // otherwise, display without filtering.
1256 1300
@@ -1273,7 +1317,7 @@ function templateLinkList()
1273 while ($i<$end && $i<count($keys)) 1317 while ($i<$end && $i<count($keys))
1274 { 1318 {
1275 $link = $linksToDisplay[$keys[$i]]; 1319 $link = $linksToDisplay[$keys[$i]];
1276 $description=$link['description']; 1320 $description=text2clickable(htmlspecialchars($link['description']));
1277 $title=$link['title']; 1321 $title=$link['title'];
1278 $classprivate = ($link['private']==0 ? '' : 'class="private"'); 1322 $classprivate = ($link['private']==0 ? '' : 'class="private"');
1279 if (isLoggedIn()) $actions=' <form method="GET" class="buttoneditform"><input type="hidden" name="edit_link" value="'.$link['linkdate'].'"><input type="submit" value="Edit" class="smallbutton"></form>'; 1323 if (isLoggedIn()) $actions=' <form method="GET" class="buttoneditform"><input type="hidden" name="edit_link" value="'.$link['linkdate'].'"><input type="submit" value="Edit" class="smallbutton"></form>';
@@ -1281,9 +1325,10 @@ function templateLinkList()
1281 if ($link['tags']!='') foreach(explode(' ',$link['tags']) as $tag) { $tags.='<span class="linktag" title="Add tag"><a href="?addtag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).'</a></span> '; } 1325 if ($link['tags']!='') foreach(explode(' ',$link['tags']) as $tag) { $tags.='<span class="linktag" title="Add tag"><a href="?addtag='.htmlspecialchars($tag).'">'.htmlspecialchars($tag).'</a></span> '; }
1282 $linklist.='<li '.$classprivate.'>'.thumbnail($link['url']); 1326 $linklist.='<li '.$classprivate.'>'.thumbnail($link['url']);
1283 $linklist.='<div class="linkcontainer"><span class="linktitle"><a href="'.htmlspecialchars($link['url']).'">'.htmlspecialchars($title).'</a></span>'.$actions.'<br>'; 1327 $linklist.='<div class="linkcontainer"><span class="linktitle"><a href="'.htmlspecialchars($link['url']).'">'.htmlspecialchars($title).'</a></span>'.$actions.'<br>';
1284 if ($description!='') $linklist.='<div class="linkdescription">'.nl2br(htmlspecialchars($description)).'</div><br>'; 1328 if ($description!='') $linklist.='<div class="linkdescription">'.nl2br($description).'</div><br>';
1285 if (!HIDE_TIMESTAMPS || isLoggedIn()) $linklist.='<span class="linkdate">'.htmlspecialchars(linkdate2locale($link['linkdate'])).' - </span>'; 1329 if (!HIDE_TIMESTAMPS || isLoggedIn()) $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">'.htmlspecialchars(linkdate2locale($link['linkdate'])).' </a> - </span>';
1286 $linklist.='<span class="linkurl">'.htmlspecialchars($link['url']).'</span><br>'.$tags."</div></li>\n"; 1330 else $linklist.='<span class="linkdate" title="Short link here"><a href="?'.smallHash($link['linkdate']).'">link</a> - </span>';
1331 $linklist.='<span class="linkurl" title="Short link">'.htmlspecialchars($link['url']).'</span><br>'.$tags."</div></li>\n";
1287 $i++; 1332 $i++;
1288 } 1333 }
1289 1334
@@ -1323,6 +1368,7 @@ function thumbnail($url)
1323 { 1368 {
1324 $path = parse_url($url,PHP_URL_PATH); 1369 $path = parse_url($url,PHP_URL_PATH);
1325 if (substr_count($path,'/')==1) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="http://i.imgur.com/'.htmlspecialchars(substr($path,1)).'s.jpg" width="90" height="90"></a></div>'; 1370 if (substr_count($path,'/')==1) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="http://i.imgur.com/'.htmlspecialchars(substr($path,1)).'s.jpg" width="90" height="90"></a></div>';
1371 if (strpos($path,'/gallery/')==0) return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="http://i.imgur.com'.htmlspecialchars(substr($path,8)).'s.jpg" width="90" height="90"></a></div>';
1326 } 1372 }
1327 if ($domain=='i.imgur.com') 1373 if ($domain=='i.imgur.com')
1328 { 1374 {
@@ -1356,6 +1402,15 @@ function thumbnail($url)
1356 return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;"></a></div>'; 1402 return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;"></a></div>';
1357 } 1403 }
1358 1404
1405 // For all other, we try to make a thumbnail of links ending with .jpg/jpeg/png/gif
1406 // Technically speaking, we should download ALL links and check their Content-Type to see if they are images.
1407 // But using the extension will do.
1408 $ext=strtolower(pathinfo($url,PATHINFO_EXTENSION));
1409 if ($ext=='jpg' || $ext=='jpeg' || $ext=='png' || $ext=='gif')
1410 {
1411 $sign = hash_hmac('sha256', $url, $GLOBALS['salt']); // We use the salt to sign data (it's random, secret, and specific to each installation)
1412 return '<div class="thumbnail"><a href="'.htmlspecialchars($url).'"><img src="?do=genthumbnail&hmac='.htmlspecialchars($sign).'&url='.urlencode($url).'" width="120" style="height:auto;"></a></div>';
1413 }
1359 return ''; // No thumbnail. 1414 return ''; // No thumbnail.
1360 1415
1361} 1416}
@@ -1406,8 +1461,10 @@ $(document).ready(function()
1406JS; 1461JS;
1407 } 1462 }
1408 $feedurl=htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']); 1463 $feedurl=htmlspecialchars(serverUrl().$_SERVER['SCRIPT_NAME']);
1409 if (!empty($_GET['searchtags'])) $feedurl.='&searchtags='.$_GET['searchtags']; 1464 $feedsearch='';
1410 elseif (!empty($_GET['searchterm'])) $feedurl.='&searchterm='.$_GET['searchterm']; 1465 if (!empty($_GET['searchtags'])) $feedsearch.='&searchtags='.$_GET['searchtags'];
1466 elseif (!empty($_GET['searchterm'])) $feedsearch.='&searchterm='.$_GET['searchterm'];
1467 $version=shaarli_version;
1411 1468
1412 $title = htmlspecialchars( $GLOBALS['title'] ); 1469 $title = htmlspecialchars( $GLOBALS['title'] );
1413 echo <<<HTML 1470 echo <<<HTML
@@ -1415,12 +1472,12 @@ JS;
1415<head> 1472<head>
1416<title>{$title}</title> 1473<title>{$title}</title>
1417<link rel="alternate" type="application/rss+xml" href="{$feedurl}" /> 1474<link rel="alternate" type="application/rss+xml" href="{$feedurl}" />
1418<link type="text/css" rel="stylesheet" href="shaarli.css" /> 1475<link type="text/css" rel="stylesheet" href="shaarli.css?version={$version}" />
1419{$jsincludes} 1476{$jsincludes}
1420</head> 1477</head>
1421<body {$data['onload']}>{$newversion} 1478<body {$data['onload']}>{$newversion}
1422<div id="pageheader"><div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;">Shaare your links...<br>{$linkcount} links</div> 1479<div id="pageheader"><div style="float:right; font-style:italic; color:#bbb; text-align:right; padding:0 5 0 0;">Shaare your links...<br>{$linkcount} links</div>
1423 <b><i>{$title}</i></b> - <a href="?">Home</a>&nbsp;{$menu}&nbsp;<a href="{$feedurl}?do=rss" style="padding-left:30px;">RSS Feed</a> <a href="{$feedurl}?do=atom" style="padding-left:10px;">ATOM Feed</a> 1480 <b><i>{$title}</i></b> - <a href="?">Home</a>&nbsp;{$menu}&nbsp;<a href="{$feedurl}?do=rss{$feedsearch}" style="padding-left:30px;">RSS Feed</a> <a href="{$feedurl}?do=atom{$feedsearch}" style="padding-left:10px;">ATOM Feed</a>
1424&nbsp;&nbsp; <a href="?do=tagcloud">Tag cloud</a> 1481&nbsp;&nbsp; <a href="?do=tagcloud">Tag cloud</a>
1425{$data['pageheader']} 1482{$data['pageheader']}
1426</div> 1483</div>
@@ -1645,7 +1702,6 @@ function genThumbnail()
1645 1702
1646 // Is this a link to an image, or to a flickr page ? 1703 // Is this a link to an image, or to a flickr page ?
1647 $imageurl=''; 1704 $imageurl='';
1648 logm('url: '.$url);
1649 if (endswith(parse_url($url,PHP_URL_PATH),'.jpg')) 1705 if (endswith(parse_url($url,PHP_URL_PATH),'.jpg'))
1650 { // This is a direct link to an image. eg. http://farm1.static.flickr.com/5/5921913_ac83ed27bd_o.jpg 1706 { // This is a direct link to an image. eg. http://farm1.static.flickr.com/5/5921913_ac83ed27bd_o.jpg
1651 preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches); 1707 preg_match('!(http://farm\d+.static.flickr.com/\d+/\d+_\w+_)\w.jpg!',$url,$matches);
@@ -1671,7 +1727,6 @@ function genThumbnail()
1671 return; 1727 return;
1672 } 1728 }
1673 } 1729 }
1674 else logm('unkown flickr url: '.$url);
1675 } 1730 }
1676 1731
1677 if ($domain=='vimeo.com' ) 1732 if ($domain=='vimeo.com' )
@@ -1695,6 +1750,20 @@ function genThumbnail()
1695 } 1750 }
1696 } 1751 }
1697 } 1752 }
1753
1754 // For all other domains, we try to download the image and make a thumbnail.
1755 list($httpstatus,$headers,$data) = getHTTP($url,30); // We allow 30 seconds max to download (and downloads are limited to 4 Mb)
1756 if (strpos($httpstatus,'200 OK'))
1757 {
1758 $filepath=CACHEDIR.'/'.$thumbname;
1759 file_put_contents($filepath,$data); // Save image to cache.
1760 if (resizeImage($filepath))
1761 {
1762 header('Content-Type: image/jpeg');
1763 echo file_get_contents($filepath);
1764 return;
1765 }
1766 }
1698 1767
1699 // Otherwise, return an empty image (8x8 transparent gif) 1768 // Otherwise, return an empty image (8x8 transparent gif)
1700 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7'); 1769 $blankgif = base64_decode('R0lGODlhCAAIAIAAAP///////yH5BAEKAAEALAAAAAAIAAgAAAIHjI+py+1dAAA7');
@@ -1703,6 +1772,36 @@ function genThumbnail()
1703 echo $blankgif; 1772 echo $blankgif;
1704} 1773}
1705 1774
1775// Make a thumbnail of the image (to width: 120 pixels)
1776// Returns true if success, false otherwise.
1777function resizeImage($filepath)
1778{
1779 if (!function_exists('imagecreatefromjpeg')) return false; // GD not present: no thumbnail possible.
1780
1781 // Trick: some stupid people rename GIF as JPEG... or else.
1782 // So we really try to open each image type whatever the extension is.
1783 $header=file_get_contents($filepath,false,NULL,0,256); // Read first 256 bytes and try to sniff file type.
1784 $im=false;
1785 if (strpos($header,'GIF8')==0) $im = imagecreatefromgif($filepath); // Well this is crude, but it should be enough.
1786 if (strpos($header,'PNG')==1) $im = imagecreatefrompng($filepath);
1787 if (strpos($header,'JFIF')) $im = imagecreatefromjpeg($filepath);
1788 if (!$im) return false; // Unable to open image (corrupted or not an image)
1789 $w = imagesx($im);
1790 $h = imagesy($im);
1791 $nw = 120; // Desired width
1792 $nh = floor(($h*$nw)/$w); // Compute new width/height while keeping ratio
1793 // Resize image:
1794 $im2 = imagecreatetruecolor($nw,$nh);
1795 imagecopyresampled($im2, $im, 0, 0, 0, 0, $nw, $nh, $w, $h);
1796 imageinterlace($im2,true); // For progressive JPEG.
1797 $tempname=$filepath.'_TEMP.jpg';
1798 imagejpeg($im2, $tempname, 90);
1799 imagedestroy($im);
1800 imagedestroy($im2);
1801 rename($tempname,$filepath); // Overwrite original picture with thumbnail.
1802 return true;
1803}
1804
1706// Invalidate caches when the database is changed or the user logs out. 1805// Invalidate caches when the database is changed or the user logs out.
1707// (eg. tags cache). 1806// (eg. tags cache).
1708function invalidateCaches() 1807function invalidateCaches()