Favicon.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <?php
  2. namespace Favicon;
  3. class Favicon
  4. {
  5. protected $url = '';
  6. protected $cacheDir;
  7. protected $cacheTimeout;
  8. protected $dataAccess;
  9. public function __construct($args = array())
  10. {
  11. if (isset($args['url'])) {
  12. $this->url = $args['url'];
  13. }
  14. $this->cacheDir = __DIR__ . '/../../resources/cache';
  15. $this->dataAccess = new DataAccess();
  16. }
  17. public function cache($args = array()) {
  18. if (isset($args['dir'])) {
  19. $this->cacheDir = $args['dir'];
  20. }
  21. if (!empty($args['timeout'])) {
  22. $this->cacheTimeout = $args['timeout'];
  23. } else {
  24. $this->cacheTimeout = 0;
  25. }
  26. }
  27. public static function baseUrl($url, $path = false)
  28. {
  29. $return = '';
  30. if (!$url = parse_url($url)) {
  31. return FALSE;
  32. }
  33. // Scheme
  34. $scheme = isset($url['scheme']) ? strtolower($url['scheme']) : null;
  35. if ($scheme != 'http' && $scheme != 'https') {
  36. return FALSE;
  37. }
  38. $return .= "{$scheme}://";
  39. // Username and password
  40. if (isset($url['user'])) {
  41. $return .= $url['user'];
  42. if (isset($url['pass'])) {
  43. $return .= ":{$url['pass']}";
  44. }
  45. $return .= '@';
  46. }
  47. // Hostname
  48. if( !isset($url['host']) ) {
  49. return FALSE;
  50. }
  51. $return .= $url['host'];
  52. // Port
  53. if (isset($url['port'])) {
  54. $return .= ":{$url['port']}";
  55. }
  56. // Path
  57. if( $path && isset($url['path']) ) {
  58. $return .= $url['path'];
  59. }
  60. $return .= '/';
  61. return $return;
  62. }
  63. public function info($url)
  64. {
  65. if(empty($url) || $url === false) {
  66. return false;
  67. }
  68. $max_loop = 5;
  69. // Discover real status by following redirects.
  70. $loop = TRUE;
  71. while ($loop && $max_loop-- > 0) {
  72. $headers = $this->dataAccess->retrieveHeader($url);
  73. if (empty($headers)) {
  74. return false;
  75. }
  76. $exploded = explode(' ', $headers[0]);
  77. if( !isset($exploded[1]) ) {
  78. return false;
  79. }
  80. list(,$status) = $exploded;
  81. switch ($status) {
  82. case '301':
  83. case '302':
  84. $url = isset($headers['location']) ? $headers['location'] : '';
  85. break;
  86. default:
  87. $loop = FALSE;
  88. break;
  89. }
  90. }
  91. return array('status' => $status, 'url' => $url);
  92. }
  93. public function endRedirect($url) {
  94. $out = $this->info($url);
  95. return !empty($out['url']) ? $out['url'] : false;
  96. }
  97. /**
  98. * Find remote (or cached) favicon
  99. * @return favicon URL, false if nothing was found
  100. **/
  101. public function get($url = '')
  102. {
  103. // URLs passed to this method take precedence.
  104. if (!empty($url)) {
  105. $this->url = $url;
  106. }
  107. // Get the base URL without the path for clearer concatenations.
  108. $original = rtrim($this->baseUrl($this->url, true), '/');
  109. $url = rtrim($this->endRedirect($this->baseUrl($this->url, false)), '/');
  110. if(($favicon = $this->checkCache($url)) || ($favicon = $this->getFavicon($url))) {
  111. $base = true;
  112. }
  113. elseif(($favicon = $this->checkCache($original)) || ($favicon = $this->getFavicon($original, false))) {
  114. $base = false;
  115. }
  116. else
  117. return false;
  118. // Save cache if necessary
  119. $cache = $this->cacheDir . '/' . md5($base ? $url : $original);
  120. if ($this->cacheTimeout && !file_exists($cache) || (is_writable($cache) && time() - filemtime($cache) > $this->cacheTimeout)) {
  121. $this->dataAccess->saveCache($cache, $favicon);
  122. }
  123. return $favicon;
  124. }
  125. private function getFavicon($url, $checkDefault = true) {
  126. $favicon = false;
  127. if(empty($url)) {
  128. return false;
  129. }
  130. // Try /favicon.ico first.
  131. if( $checkDefault ) {
  132. $info = $this->info("{$url}/favicon.ico");
  133. if ($info['status'] == '200') {
  134. $favicon = $info['url'];
  135. }
  136. }
  137. // See if it's specified in a link tag in domain url.
  138. if (!$favicon) {
  139. $favicon = $this->getInPage($url);
  140. }
  141. // Make sure the favicon is an absolute URL.
  142. if( $favicon && filter_var($favicon, FILTER_VALIDATE_URL) === false ) {
  143. $favicon = $url . '/' . $favicon;
  144. }
  145. // Sometimes people lie, so check the status.
  146. // And sometimes, it's not even an image. Sneaky bastards!
  147. // If cacheDir isn't writable, that's not our problem
  148. if ($favicon && is_writable($this->cacheDir) && !$this->checkImageMType($favicon)) {
  149. $favicon = false;
  150. }
  151. return $favicon;
  152. }
  153. private function getInPage($url) {
  154. $html = $this->dataAccess->retrieveUrl("{$url}/");
  155. preg_match('!<head.*?>.*</head>!ims', $html, $match);
  156. if(empty($match) || count($match) == 0) {
  157. return false;
  158. }
  159. $head = $match[0];
  160. $dom = new \DOMDocument();
  161. // Use error supression, because the HTML might be too malformed.
  162. if (@$dom->loadHTML($head)) {
  163. $links = $dom->getElementsByTagName('link');
  164. foreach ($links as $link) {
  165. if ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'shortcut icon') {
  166. return $link->getAttribute('href');
  167. } elseif ($link->hasAttribute('rel') && strtolower($link->getAttribute('rel')) == 'icon') {
  168. return $link->getAttribute('href');
  169. } elseif ($link->hasAttribute('href') && strpos($link->getAttribute('href'), 'favicon') !== FALSE) {
  170. return $link->getAttribute('href');
  171. }
  172. }
  173. }
  174. return false;
  175. }
  176. private function checkCache($url) {
  177. if ($this->cacheTimeout) {
  178. $cache = $this->cacheDir . '/' . md5($url);
  179. if (file_exists($cache) && is_readable($cache) && (time() - filemtime($cache) < $this->cacheTimeout)) {
  180. return $this->dataAccess->readCache($cache);
  181. }
  182. }
  183. return false;
  184. }
  185. private function checkImageMType($url) {
  186. $tmpFile = $this->cacheDir . '/tmp.ico';
  187. $fileContent = $this->dataAccess->retrieveUrl($url);
  188. $this->dataAccess->saveCache($tmpFile, $fileContent);
  189. $finfo = finfo_open(FILEINFO_MIME_TYPE);
  190. $isImage = strpos(finfo_file($finfo, $tmpFile), 'image') !== false;
  191. finfo_close($finfo);
  192. unlink($tmpFile);
  193. return $isImage;
  194. }
  195. /**
  196. * @return mixed
  197. */
  198. public function getCacheDir()
  199. {
  200. return $this->cacheDir;
  201. }
  202. /**
  203. * @param mixed $cacheDir
  204. */
  205. public function setCacheDir($cacheDir)
  206. {
  207. $this->cacheDir = $cacheDir;
  208. }
  209. /**
  210. * @return mixed
  211. */
  212. public function getCacheTimeout()
  213. {
  214. return $this->cacheTimeout;
  215. }
  216. /**
  217. * @param mixed $cacheTimeout
  218. */
  219. public function setCacheTimeout($cacheTimeout)
  220. {
  221. $this->cacheTimeout = $cacheTimeout;
  222. }
  223. /**
  224. * @return string
  225. */
  226. public function getUrl()
  227. {
  228. return $this->url;
  229. }
  230. /**
  231. * @param string $url
  232. */
  233. public function setUrl($url)
  234. {
  235. $this->url = $url;
  236. }
  237. /**
  238. * @param DataAccess $dataAccess
  239. */
  240. public function setDataAccess($dataAccess)
  241. {
  242. $this->dataAccess = $dataAccess;
  243. }
  244. }