miniflux-legacy/models/feed.php

499 lines
12 KiB
PHP
Raw Normal View History

<?php
namespace Model\Feed;
2015-04-11 16:42:23 +02:00
use UnexpectedValueException;
2014-12-24 23:54:27 +01:00
use Model\Config;
use Model\Item;
use Model\Group;
2015-12-07 14:18:27 +01:00
use Helper;
use SimpleValidator\Validator;
use SimpleValidator\Validators;
2014-02-08 20:13:14 +01:00
use PicoDb\Database;
use PicoFeed\Serialization\Export;
use PicoFeed\Serialization\Import;
use PicoFeed\Reader\Reader;
2014-12-24 23:54:27 +01:00
use PicoFeed\Reader\Favicon;
use PicoFeed\PicoFeedException;
use PicoFeed\Client\InvalidUrlException;
const LIMIT_ALL = -1;
// Store the favicon
2015-12-07 14:18:27 +01:00
function store_favicon($feed_id, $link, $type, $icon)
2014-12-24 23:54:27 +01:00
{
2015-12-07 14:18:27 +01:00
$file = $feed_id.Helper\favicon_extension($type);
if (file_put_contents(FAVICON_DIRECTORY.DIRECTORY_SEPARATOR.$file, $icon) === false) {
return false;
}
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')
2014-12-24 23:54:27 +01:00
->table('favicons')
->save(array(
'feed_id' => $feed_id,
'link' => $link,
2015-12-07 14:18:27 +01:00
'file' => $file,
'type' => $type
2014-12-24 23:54:27 +01:00
));
}
2015-12-07 14:18:27 +01:00
// Delete the favicon
function delete_favicon($feed_id)
{
foreach (get_favicons(array ($feed_id)) as $favicon) {
unlink(FAVICON_DIRECTORY.DIRECTORY_SEPARATOR.$favicon);
}
}
// Delete all the favicons
function delete_all_favicons()
{
foreach (get_all_favicons() as $favicon) {
unlink(FAVICON_DIRECTORY.DIRECTORY_SEPARATOR.$favicon);
}
}
// Download favicon
function fetch_favicon($feed_id, $site_url, $icon_link)
2014-12-24 23:54:27 +01:00
{
if (Config\get('favicons') == 1 && ! has_favicon($feed_id)) {
$favicon = new Favicon;
$link = $favicon->find($site_url, $icon_link);
2015-12-07 14:18:27 +01:00
$icon = $favicon->getContent();
$type = $favicon->getType();
if ($icon !== '') {
2015-12-07 14:18:27 +01:00
store_favicon($feed_id, $link, $type, $icon);
}
2014-12-24 23:54:27 +01:00
}
}
// Return true if the feed have a favicon
function has_favicon($feed_id)
{
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')->table('favicons')->eq('feed_id', $feed_id)->count() === 1;
2014-12-24 23:54:27 +01:00
}
// Get favicons for those feeds
function get_favicons(array $feed_ids)
{
if (Config\get('favicons') == 0) {
return array();
}
2015-08-15 03:33:39 +02:00
$db = Database::getInstance('db')
2015-01-28 02:13:16 +01:00
->hashtable('favicons')
->columnKey('feed_id')
2015-12-07 14:18:27 +01:00
->columnValue('file');
// pass $feeds_ids as argument list to hashtable::get(), use ... operator with php 5.6+
return call_user_func_array(array($db, 'get'), $feed_ids);
2014-12-24 23:54:27 +01:00
}
// Get all favicons for a list of items
function get_item_favicons(array $items)
{
$feed_ids = array();
foreach ($items as $item) {
$feed_ids[$item['feed_id']] = $item['feed_id'];
2014-12-24 23:54:27 +01:00
}
return get_favicons($feed_ids);
}
2014-12-25 00:12:41 +01:00
// Get all favicons
function get_all_favicons()
{
if (Config\get('favicons') == 0) {
return array();
}
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')
2015-01-28 02:13:16 +01:00
->hashtable('favicons')
2015-12-07 14:18:27 +01:00
->getAll('feed_id', 'file');
2014-12-25 00:12:41 +01:00
}
// Update feed information
function update(array $values)
{
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->startTransaction();
2015-08-15 03:33:39 +02:00
$result = Database::getInstance('db')
->table('feeds')
->eq('id', $values['id'])
->save(array(
'title' => $values['title'],
'site_url' => $values['site_url'],
'feed_url' => $values['feed_url'],
'enabled' => $values['enabled'],
'rtl' => $values['rtl'],
'download_content' => $values['download_content'],
'cloak_referrer' => $values['cloak_referrer'],
2015-12-16 20:55:53 +01:00
'parsing_error' => 0,
));
if ($result) {
if (! Group\update_feed_groups($values['id'], $values['feed_group_ids'], $values['create_group'])) {
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->cancelTransaction();
$result = false;
}
}
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->closeTransaction();
return $result;
}
// Export all feeds
function export_opml()
{
2014-05-20 20:20:27 +02:00
$opml = new Export(get_all());
return $opml->execute();
}
// Import OPML file
function import_opml($content)
{
2014-05-20 20:20:27 +02:00
$import = new Import($content);
$feeds = $import->execute();
if ($feeds) {
2015-08-15 03:33:39 +02:00
$db = Database::getInstance('db');
$db->startTransaction();
foreach ($feeds as $feed) {
if (! $db->table('feeds')->eq('feed_url', $feed->feed_url)->count()) {
$db->table('feeds')->save(array(
'title' => $feed->title,
'site_url' => $feed->site_url,
'feed_url' => $feed->feed_url
));
}
}
$db->closeTransaction();
2014-05-20 20:20:27 +02:00
Config\write_debug();
return true;
}
2014-05-20 20:20:27 +02:00
Config\write_debug();
return false;
}
// Add a new feed from an URL
function create($url, $enable_grabber = false, $force_rtl = false, $cloak_referrer = false, $group_ids = array(), $create_group = '')
{
$feed_id = false;
2015-08-15 03:33:39 +02:00
$db = Database::getInstance('db');
// Discover the feed
$reader = new Reader(Config\get_reader_config());
$resource = $reader->discover($url);
// Feed already there
if ($db->table('feeds')->eq('feed_url', $resource->getUrl())->count()) {
2015-04-11 16:42:23 +02:00
throw new UnexpectedValueException;
}
// Parse the feed
$parser = $reader->getParser(
$resource->getUrl(),
$resource->getContent(),
$resource->getEncoding()
);
2014-12-24 23:54:27 +01:00
if ($enable_grabber) {
$parser->enableContentGrabber();
}
$feed = $parser->execute();
// Save the feed
$result = $db->table('feeds')->save(array(
'title' => $feed->getTitle(),
'site_url' => $feed->getSiteUrl(),
'feed_url' => $feed->getFeedUrl(),
'download_content' => $enable_grabber ? 1 : 0,
'rtl' => $force_rtl ? 1 : 0,
'last_modified' => $resource->getLastModified(),
'last_checked' => time(),
'etag' => $resource->getEtag(),
'cloak_referrer' => $cloak_referrer ? 1 : 0,
));
if ($result) {
2015-08-15 03:33:39 +02:00
$feed_id = $db->getLastId();
Group\update_feed_groups($feed_id, $group_ids, $create_group);
Item\update_all($feed_id, $feed->getItems());
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
}
return $feed_id;
}
// Refresh all feeds
function refresh_all($limit = LIMIT_ALL)
{
2015-10-20 03:21:18 +02:00
foreach (get_ids($limit) as $feed_id) {
refresh($feed_id);
}
// Auto-vacuum for people using the cronjob
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->getConnection()->exec('VACUUM');
return true;
}
// Refresh one feed
function refresh($feed_id)
{
try {
$feed = get($feed_id);
2014-05-20 20:20:27 +02:00
if (empty($feed)) {
return false;
}
$reader = new Reader(Config\get_reader_config());
$resource = $reader->download(
$feed['feed_url'],
$feed['last_modified'],
$feed['etag']
);
// Update the `last_checked` column each time, HTTP cache or not
update_last_checked($feed_id);
// Feed modified
if ($resource->isModified()) {
$parser = $reader->getParser(
$resource->getUrl(),
$resource->getContent(),
$resource->getEncoding()
);
if ($feed['download_content']) {
$parser->enableContentGrabber();
// Don't fetch previous items, only new one
$parser->setGrabberIgnoreUrls(
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->table('items')->eq('feed_id', $feed_id)->findAllByColumn('url')
);
}
$feed = $parser->execute();
update_cache($feed_id, $resource->getLastModified(), $resource->getEtag());
2014-05-20 20:20:27 +02:00
Item\update_all($feed_id, $feed->getItems());
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
}
update_parsing_error($feed_id, 0);
Config\write_debug();
return true;
}
catch (PicoFeedException $e) {
}
update_parsing_error($feed_id, 1);
2014-05-20 20:20:27 +02:00
Config\write_debug();
return false;
}
// Get the list of feeds ID to refresh
function get_ids($limit = LIMIT_ALL)
{
2015-08-15 03:33:39 +02:00
$query = Database::getInstance('db')->table('feeds')->eq('enabled', 1)->asc('last_checked');
if ($limit !== LIMIT_ALL) {
$query->limit((int) $limit);
}
2015-01-28 02:13:16 +01:00
return $query->findAllByColumn('id');
}
// get number of feeds with errors
function count_failed_feeds()
{
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')
->table('feeds')
->eq('parsing_error', '1')
->count();
}
// Get all feeds
function get_all()
{
2015-12-24 13:07:06 +01:00
$result = Database::getInstance('db')
->table('feeds')
->asc('title')
->findAll();
2015-12-24 13:07:06 +01:00
$groups = Group\get_all_grouped_by_feeds();
foreach ($result as &$feed) {
$feed['groups'] = isset($groups[$feed['id']])
? $groups[$feed['id']]
: array();
}
return $result;
}
// Get all feeds with the number unread/total items in the order failed, working, disabled
function get_all_item_counts()
{
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')
->table('feeds')
->columns(
'feeds.*',
'SUM(CASE WHEN items.status IN ("unread") THEN 1 ELSE 0 END) as "items_unread"',
'SUM(CASE WHEN items.status IN ("read", "unread") THEN 1 ELSE 0 END) as "items_total"'
)
->join('items', 'feed_id', 'id')
->groupBy('feeds.id')
->desc('feeds.parsing_error')
->desc('feeds.enabled')
->asc('feeds.title')
->findAll();
}
// Get unread/total count for one feed
function count_items($feed_id)
{
2015-08-15 03:33:39 +02:00
$counts = Database::getInstance('db')
->table('items')
->columns('status', 'count(*) as item_count')
->in('status', array('read', 'unread'))
->eq('feed_id', $feed_id)
->groupBy('status')
->findAll();
$result = array(
'items_unread' => 0,
'items_total' => 0,
);
foreach ($counts as &$count) {
if ($count['status'] === 'unread') {
$result['items_unread'] = (int) $count['item_count'];
}
$result['items_total'] += $count['item_count'];
}
return $result;
}
// Get one feed
function get($feed_id)
{
2015-12-24 13:07:06 +01:00
$result = Database::getInstance('db')
->table('feeds')
->eq('id', $feed_id)
->findOne();
2015-12-24 13:07:06 +01:00
if (!$result) {
return $result;
}
$groups = Group\get_all_grouped_by_feeds();
$result['groups'] = isset($groups[$result['id']])
? $groups[$result['id']]
: array();
return $result;
}
// Update parsing error column
function update_parsing_error($feed_id, $value)
{
2015-08-15 03:33:39 +02:00
Database::getInstance('db')->table('feeds')->eq('id', $feed_id)->save(array('parsing_error' => $value));
}
// Update last check date
function update_last_checked($feed_id)
{
2015-08-15 03:33:39 +02:00
Database::getInstance('db')
->table('feeds')
->eq('id', $feed_id)
->save(array(
'last_checked' => time()
));
}
// Update Etag and last Modified columns
function update_cache($feed_id, $last_modified, $etag)
{
2015-08-15 03:33:39 +02:00
Database::getInstance('db')
->table('feeds')
->eq('id', $feed_id)
->save(array(
'last_modified' => $last_modified,
'etag' => $etag
));
}
// Remove one feed
function remove($feed_id)
{
2015-12-07 14:18:27 +01:00
delete_favicon($feed_id);
Group\remove_all($feed_id);
// Items are removed by a sql constraint
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')->table('feeds')->eq('id', $feed_id)->remove();
}
// Remove all feeds
function remove_all()
{
2015-12-07 14:18:27 +01:00
delete_all_favicons();
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')->table('feeds')->remove();
}
// Enable a feed (activate refresh)
function enable($feed_id)
{
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')->table('feeds')->eq('id', $feed_id)->save((array('enabled' => 1)));
}
// Disable feed
function disable($feed_id)
{
2015-08-15 03:33:39 +02:00
return Database::getInstance('db')->table('feeds')->eq('id', $feed_id)->save((array('enabled' => 0)));
}
// Validation for edit
function validate_modification(array $values)
{
$v = new Validator($values, array(
new Validators\Required('id', t('The feed id is required')),
new Validators\Required('title', t('The title is required')),
new Validators\Required('site_url', t('The site url is required')),
new Validators\Required('feed_url', t('The feed url is required')),
));
$result = $v->execute();
$errors = $v->getErrors();
return array(
$result,
$errors
);
}