2016-12-26 15:44:53 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace Miniflux\Handler\Feed;
|
|
|
|
|
|
|
|
use Miniflux\Helper;
|
|
|
|
use Miniflux\Model;
|
|
|
|
use PicoFeed;
|
|
|
|
use PicoFeed\Config\Config as ReaderConfig;
|
|
|
|
use PicoFeed\Logging\Logger;
|
2016-12-26 22:54:44 +01:00
|
|
|
use PicoFeed\Reader\Favicon;
|
2016-12-26 15:44:53 +01:00
|
|
|
use PicoFeed\Reader\Reader;
|
|
|
|
|
|
|
|
function fetch_feed($url, $download_content = false, $etag = '', $last_modified = '')
|
|
|
|
{
|
|
|
|
$error_message = '';
|
|
|
|
$feed = null;
|
|
|
|
$resource = null;
|
|
|
|
|
|
|
|
try {
|
|
|
|
$reader = new Reader(get_reader_config());
|
|
|
|
$resource = $reader->discover($url, $last_modified, $etag);
|
|
|
|
|
|
|
|
if ($resource->isModified()) {
|
|
|
|
$parser = $reader->getParser(
|
|
|
|
$resource->getUrl(),
|
|
|
|
$resource->getContent(),
|
|
|
|
$resource->getEncoding()
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($download_content) {
|
|
|
|
$parser->enableContentGrabber();
|
|
|
|
}
|
|
|
|
|
|
|
|
$feed = $parser->execute();
|
|
|
|
}
|
|
|
|
} catch (PicoFeed\Client\InvalidCertificateException $e) {
|
|
|
|
$error_message = t('Invalid SSL certificate.');
|
|
|
|
} catch (PicoFeed\Client\InvalidUrlException $e) {
|
|
|
|
$error_message = $e->getMessage();
|
|
|
|
} catch (PicoFeed\Client\MaxRedirectException $e) {
|
|
|
|
$error_message = t('Maximum number of HTTP redirection exceeded.');
|
|
|
|
} catch (PicoFeed\Client\MaxSizeException $e) {
|
|
|
|
$error_message = t('The content size exceeds to maximum allowed size.');
|
|
|
|
} catch (PicoFeed\Client\TimeoutException $e) {
|
|
|
|
$error_message = t('Connection timeout.');
|
2016-12-27 02:59:24 +01:00
|
|
|
} catch (PicoFeed\Client\ForbiddenException $e) {
|
|
|
|
$error_message = t('Not allowed to fetch feed.');
|
|
|
|
} catch (PicoFeed\Client\UnauthorizedException $e) {
|
|
|
|
$error_message = t('Not allowed to fetch feed.');
|
2016-12-26 15:44:53 +01:00
|
|
|
} catch (PicoFeed\Parser\MalformedXmlException $e) {
|
|
|
|
$error_message = t('Feed is malformed.');
|
|
|
|
} catch (PicoFeed\Reader\SubscriptionNotFoundException $e) {
|
|
|
|
$error_message = t('Unable to find a subscription.');
|
|
|
|
} catch (PicoFeed\Reader\UnsupportedFeedFormatException $e) {
|
|
|
|
$error_message = t('Unable to detect the feed format.');
|
2016-12-27 02:59:24 +01:00
|
|
|
} catch (PicoFeed\PicoFeedException $e) {
|
|
|
|
$error_message = $e->getMessage();
|
2016-12-26 15:44:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return array($feed, $resource, $error_message);
|
|
|
|
}
|
|
|
|
|
|
|
|
function create_feed($user_id, $url, $download_content = false, $rtl = false, $cloak_referrer = false, array $feed_group_ids = array(), $group_name = null)
|
|
|
|
{
|
|
|
|
$feed_id = null;
|
2017-01-04 02:07:56 +01:00
|
|
|
$url = trim($url);
|
2016-12-26 15:44:53 +01:00
|
|
|
list($feed, $resource, $error_message) = fetch_feed($url, $download_content);
|
|
|
|
|
|
|
|
if ($feed !== null) {
|
|
|
|
$feed_id = Model\Feed\create(
|
|
|
|
$user_id,
|
|
|
|
$feed,
|
|
|
|
$resource->getEtag(),
|
|
|
|
$resource->getLastModified(),
|
2016-12-29 02:24:00 +01:00
|
|
|
$resource->getExpiration()->getTimestamp(),
|
2016-12-26 15:44:53 +01:00
|
|
|
$rtl,
|
|
|
|
$download_content,
|
|
|
|
$cloak_referrer
|
|
|
|
);
|
|
|
|
|
|
|
|
if ($feed_id === -1) {
|
|
|
|
$error_message = t('This subscription already exists.');
|
|
|
|
} else if ($feed_id === false) {
|
|
|
|
$error_message = t('Unable to save this subscription in the database.');
|
|
|
|
} else {
|
2016-12-26 22:54:44 +01:00
|
|
|
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
|
2016-12-26 15:44:53 +01:00
|
|
|
|
2016-12-29 21:45:09 +01:00
|
|
|
if (! empty($feed_group_ids) || ! empty($group_name)) {
|
2016-12-26 15:44:53 +01:00
|
|
|
Model\Group\update_feed_groups($user_id, $feed_id, $feed_group_ids, $group_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array($feed_id, $error_message);
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_feed($user_id, $feed_id)
|
|
|
|
{
|
|
|
|
$subscription = Model\Feed\get_feed($user_id, $feed_id);
|
|
|
|
|
2017-01-15 20:00:32 +01:00
|
|
|
if ($subscription['enabled'] == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-12-26 15:44:53 +01:00
|
|
|
list($feed, $resource, $error_message) = fetch_feed(
|
|
|
|
$subscription['feed_url'],
|
|
|
|
(bool) $subscription['download_content'],
|
|
|
|
$subscription['etag'],
|
|
|
|
$subscription['last_modified']
|
|
|
|
);
|
|
|
|
|
|
|
|
if (! empty($error_message)) {
|
2016-12-28 04:20:25 +01:00
|
|
|
$error_count = $subscription['parsing_error'] + 1;
|
2016-12-26 15:44:53 +01:00
|
|
|
Model\Feed\update_feed($user_id, $feed_id, array(
|
2016-12-28 03:59:09 +01:00
|
|
|
'last_checked' => time(),
|
2016-12-28 04:20:25 +01:00
|
|
|
'parsing_error' => $error_count,
|
2016-12-28 03:59:09 +01:00
|
|
|
'parsing_error_message' => $error_message,
|
2016-12-28 04:20:25 +01:00
|
|
|
'enabled' => $error_count > SUBSCRIPTION_DISABLE_THRESHOLD_ERROR ? 0 : 1,
|
2016-12-26 15:44:53 +01:00
|
|
|
));
|
|
|
|
|
|
|
|
return false;
|
2017-01-15 20:00:32 +01:00
|
|
|
} else if (Model\Feed\is_duplicated_feed($user_id, $feed_id, $resource->getUrl())) {
|
|
|
|
Model\Feed\update_feed($user_id, $feed_id, array(
|
|
|
|
'enabled' => 0,
|
|
|
|
'last_checked' => time(),
|
|
|
|
'parsing_error' => 1,
|
|
|
|
'parsing_error_message' => t('Duplicated feed'),
|
|
|
|
));
|
2016-12-26 15:44:53 +01:00
|
|
|
} else {
|
|
|
|
Model\Feed\update_feed($user_id, $feed_id, array(
|
2016-12-28 03:59:09 +01:00
|
|
|
'feed_url' => $resource->getUrl(),
|
|
|
|
'etag' => $resource->getEtag(),
|
|
|
|
'last_modified' => $resource->getLastModified(),
|
|
|
|
'last_checked' => time(),
|
2016-12-29 02:24:00 +01:00
|
|
|
'expiration' => $resource->getExpiration()->getTimestamp(),
|
2016-12-28 03:59:09 +01:00
|
|
|
'parsing_error' => 0,
|
|
|
|
'parsing_error_message' => '',
|
2016-12-26 15:44:53 +01:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($feed !== null) {
|
|
|
|
Model\Item\update_feed_items($user_id, $feed_id, $feed->getItems(), $subscription['rtl']);
|
2016-12-26 22:54:44 +01:00
|
|
|
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
|
2016-12-26 15:44:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_feeds($user_id, $limit = null)
|
|
|
|
{
|
2016-12-29 02:24:00 +01:00
|
|
|
foreach (Model\Feed\get_feed_ids_to_refresh($user_id, $limit) as $feed_id) {
|
2016-12-26 15:44:53 +01:00
|
|
|
update_feed($user_id, $feed_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-26 22:54:44 +01:00
|
|
|
function fetch_favicon($feed_id, $site_url, $icon_link)
|
|
|
|
{
|
|
|
|
if (Helper\bool_config('favicons') && ! Model\Favicon\has_favicon($feed_id)) {
|
|
|
|
$favicon = new Favicon();
|
2017-01-15 22:05:50 +01:00
|
|
|
$icon_url = $favicon->find($site_url, $icon_link);
|
2016-12-26 22:54:44 +01:00
|
|
|
|
2017-01-15 22:05:50 +01:00
|
|
|
if (! empty($icon_url)) {
|
2016-12-26 22:54:44 +01:00
|
|
|
Model\Favicon\create_feed_favicon($feed_id, $favicon->getType(), $favicon->getContent());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-26 15:44:53 +01:00
|
|
|
function get_reader_config()
|
|
|
|
{
|
|
|
|
$config = new ReaderConfig;
|
|
|
|
$config->setTimezone(Helper\config('timezone'));
|
|
|
|
|
|
|
|
// Client
|
|
|
|
$config->setClientTimeout(HTTP_TIMEOUT);
|
|
|
|
$config->setClientUserAgent(HTTP_USER_AGENT);
|
|
|
|
$config->setMaxBodySize(HTTP_MAX_RESPONSE_SIZE);
|
|
|
|
|
|
|
|
// Grabber
|
|
|
|
$config->setGrabberRulesFolder(RULES_DIRECTORY);
|
|
|
|
|
|
|
|
// Proxy
|
|
|
|
$config->setProxyHostname(PROXY_HOSTNAME);
|
|
|
|
$config->setProxyPort(PROXY_PORT);
|
|
|
|
$config->setProxyUsername(PROXY_USERNAME);
|
|
|
|
$config->setProxyPassword(PROXY_PASSWORD);
|
|
|
|
|
|
|
|
// Filter
|
|
|
|
$config->setFilterIframeWhitelist(Model\Config\get_iframe_whitelist());
|
|
|
|
|
|
|
|
// Parser
|
|
|
|
$config->setParserHashAlgo('crc32b');
|
|
|
|
|
|
|
|
if (DEBUG_MODE) {
|
|
|
|
Logger::enable();
|
|
|
|
}
|
|
|
|
|
|
|
|
return $config;
|
|
|
|
}
|