diff --git a/inc/DataAccess.php b/inc/DataAccess.php new file mode 100644 index 0000000..2bfdf64 --- /dev/null +++ b/inc/DataAccess.php @@ -0,0 +1,40 @@ +set_context(); + return @file_get_contents($url); + } + + public function retrieveHeader($url) { + $this->set_context(); + return @get_headers($url, TRUE); + } + + public function saveCache($file, $data) { + file_put_contents($file, $data); + } + + public function readCache($file) { + return file_get_contents($file); + } + + private function set_context() { + stream_context_set_default( + array( + 'http' => array( + 'method' => 'GET', + 'timeout' => 10, + 'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Favicon; +https://github.com/ArthurHoaro/favicon) Gecko/20100101 Firefox/32.0\r\n", + ) + ) + ); + } +} \ No newline at end of file diff --git a/inc/Favicon.php b/inc/Favicon.php new file mode 100644 index 0000000..7ea6ccf --- /dev/null +++ b/inc/Favicon.php @@ -0,0 +1,293 @@ +url = $args['url']; + } + + $this->cacheDir = __DIR__ . '/../../resources/cache'; + $this->dataAccess = new DataAccess(); + } + + public function cache($args = array()) { + if (isset($args['dir'])) { + $this->cacheDir = $args['dir']; + } + + if (!empty($args['timeout'])) { + $this->cacheTimeout = $args['timeout']; + } else { + $this->cacheTimeout = 0; + } + } + + public static function baseUrl($url, $path = false) + { + $return = ''; + + if (!$url = parse_url($url)) { + return FALSE; + } + + // Scheme + $scheme = isset($url['scheme']) ? strtolower($url['scheme']) : null; + if ($scheme != 'http' && $scheme != 'https') { + + return FALSE; + } + $return .= "{$scheme}://"; + + // Username and password + if (isset($url['user'])) { + $return .= $url['user']; + if (isset($url['pass'])) { + $return .= ":{$url['pass']}"; + } + $return .= '@'; + } + + // Hostname + if( !isset($url['host']) ) { + return FALSE; + } + + $return .= $url['host']; + + // Port + if (isset($url['port'])) { + $return .= ":{$url['port']}"; + } + + // Path + if( $path && isset($url['path']) ) { + $return .= $url['path']; + } + $return .= '/'; + + return $return; + } + + public function info($url) + { + if(empty($url) || $url === false) { + return false; + } + + $max_loop = 5; + + // Discover real status by following redirects. + $loop = TRUE; + while ($loop && $max_loop-- > 0) { + $headers = $this->dataAccess->retrieveHeader($url); + $exploded = explode(' ', $headers[0]); + + if( !isset($exploded[1]) ) { + return false; + } + list(,$status) = $exploded; + + switch ($status) { + case '301': + case '302': + $url = $headers['Location']; + break; + default: + $loop = FALSE; + break; + } + } + + return array('status' => $status, 'url' => $url); + } + + public function endRedirect($url) { + $out = $this->info($url); + return !empty($out['url']) ? $out['url'] : false; + } + + /** + * Find remote (or cached) favicon + * @return favicon URL, false if nothing was found + **/ + public function get($url = '') + { + // URLs passed to this method take precedence. + if (!empty($url)) { + $this->url = $url; + } + + // Get the base URL without the path for clearer concatenations. + $original = rtrim($this->baseUrl($this->url, true), '/'); + $url = rtrim($this->endRedirect($this->baseUrl($this->url, false)), '/'); + + if(($favicon = $this->checkCache($url)) || ($favicon = $this->getFavicon($url))) { + $base = true; + } + elseif(($favicon = $this->checkCache($original)) || ($favicon = $this->getFavicon($original, false))) { + $base = false; + } + else + return false; + + // Save cache if necessary + $cache = $this->cacheDir . '/' . md5($base ? $url : $original); + if ($this->cacheTimeout && !file_exists($cache) || (is_writable($cache) && time() - filemtime($cache) > $this->cacheTimeout)) { + $this->dataAccess->saveCache($cache, $favicon); + } + + return $favicon; + } + + private function getFavicon($url, $checkDefault = true) { + $favicon = false; + + if(empty($url)) { + return false; + } + + // Try /favicon.ico first. + if( $checkDefault ) { + $info = $this->info("{$url}/favicon.ico"); + if ($info['status'] == '200') { + $favicon = $info['url']; + } + } + + // See if it's specified in a link tag in domain url. + if (!$favicon) { + $favicon = $this->getInPage($url); + } + + // Make sure the favicon is an absolute URL. + if( $favicon && filter_var($favicon, FILTER_VALIDATE_URL) === false ) { + $favicon = $url . '/' . $favicon; + } + + // Sometimes people lie, so check the status. + // And sometimes, it's not even an image. Sneaky bastards! + // If cacheDir isn't writable, that's not our problem + if ($favicon && is_writable($this->cacheDir) && !$this->checkImageMType($favicon)) { + $favicon = false; + } + + return $favicon; + } + + private function getInPage($url) { + $html = $this->dataAccess->retrieveUrl("{$url}/"); + preg_match('!.*!ims', $html, $match); + + if(empty($match) || count($match) == 0) { + return false; + } + + $head = $match[0]; + + $dom = new \DOMDocument(); + // Use error supression, because the HTML might be too malformed. + if (@$dom->loadHTML($head)) { + $links = $dom->getElementsByTagName('link'); + foreach ($links as $link) { + if ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'shortcut icon') { + return $link->getAttribute('href'); + } elseif ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'icon') { + return $link->getAttribute('href'); + } elseif ($link->hasAttribute('href') && strpos($link->getAttribute('href'), 'favicon') !== FALSE) { + return $link->getAttribute('href'); + } + } + } + return false; + } + + private function checkCache($url) { + if ($this->cacheTimeout) { + $cache = $this->cacheDir . '/' . md5($url); + if (file_exists($cache) && is_readable($cache) && (time() - filemtime($cache) < $this->cacheTimeout)) { + return $this->dataAccess->readCache($cache); + } + } + return false; + } + + private function checkImageMType($url) { + $tmpFile = $this->cacheDir . '/tmp.ico'; + + $fileContent = $this->dataAccess->retrieveUrl($url); + $this->dataAccess->saveCache($tmpFile, $fileContent); + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $isImage = strpos(finfo_file($finfo, $tmpFile), 'image') !== false; + finfo_close($finfo); + + unlink($tmpFile); + + return $isImage; + } + + /** + * @return mixed + */ + public function getCacheDir() + { + return $this->cacheDir; + } + + /** + * @param mixed $cacheDir + */ + public function setCacheDir($cacheDir) + { + $this->cacheDir = $cacheDir; + } + + /** + * @return mixed + */ + public function getCacheTimeout() + { + return $this->cacheTimeout; + } + + /** + * @param mixed $cacheTimeout + */ + public function setCacheTimeout($cacheTimeout) + { + $this->cacheTimeout = $cacheTimeout; + } + + /** + * @return string + */ + public function getUrl() + { + return $this->url; + } + + /** + * @param string $url + */ + public function setUrl($url) + { + $this->url = $url; + } + + /** + * @param DataAccess $dataAccess + */ + public function setDataAccess($dataAccess) + { + $this->dataAccess = $dataAccess; + } +} diff --git a/inc/shaarli.css b/inc/shaarli.css index cdc0579..c4b4aa7 100644 --- a/inc/shaarli.css +++ b/inc/shaarli.css @@ -69,9 +69,9 @@ h1 { font-size:20pt; font-weight:bold; font-style:italic; margin-bottom:20px; } /* Small tab on the left of each link with edit/delete buttons. */ .button_edit, .button_delete { border-radius:0; box-shadow:none; border-style:none; border-width:0; padding:0; background:none; } -.linkeditbuttons { - position:absolute; - left:-1px; +.linkeditbuttons { + position:absolute; + left:-1px; padding:4px 2px 2px 2px; background-color:#f0f0f0; @@ -111,7 +111,7 @@ cursor:pointer; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); width:auto; padding:0 10px 5px 10px; - margin: auto; + margin: auto; } #pageheader a @@ -282,7 +282,7 @@ font-size:9pt; .thumbnail { float:left; margin-right: 10px; } .linkcontainer { position: static; margin-left:130px; } */ - +.favicon {width:16px;height:16px;margin-right:0.1em;} @@ -305,7 +305,7 @@ font-size:9pt; background-color: transparent; background-color: rgba(0, 0, 0, 0.4); /* FF3+, Saf3+, Opera 10.10+, Chrome, IE9 */ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#66000000,endColorstr=#66000000); /* IE6–IE9 */ -text-shadow:2px 2px 1px #000000; +text-shadow:2px 2px 1px #000000; } /* Minimal customisation for jQuery widgets */ @@ -337,7 +337,7 @@ box-shadow:2px 2px 20px 2px #333333; div.daily { - font-family: Georgia, 'DejaVu Serif', Norasi, serif; + font-family: Georgia, 'DejaVu Serif', Norasi, serif; background-color: #E6D6BE; /* Background paper texture by BashCorpo: http://www.bashcorpo.dk/textures.php @@ -356,7 +356,7 @@ div.daily #daily_col3 { float:left;position:relative; width:33%;} div.dailyAbout -{ +{ float:left; border: 1px solid black; font-size: 8pt; @@ -364,21 +364,21 @@ div.dailyAbout left:10px; top: 15px; padding: 5px 5px 5px 5px; - text-align:center; + text-align:center; } div.dailyAbout a { color: #890500; } div.dailyTitle - { + { font-weight: bold; font-size: 44pt; - text-align:center; + text-align:center; padding:10px 20px 0px 20px; } div.dailyDate - { + { font-size: 12pt; - font-weight:bold; - text-align:center; + font-weight:bold; + text-align:center; padding:0px 20px 30px 20px; } @@ -392,19 +392,19 @@ div.dailyEntry div.dailyEntry a { text-decoration:none; color: #890500; } div.dailyEntryTags { font-size:7.75pt; } div.dailyEntryTitle { font-size:18pt; font-weight:bold;} -div.dailyEntryThumbnail -{ - width:100%; - text-align:center; - background-color:rgb(128,128,128); +div.dailyEntryThumbnail +{ + width:100%; + text-align:center; + background-color:rgb(128,128,128); background:url(../images/50pc_transparent.png); padding:4px 0px 2px 0px; } div.dailyEntryDescription -{ +{ margin-top: 10px; - margin-bottom: 30px; - text-align:justify; + margin-bottom: 30px; + text-align:justify; overflow:auto; } @@ -414,7 +414,7 @@ div.dailyEntryDescription } /* For lazy images loading in picture wall. - using http://www.appelsiini.net/projects/lazyload + using http://www.appelsiini.net/projects/lazyload */ .lazyimage { display:none; } @@ -463,7 +463,7 @@ div.dailyDate { font-size: 11pt;padding:0px; display:block; } div.dailyEntryTitle { font-size:16pt; font-weight:bold;} div.dailyEntryDescription { font-size:10pt; } -} +} /* Highlight search results */ .highlight { background-color: #FFFF33; } diff --git a/index.php b/index.php index c102e42..09084c6 100644 --- a/index.php +++ b/index.php @@ -22,9 +22,11 @@ $GLOBALS['config']['BAN_DURATION'] = 1800; // Ban duration for IP address after $GLOBALS['config']['OPEN_SHAARLI'] = false; // If true, anyone can add/edit/delete links without having to login $GLOBALS['config']['HIDE_TIMESTAMPS'] = false; // If true, the moment when links were saved are not shown to users that are not logged in. $GLOBALS['config']['ENABLE_THUMBNAILS'] = true; // Enable thumbnails in links. +$GLOBALS['config']['ENABLE_FAVICON'] = true; // Enable favicon in links. $GLOBALS['config']['CACHEDIR'] = 'cache'; // Cache directory for thumbnails for SLOW services (like flickr) $GLOBALS['config']['PAGECACHE'] = 'pagecache'; // Page cache directory. $GLOBALS['config']['ENABLE_LOCALCACHE'] = true; // Enable Shaarli to store thumbnail in a local cache. Disable to reduce webspace usage. + // Care if favicon is active and local cache are false serve page can be long $GLOBALS['config']['PUBSUBHUB_URL'] = ''; // PubSubHubbub support. Put an empty string to disable, or put your hub url here to enable. $GLOBALS['config']['UPDATECHECK_FILENAME'] = $GLOBALS['config']['DATADIR'].'/lastupdatecheck.txt'; // For updates check of Shaarli. $GLOBALS['config']['UPDATECHECK_INTERVAL'] = 86400 ; // Updates check frequency for Shaarli. 86400 seconds=24 hours @@ -344,7 +346,7 @@ function isLoggedIn() } // Force logout. -function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); } +function logout() { if (isset($_SESSION)) { unset($_SESSION['uid']); unset($_SESSION['ip']); unset($_SESSION['username']); unset($_SESSION['privateonly']); } setcookie('shaarli_staySignedIn', FALSE, 0, WEB_PATH); } @@ -913,7 +915,7 @@ function showRSS() else $linksToDisplay = $LINKSDB; $nblinksToDisplay = 50; // Number of links to display. if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. - { + { $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ; } @@ -988,7 +990,7 @@ function showATOM() else $linksToDisplay = $LINKSDB; $nblinksToDisplay = 50; // Number of links to display. if (!empty($_GET['nb'])) // In URL, you can specificy the number of links. Example: nb=200 or nb=all for all links. - { + { $nblinksToDisplay = $_GET['nb']=='all' ? count($linksToDisplay) : max($_GET['nb']+0,1) ; } @@ -1563,7 +1565,7 @@ function renderPage() $title = (empty($_GET['title']) ? '' : $_GET['title'] ); // Get title if it was provided in URL (by the bookmarklet). $description = (empty($_GET['description']) ? '' : $_GET['description']); // Get description if it was provided in URL (by the bookmarklet). [Bronco added that] $tags = (empty($_GET['tags']) ? '' : $_GET['tags'] ); // Get tags if it was provided in URL - $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL + $private = (!empty($_GET['private']) && $_GET['private'] === "1" ? 1 : 0); // Get private if it was provided in URL if (($url!='') && parse_url($url,PHP_URL_SCHEME)=='') $url = 'http://'.$url; // If this is an HTTP link, we try go get the page to extact the title (otherwise we will to straight to the edit form.) if (empty($title) && parse_url($url,PHP_URL_SCHEME)=='http') @@ -1574,7 +1576,7 @@ function renderPage() { // Look for charset in html header. preg_match('##Usi', $data, $meta); - + // If found, extract encoding. if (!empty($meta[0])) { @@ -1584,7 +1586,7 @@ function renderPage() $html_charset = (!empty($enc[1])) ? strtolower($enc[1]) : 'utf-8'; } else { $html_charset = 'utf-8'; } - + // Extract title $title = html_extract_title($data); if (!empty($title)) @@ -2052,6 +2054,42 @@ function lazyThumbnail($url,$href=false) return $html; } +function returnFavicon($url){ + if(!$GLOBALS['config']['ENABLE_FAVICON']){ + return; + } + $faviconHash = md5($url); + $path = substr($faviconHash, 0,2).'/'.substr($faviconHash, 2,2); + $faviconPath = $GLOBALS['config']['CACHEDIR'].'/'.$path.'/'.$faviconHash.'.ico'; + if($GLOBALS['config']['ENABLE_LOCALCACHE'] === true && file_exists($faviconPath)){ + $content = file_get_contents($faviconPath); + return 'favicon'; + } + if(file_exists($GLOBALS['config']['CACHEDIR'].'/'.$path.'/'.$faviconHash)){ + return; + } + require_once 'inc/DataAccess.php'; + require_once 'inc/Favicon.php'; + $favicon = new \Favicon\Favicon(); + $urlOfFavicon = $favicon->get($url); + if(!$urlOfFavicon){ + if($GLOBALS['config']['ENABLE_LOCALCACHE'] === true){ + mkdir($GLOBALS['config']['CACHEDIR'].'/'.$path,0777,true); + touch($GLOBALS['config']['CACHEDIR'].'/'.$path.'/'.$faviconHash); + return; + } else { + return; + } + } + if($GLOBALS['config']['ENABLE_LOCALCACHE'] === true && !is_dir($GLOBALS['config']['CACHEDIR'].'/'.$path.'/')){ + mkdir($GLOBALS['config']['CACHEDIR'].'/'.$path,0777,true); + } + $content = file_get_contents($urlOfFavicon); + if($GLOBALS['config']['ENABLE_LOCALCACHE'] === true){ + file_put_contents($faviconPath, $content); + } + return 'favicon'; +} // ----------------------------------------------------------------------------------------------- // Installation diff --git a/tpl/linklist.html b/tpl/linklist.html index ddc38cb..73c6092 100644 --- a/tpl/linklist.html +++ b/tpl/linklist.html @@ -40,7 +40,7 @@ {/if} - {$value.title|htmlspecialchars} + {function="returnFavicon($value.url)"}{$value.title|htmlspecialchars}
{if="$value.description"}
{$value.description}
{/if} {if="!$GLOBALS['config']['HIDE_TIMESTAMPS'] || isLoggedIn()"} @@ -48,8 +48,8 @@ {else} permalink - {/if} -
- +
- {$value.url|htmlspecialchars}
{if="$value.tags"}
@@ -65,12 +65,12 @@
- {include="page.footer"} + {include="page.footer"}