From 31ae116fe4ab5cac3d2a85069adae2d211ecc846 Mon Sep 17 00:00:00 2001 From: Bernhard Posselt Date: Tue, 8 Apr 2014 18:50:10 +0200 Subject: migrated database, utility, bootstrap from appframework --- utility/faviconfetcher.php | 151 +++++++++++++++++++++++++++++++++++++++ utility/simplepieapifactory.php | 56 +++++++++++++++ utility/simplepiefilefactory.php | 35 --------- utility/timefactory.php | 42 +++++++++++ 4 files changed, 249 insertions(+), 35 deletions(-) create mode 100644 utility/faviconfetcher.php create mode 100644 utility/simplepieapifactory.php delete mode 100644 utility/simplepiefilefactory.php create mode 100644 utility/timefactory.php (limited to 'utility') diff --git a/utility/faviconfetcher.php b/utility/faviconfetcher.php new file mode 100644 index 000000000..d5d270a49 --- /dev/null +++ b/utility/faviconfetcher.php @@ -0,0 +1,151 @@ +. + * + */ + + +namespace OCA\News\Utility; + + +class FaviconFetcher { + + private $apiFactory; + + + /** + * Inject a factory to build a simplepie file object. This is needed because + * the file object contains logic in its constructor which makes it + * impossible to inject and test + */ + public function __construct(SimplePieAPIFactory $apiFactory) { + $this->apiFactory = $apiFactory; + } + + + /** + * Fetches a favicon from a given URL + * @param string|null $url the url where to fetch it from + */ + public function fetch($url) { + try { + $url = $this->buildURL($url); + } catch (NoValidUrlException $e) { + return null; + } + + $faviconUrl = $this->extractFromPage($url); + + // check the url for a valid image + if($faviconUrl && $this->isImage($faviconUrl)) { + return $faviconUrl; + } elseif($url) { + // try /favicon.ico as fallback + $parts = parse_url($url); + $faviconUrl = $parts['scheme'] . "://" . $parts['host'] . (array_key_exists("port", $parts) ? $parts['port'] : '') . "/favicon.ico"; + + if($this->isImage($faviconUrl)) { + return $faviconUrl; + } + } + + return null; + } + + + /** + * Tries to get a favicon from a page + * @param string $url the url to the page + * @return string the full url to the page + */ + protected function extractFromPage($url) { + if(!$url) { + return null; + } + + $file = $this->apiFactory->getFile($url); + + if($file->body !== '') { + $document = new \DOMDocument(); + @$document->loadHTML($file->body); + + if($document) { + $xpath = new \DOMXpath($document); + $elements = $xpath->query("//link[contains(@rel, 'icon')]"); + + if ($elements->length > 0) { + $iconPath = $elements->item(0)->getAttribute('href'); + $absPath = \SimplePie_Misc::absolutize_url($iconPath, $url); + return $absPath; + } + } + } + } + + + /** + * Test if the file is an image + * @param string $url the url to the file + * @return bool true if image + */ + protected function isImage($url) { + // check for empty urls + if(!$url) { + return false; + } + + $file = $this->apiFactory->getFile($url); + $sniffer = new \SimplePie_Content_Type_Sniffer($file); + return $sniffer->image() !== false; + } + + + /** + * Get HTTP or HTTPS addresses from an incomplete URL + * @param string $url the url that should be built + * @return string a string containing the http or https address + * @throws NoValidUrlException when no valid url can be returned + */ + protected function buildURL($url) { + // trim the right / from the url + $url = trim($url); + $url = rtrim($url, '/'); + + // check for http:// or https:// and validate URL + if (strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0) { + if (filter_var($url, FILTER_VALIDATE_URL)) { + return $url; + } + } elseif (filter_var("http://" . $url, FILTER_VALIDATE_URL)) { + // maybe $url was something like www.example.com + return 'http://' . $url; + } + + // no valid URL was passed in or could be built from $url + throw new NoValidUrlException(); + } + +} + +/** + * Thrown when no valid url was found by faviconfetcher + */ +class NoValidUrlException extends \Exception { +} diff --git a/utility/simplepieapifactory.php b/utility/simplepieapifactory.php new file mode 100644 index 000000000..70e833a4e --- /dev/null +++ b/utility/simplepieapifactory.php @@ -0,0 +1,56 @@ +. + * + */ + + +namespace OCA\News\Utility; + + +class SimplePieAPIFactory { + + /** + * Builds a simplepie file object. This is needed because + * the file object contains logic in its constructor which makes it + * impossible to inject and test + * @return SimplePie_File a new object + */ + public function getFile($url, $timeout=10, $redirects=5, $headers=null, + $useragent=null, $force_fsockopen=false) { + + return new \SimplePie_File($url, $timeout, $redirects, $headers, + $useragent, $force_fsockopen); + } + + + /** + * Returns a new instance of a SimplePie_Core() object. This is needed + * because the class relies on external dependencies which are not passed + * in via the constructor and thus making it nearly impossible to unittest + * code that uses this class + * @return \SimplePie_Core instance + */ + public function getCore() { + return new \SimplePie_Core(); + } + + +} \ No newline at end of file diff --git a/utility/simplepiefilefactory.php b/utility/simplepiefilefactory.php deleted file mode 100644 index a7a051c23..000000000 --- a/utility/simplepiefilefactory.php +++ /dev/null @@ -1,35 +0,0 @@ -. -* -*/ - -namespace OCA\News\Utility; - - -class SimplePieFileFactory { - - public function getFile($url, $timeout, $userAgent = null) { - return new \SimplePie_File($url, $timeout, 5, null, $userAgent); - } - -} diff --git a/utility/timefactory.php b/utility/timefactory.php new file mode 100644 index 000000000..ad97d5915 --- /dev/null +++ b/utility/timefactory.php @@ -0,0 +1,42 @@ +. + * + */ + + +namespace OCA\News\Utility; + + +/** + * Needed to mock calls to time() + */ +class TimeFactory { + + + /** + * @return int the result of a call to time() + */ + public function getTime() { + return time(); + } + + +} \ No newline at end of file -- cgit v1.2.3