diff options
Diffstat (limited to 'vendor/miniflux/picofeed/lib/PicoFeed/Reader')
5 files changed, 414 insertions, 0 deletions
diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Favicon.php b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Favicon.php new file mode 100644 index 00000000..d4ca07db --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Favicon.php @@ -0,0 +1,186 @@ +<?php + +namespace PicoFeed\Reader; + +use DOMXPath; +use PicoFeed\Base; +use PicoFeed\Client\Client; +use PicoFeed\Client\ClientException; +use PicoFeed\Client\Url; +use PicoFeed\Logging\Logger; +use PicoFeed\Parser\XmlParser; + +/** + * Favicon class. + * + * https://en.wikipedia.org/wiki/Favicon + * + * @author Frederic Guillot + */ +class Favicon extends Base +{ + /** + * Valid types for favicon (supported by browsers). + * + * @var array + */ + private $types = array( + 'image/png', + 'image/gif', + 'image/x-icon', + 'image/jpeg', + 'image/jpg', + 'image/svg+xml', + ); + + /** + * Icon binary content. + * + * @var string + */ + private $content = ''; + + /** + * Icon content type. + * + * @var string + */ + private $content_type = ''; + + /** + * Get the icon file content (available only after the download). + * + * @return string + */ + public function getContent() + { + return $this->content; + } + + /** + * Get the icon file type (available only after the download). + * + * @return string + */ + public function getType() + { + foreach ($this->types as $type) { + if (strpos($this->content_type, $type) === 0) { + return $type; + } + } + + return 'image/x-icon'; + } + + /** + * Get data URI (http://en.wikipedia.org/wiki/Data_URI_scheme). + * + * @return string + */ + public function getDataUri() + { + if (empty($this->content)) { + return ''; + } + + return sprintf( + 'data:%s;base64,%s', + $this->getType(), + base64_encode($this->content) + ); + } + + /** + * Download and check if a resource exists. + * + * @param string $url URL + * @return \PicoFeed\Client\Client Client instance + */ + public function download($url) + { + $client = Client::getInstance(); + $client->setConfig($this->config); + + Logger::setMessage(get_called_class().' Download => '.$url); + + try { + $client->execute($url); + } catch (ClientException $e) { + Logger::setMessage(get_called_class().' Download Failed => '.$e->getMessage()); + } + + return $client; + } + + /** + * Check if a remote file exists. + * + * @param string $url URL + * @return bool + */ + public function exists($url) + { + return $this->download($url)->getContent() !== ''; + } + + /** + * Get the icon link for a website. + * + * @param string $website_link URL + * @param string $favicon_link optional URL + * @return string + */ + public function find($website_link, $favicon_link = '') + { + $website = new Url($website_link); + + if ($favicon_link !== '') { + $icons = array($favicon_link); + } else { + $icons = $this->extract($this->download($website->getBaseUrl('/'))->getContent()); + $icons[] = $website->getBaseUrl('/favicon.ico'); + } + + foreach ($icons as $icon_link) { + $icon_link = Url::resolve($icon_link, $website); + $resource = $this->download($icon_link); + $this->content = $resource->getContent(); + $this->content_type = $resource->getContentType(); + + if ($this->content !== '') { + return $icon_link; + } elseif ($favicon_link !== '') { + return $this->find($website_link); + } + } + + return ''; + } + + /** + * Extract the icon links from the HTML. + * + * @param string $html HTML + * @return array + */ + public function extract($html) + { + $icons = array(); + + if (empty($html)) { + return $icons; + } + + $dom = XmlParser::getHtmlDocument($html); + + $xpath = new DOMXpath($dom); + $elements = $xpath->query('//link[@rel="icon" or @rel="shortcut icon" or @rel="Shortcut Icon" or @rel="icon shortcut"]'); + + for ($i = 0; $i < $elements->length; ++$i) { + $icons[] = $elements->item($i)->getAttribute('href'); + } + + return $icons; + } +} diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Reader.php b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Reader.php new file mode 100644 index 00000000..769ffe93 --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/Reader.php @@ -0,0 +1,190 @@ +<?php + +namespace PicoFeed\Reader; + +use DOMXPath; +use PicoFeed\Base; +use PicoFeed\Client\Client; +use PicoFeed\Client\Url; +use PicoFeed\Logging\Logger; +use PicoFeed\Parser\XmlParser; + +/** + * Reader class. + * + * @author Frederic Guillot + */ +class Reader extends Base +{ + /** + * Feed formats for detection. + * + * @var array + */ + private $formats = array( + 'Atom' => '//feed', + 'Rss20' => '//rss[@version="2.0"]', + 'Rss92' => '//rss[@version="0.92"]', + 'Rss91' => '//rss[@version="0.91"]', + 'Rss10' => '//rdf', + ); + + /** + * Download a feed (no discovery). + * + * @param string $url Feed url + * @param string $last_modified Last modified HTTP header + * @param string $etag Etag HTTP header + * @param string $username HTTP basic auth username + * @param string $password HTTP basic auth password + * + * @return \PicoFeed\Client\Client + */ + public function download($url, $last_modified = '', $etag = '', $username = '', $password = '') + { + $url = $this->prependScheme($url); + + return Client::getInstance() + ->setConfig($this->config) + ->setLastModified($last_modified) + ->setEtag($etag) + ->setUsername($username) + ->setPassword($password) + ->execute($url); + } + + /** + * Discover and download a feed. + * + * @param string $url Feed or website url + * @param string $last_modified Last modified HTTP header + * @param string $etag Etag HTTP header + * @param string $username HTTP basic auth username + * @param string $password HTTP basic auth password + * @return Client + * @throws SubscriptionNotFoundException + */ + public function discover($url, $last_modified = '', $etag = '', $username = '', $password = '') + { + $client = $this->download($url, $last_modified, $etag, $username, $password); + + // It's already a feed or the feed was not modified + if (!$client->isModified() || $this->detectFormat($client->getContent())) { + return $client; + } + + // Try to find a subscription + $links = $this->find($client->getUrl(), $client->getContent()); + + if (empty($links)) { + throw new SubscriptionNotFoundException('Unable to find a subscription'); + } + + return $this->download($links[0], $last_modified, $etag, $username, $password); + } + + /** + * Find feed urls inside a HTML document. + * + * @param string $url Website url + * @param string $html HTML content + * + * @return array List of feed links + */ + public function find($url, $html) + { + Logger::setMessage(get_called_class().': Try to discover subscriptions'); + + $dom = XmlParser::getHtmlDocument($html); + $xpath = new DOMXPath($dom); + $links = array(); + + $queries = array( + '//link[@type="application/rss+xml"]', + '//link[@type="application/atom+xml"]', + ); + + foreach ($queries as $query) { + $nodes = $xpath->query($query); + + foreach ($nodes as $node) { + $link = $node->getAttribute('href'); + + if (!empty($link)) { + $feedUrl = new Url($link); + $siteUrl = new Url($url); + + $links[] = $feedUrl->getAbsoluteUrl($feedUrl->isRelativeUrl() ? $siteUrl->getBaseUrl() : ''); + } + } + } + + Logger::setMessage(get_called_class().': '.implode(', ', $links)); + + return $links; + } + + /** + * Get a parser instance. + * + * @param string $url Site url + * @param string $content Feed content + * @param string $encoding HTTP encoding + * + * @return \PicoFeed\Parser\Parser + */ + public function getParser($url, $content, $encoding) + { + $format = $this->detectFormat($content); + + if (empty($format)) { + throw new UnsupportedFeedFormatException('Unable to detect feed format'); + } + + $className = '\PicoFeed\Parser\\'.$format; + + $parser = new $className($content, $encoding, $url); + $parser->setHashAlgo($this->config->getParserHashAlgo()); + $parser->setConfig($this->config); + + return $parser; + } + + /** + * Detect the feed format. + * + * @param string $content Feed content + * + * @return string + */ + public function detectFormat($content) + { + $dom = XmlParser::getHtmlDocument($content); + $xpath = new DOMXPath($dom); + + foreach ($this->formats as $parser_name => $query) { + $nodes = $xpath->query($query); + + if ($nodes->length === 1) { + return $parser_name; + } + } + + return ''; + } + + /** + * Add the prefix "http://" if the end-user just enter a domain name. + * + * @param string $url Url + * @retunr string + */ + public function prependScheme($url) + { + if (!preg_match('%^https?://%', $url)) { + $url = 'http://'.$url; + } + + return $url; + } +} diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Reader/ReaderException.php b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/ReaderException.php new file mode 100644 index 00000000..4f03dbe0 --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/ReaderException.php @@ -0,0 +1,14 @@ +<?php + +namespace PicoFeed\Reader; + +use PicoFeed\PicoFeedException; + +/** + * ReaderException Exception. + * + * @author Frederic Guillot + */ +abstract class ReaderException extends PicoFeedException +{ +} diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Reader/SubscriptionNotFoundException.php b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/SubscriptionNotFoundException.php new file mode 100644 index 00000000..dd77847e --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/SubscriptionNotFoundException.php @@ -0,0 +1,12 @@ +<?php + +namespace PicoFeed\Reader; + +/** + * SubscriptionNotFoundException Exception. + * + * @author Frederic Guillot + */ +class SubscriptionNotFoundException extends ReaderException +{ +} diff --git a/vendor/miniflux/picofeed/lib/PicoFeed/Reader/UnsupportedFeedFormatException.php b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/UnsupportedFeedFormatException.php new file mode 100644 index 00000000..4aa8d87f --- /dev/null +++ b/vendor/miniflux/picofeed/lib/PicoFeed/Reader/UnsupportedFeedFormatException.php @@ -0,0 +1,12 @@ +<?php + +namespace PicoFeed\Reader; + +/** + * UnsupportedFeedFormatException Exception. + * + * @author Frederic Guillot + */ +class UnsupportedFeedFormatException extends ReaderException +{ +} |