Merge pull-request #393

This commit is contained in:
Frederic Guillot 2015-08-14 19:54:59 -04:00
commit cb6eec9497
27 changed files with 501 additions and 75 deletions

View File

@ -247,6 +247,38 @@ textarea.form-error {
display: none; display: none;
} }
/* Feed group buttons */
#grouplist span {
float: left;
opacity: 0.4;
min-width: 4em;
margin-right: 6px;
overflow: auto;
text-align: center;
}
#grouplist input[type="text"] {
width: 150px;
}
#grouplist input[type="checkbox"] + span, #grouplist input[type="text"] {
padding: 4px;
margin-top: 6px;
margin-bottom: 0px;
}
#grouplist input[type="checkbox"]:checked + span {
opacity: 1;
}
ul#grouplist {
float: left;
}
ul#grouplist li {
margin-left: 0px;
}
/* alerts */ /* alerts */
.alert, .panel { .alert, .panel {
padding: 8px 35px 8px 14px; padding: 8px 35px 8px 14px;
@ -287,7 +319,7 @@ textarea.form-error {
} }
/* buttons */ /* buttons */
.btn { .btn, #grouplist span {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
display: inline-block; display: inline-block;

View File

@ -22,7 +22,8 @@
"models/schema.php", "models/schema.php",
"models/auto_update.php", "models/auto_update.php",
"models/database.php", "models/database.php",
"models/remember_me.php" "models/remember_me.php",
"models/group.php"
], ],
"classmap": [ "classmap": [
"vendor/fguillot/json-rpc/src/", "vendor/fguillot/json-rpc/src/",

View File

@ -19,10 +19,16 @@ Router\get_action('edit-feed', function() {
$id = Request\int_param('feed_id'); $id = Request\int_param('feed_id');
$values = Model\Feed\get($id);
$values += array(
'feed_group_ids' => Model\Group\get_feed_group_ids($id)
);
Response\html(Template\layout('edit_feed', array( Response\html(Template\layout('edit_feed', array(
'values' => Model\Feed\get($id), 'values' => $values,
'errors' => array(), 'errors' => array(),
'nb_unread_items' => Model\Item\count_by_status('unread'), 'nb_unread_items' => Model\Item\count_by_status('unread'),
'groups' => Model\Group\get_all(),
'menu' => 'feeds', 'menu' => 'feeds',
'title' => t('Edit subscription') 'title' => t('Edit subscription')
))); )));
@ -32,7 +38,14 @@ Router\get_action('edit-feed', function() {
Router\post_action('edit-feed', function() { Router\post_action('edit-feed', function() {
$values = Request\values(); $values = Request\values();
$values += array('enabled' => 0, 'download_content' => 0, 'rtl' => 0, 'cloak_referrer' => 0); $values += array(
'enabled' => 0,
'download_content' => 0,
'rtl' => 0,
'cloak_referrer' => 0,
'feed_group_ids' => array(),
'create_group' => ''
);
list($valid, $errors) = Model\Feed\validate_modification($values); list($valid, $errors) = Model\Feed\validate_modification($values);
@ -50,6 +63,7 @@ Router\post_action('edit-feed', function() {
'values' => $values, 'values' => $values,
'errors' => $errors, 'errors' => $errors,
'nb_unread_items' => Model\Item\count_by_status('unread'), 'nb_unread_items' => Model\Item\count_by_status('unread'),
'groups' => Model\Group\get_all(),
'menu' => 'feeds', 'menu' => 'feeds',
'title' => t('Edit subscription') 'title' => t('Edit subscription')
))); )));
@ -129,12 +143,19 @@ Router\get_action('feeds', function() {
// Display form to add one feed // Display form to add one feed
Router\get_action('add', function() { Router\get_action('add', function() {
$values = array('download_content' => 0, 'rtl' => 0, 'cloak_referrer' => 0); $values = array(
'download_content' => 0,
'rtl' => 0,
'cloak_referrer' => 0,
'create_group' => '',
'feed_group_ids' => array()
);
Response\html(Template\layout('add', array( Response\html(Template\layout('add', array(
'values' => $values + array('csrf' => Model\Config\generate_csrf()), 'values' => $values + array('csrf' => Model\Config\generate_csrf()),
'errors' => array(), 'errors' => array(),
'nb_unread_items' => Model\Item\count_by_status('unread'), 'nb_unread_items' => Model\Item\count_by_status('unread'),
'groups' => Model\Group\get_all(),
'menu' => 'feeds', 'menu' => 'feeds',
'title' => t('New subscription') 'title' => t('New subscription')
))); )));
@ -158,10 +179,24 @@ Router\action('subscribe', function() {
} }
} }
$values += array('url' => trim($url), 'download_content' => 0, 'rtl' => 0, 'cloak_referrer' => 0); $values += array(
'url' => trim($url),
'download_content' => 0,
'rtl' => 0,
'cloak_referrer' => 0,
'create_group' => '',
'feed_group_ids' => array()
);
try { try {
$feed_id = Model\Feed\create($values['url'], $values['download_content'], $values['rtl'], $values['cloak_referrer']); $feed_id = Model\Feed\create(
$values['url'],
$values['download_content'],
$values['rtl'],
$values['cloak_referrer'],
$values['feed_group_ids'],
$values['create_group']
);
} }
catch (UnexpectedValueException $e) { catch (UnexpectedValueException $e) {
$error_message = t('This subscription already exists.'); $error_message = t('This subscription already exists.');
@ -210,6 +245,7 @@ Router\action('subscribe', function() {
Response\html(Template\layout('add', array( Response\html(Template\layout('add', array(
'values' => $values + array('csrf' => Model\Config\generate_csrf()), 'values' => $values + array('csrf' => Model\Config\generate_csrf()),
'nb_unread_items' => Model\Item\count_by_status('unread'), 'nb_unread_items' => Model\Item\count_by_status('unread'),
'groups' => Model\Group\get_all(),
'menu' => 'feeds', 'menu' => 'feeds',
'title' => t('Subscriptions') 'title' => t('Subscriptions')
))); )));

View File

@ -12,6 +12,7 @@ Router\get_action('history', function() {
$nb_items = Model\Item\count_by_status('read'); $nb_items = Model\Item\count_by_status('read');
$items = Model\Item\get_all_by_status( $items = Model\Item\get_all_by_status(
'read', 'read',
null,
$offset, $offset,
Model\Config\get('items_per_page'), Model\Config\get('items_per_page'),
'updated', 'updated',

View File

@ -14,11 +14,26 @@ Router\get_action('unread', function() {
$order = Request\param('order', 'updated'); $order = Request\param('order', 'updated');
$direction = Request\param('direction', Model\Config\get('items_sorting_direction')); $direction = Request\param('direction', Model\Config\get('items_sorting_direction'));
$offset = Request\int_param('offset', 0); $offset = Request\int_param('offset', 0);
$items = Model\Item\get_all_by_status('unread', $offset, Model\Config\get('items_per_page'), $order, $direction); $group_id = Request\int_param('group_id', null);
$nb_items = Model\Item\count_by_status('unread'); $feed_ids = null;
if ($nb_items === 0) { if (!is_null($group_id)) {
$feed_ids = Model\Group\get_feeds_by_group($group_id);
}
$items = Model\Item\get_all_by_status(
'unread',
$feed_ids,
$offset,
Model\Config\get('items_per_page'),
$order,
$direction
);
$nb_items = Model\Item\count_by_status('unread', $feed_ids);
$nb_unread_items = Model\Item\count_by_status('unread');
if ($nb_unread_items === 0) {
$action = Model\Config\get('redirect_nothing_to_read'); $action = Model\Config\get('redirect_nothing_to_read');
Response\redirect('?action='.$action.'&nothing_to_read=1'); Response\redirect('?action='.$action.'&nothing_to_read=1');
} }
@ -29,13 +44,15 @@ Router\get_action('unread', function() {
'order' => $order, 'order' => $order,
'direction' => $direction, 'direction' => $direction,
'display_mode' => Model\Config\get('items_display_mode'), 'display_mode' => Model\Config\get('items_display_mode'),
'group_id' => $group_id,
'items' => $items, 'items' => $items,
'nb_items' => $nb_items, 'nb_items' => $nb_items,
'nb_unread_items' => $nb_items, 'nb_unread_items' => $nb_unread_items,
'offset' => $offset, 'offset' => $offset,
'items_per_page' => Model\Config\get('items_per_page'), 'items_per_page' => Model\Config\get('items_per_page'),
'title' => 'Miniflux ('.$nb_items.')', 'title' => 'Miniflux ('.$nb_items.')',
'menu' => 'unread' 'menu' => 'unread',
'groups' => Model\Group\get_all()
))); )));
}); });
@ -146,10 +163,18 @@ Router\post_action('mark-item-unread', function() {
Response\json(array('Ok')); Response\json(array('Ok'));
}); });
// Mark all unread items as read // Mark unread items as read
Router\get_action('mark-all-read', function() { Router\get_action('mark-all-read', function() {
$group_id = Request\int_param('group_id', null);
if (!is_null($group_id)) {
Model\Item\mark_group_as_read($group_id);
}
else {
Model\Item\mark_all_as_read(); Model\Item\mark_all_as_read();
}
Response\redirect('?action=unread'); Response\redirect('?action=unread');
}); });

View File

@ -3,6 +3,7 @@
require '../common.php'; require '../common.php';
use Model\Feed; use Model\Feed;
use Model\Group;
use Model\Service; use Model\Service;
use PicoDb\Database; use PicoDb\Database;
@ -59,24 +60,17 @@ route('groups', function() {
if ($response['auth']) { if ($response['auth']) {
$feed_ids = Database::get('db') $response['groups'] = Group\get_all();
->table('feeds') $response['feeds_groups'] = array();
->findAllByColumn('id'); $group_map = Group\get_map();
$response['groups'] = array( foreach ($group_map as $group_id => $feed_ids) {
array( $response['feeds_groups'][] = array(
'id' => 1, 'group_id' => $group_id,
'title' => t('All'), 'feed_ids' => implode(',', $feed_ids)
)
);
$response['feeds_groups'] = array(
array(
'group_id' => 1,
'feed_ids' => implode(',', $feed_ids),
)
); );
} }
}
response($response); response($response);
}); });
@ -89,8 +83,9 @@ route('feeds', function() {
if ($response['auth']) { if ($response['auth']) {
$response['feeds'] = array(); $response['feeds'] = array();
$response['feeds_groups'] = array();
$feeds = Feed\get_all(); $feeds = Feed\get_all();
$feed_ids = array();
foreach ($feeds as $feed) { foreach ($feeds as $feed) {
$response['feeds'][] = array( $response['feeds'][] = array(
@ -102,17 +97,16 @@ route('feeds', function() {
'is_spark' => 0, 'is_spark' => 0,
'last_updated_on_time' => $feed['last_checked'] ?: time(), 'last_updated_on_time' => $feed['last_checked'] ?: time(),
); );
$feed_ids[] = $feed['id'];
} }
$response['feeds_groups'] = array( $group_map = Group\get_map();
array( foreach ($group_map as $group_id => $feed_ids) {
'group_id' => 1, $response['feeds_groups'][] = array(
'feed_ids' => implode(',', $feed_ids), 'group_id' => $group_id,
) 'feed_ids' => implode(',', $feed_ids)
); );
} }
}
response($response); response($response);
}); });
@ -297,7 +291,7 @@ route('write_feeds', function() {
->table('items') ->table('items')
->eq('feed_id', $_POST['id']) ->eq('feed_id', $_POST['id'])
->lte('updated', $_POST['before']) ->lte('updated', $_POST['before'])
->update(array('status' => $_POST['as'] === 'read' ? 'read' : 'unread')); ->update(array('status' => 'read'));
} }
response($response); response($response);
@ -309,11 +303,15 @@ route('write_groups', function() {
$response = auth(); $response = auth();
if ($response['auth']) { if ($response['auth']) {
$db = Database::get('db')
Database::get('db')
->table('items') ->table('items')
->lte('updated', $_POST['before']) ->lte('updated', $_POST['before']);
->update(array('status' => $_POST['as'] === 'read' ? 'read' : 'unread'));
if ($_POST['id'] > 0) {
$db->in('feed_id', Model\Group\get_feeds_by_group($_POST['id']));
}
$db->update(array('status' => 'read'));
} }
response($response); response($response);

View File

@ -115,7 +115,7 @@ $server->register('item.bookmark.delete', function ($item_id) {
// Get all unread items // Get all unread items
$server->register('item.list_unread', function ($offset = null, $limit = null) { $server->register('item.list_unread', function ($offset = null, $limit = null) {
return Model\Item\get_all_by_status('unread', $offset, $limit); return Model\Item\get_all_by_status('unread', null, $offset, $limit);
}); });
// Count all unread items // Count all unread items
@ -127,7 +127,7 @@ $server->register('item.count_unread', function () {
// Get all read items // Get all read items
$server->register('item.list_read', function ($offset = null, $limit = null) { $server->register('item.list_read', function ($offset = null, $limit = null) {
return Model\Item\get_all_by_status('read', $offset, $limit); return Model\Item\get_all_by_status('read', null, $offset, $limit);
}); });
// Count all read items // Count all read items

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
'Maximum number of HTTP redirections exceeded.' => 'Nombre maximal de redirections HTTP dépassé.', 'Maximum number of HTTP redirections exceeded.' => 'Nombre maximal de redirections HTTP dépassé.',
'The content size exceeds to maximum allowed size.' => 'La taille du contenu dépasse la taille maximale autorisée.', 'The content size exceeds to maximum allowed size.' => 'La taille du contenu dépasse la taille maximale autorisée.',
'Unable to detect the feed format.' => 'Impossible de détecter le format du flux.', 'Unable to detect the feed format.' => 'Impossible de détecter le format du flux.',
'add a new group' => 'add a new group',
'Groups' => 'Groups',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -230,4 +230,14 @@ return array(
'Enable debug mode' => 'Включить отладочный режим', 'Enable debug mode' => 'Включить отладочный режим',
'Original link marks article as read' => 'При переходе на оригинал отмечать статью как прочитанную', 'Original link marks article as read' => 'При переходе на оригинал отмечать статью как прочитанную',
'Cloak the image referrer' => 'Проксировать загрузку изображений', 'Cloak the image referrer' => 'Проксировать загрузку изображений',
// 'This subscription already exists.' => '',
// 'Connection timeout.' => '',
// 'Error occured.' => '',
// 'Feed is malformed.' => '',
// 'Invalid SSL certificate.' => '',
// 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -238,4 +238,6 @@ return array(
// 'Maximum number of HTTP redirections exceeded.' => '', // 'Maximum number of HTTP redirections exceeded.' => '',
// 'The content size exceeds to maximum allowed size.' => '', // 'The content size exceeds to maximum allowed size.' => '',
// 'Unable to detect the feed format.' => '', // 'Unable to detect the feed format.' => '',
// 'add a new group' => '',
// 'Groups' => '',
); );

View File

@ -5,6 +5,7 @@ namespace Model\Feed;
use UnexpectedValueException; use UnexpectedValueException;
use Model\Config; use Model\Config;
use Model\Item; use Model\Item;
use Model\Group;
use SimpleValidator\Validator; use SimpleValidator\Validator;
use SimpleValidator\Validators; use SimpleValidator\Validators;
use PicoDb\Database; use PicoDb\Database;
@ -93,7 +94,9 @@ function get_all_favicons()
// Update feed information // Update feed information
function update(array $values) function update(array $values)
{ {
return Database::get('db') Database::get('db')->startTransaction();
$result = Database::get('db')
->table('feeds') ->table('feeds')
->eq('id', $values['id']) ->eq('id', $values['id'])
->save(array( ->save(array(
@ -105,6 +108,17 @@ function update(array $values)
'download_content' => $values['download_content'], 'download_content' => $values['download_content'],
'cloak_referrer' => $values['cloak_referrer'], 'cloak_referrer' => $values['cloak_referrer'],
)); ));
if ($result) {
if (! Group\update_feed_groups($values['id'], $values['feed_group_ids'], $values['create_group'])) {
Database::get('db')->cancelTransaction();
$result = false;
}
}
Database::get('db')->closeTransaction();
return $result;
} }
// Export all feeds // Export all feeds
@ -150,7 +164,7 @@ function import_opml($content)
} }
// Add a new feed from an URL // Add a new feed from an URL
function create($url, $enable_grabber = false, $force_rtl = false, $cloak_referrer = false) function create($url, $enable_grabber = false, $force_rtl = false, $cloak_referrer = false, $group_ids = array(), $create_group = '')
{ {
$feed_id = false; $feed_id = false;
@ -194,6 +208,7 @@ function create($url, $enable_grabber = false, $force_rtl = false, $cloak_referr
if ($result) { if ($result) {
$feed_id = $db->getConnection()->getLastId(); $feed_id = $db->getConnection()->getLastId();
Group\update_feed_groups($feed_id, $group_ids, $create_group);
Item\update_all($feed_id, $feed->getItems()); Item\update_all($feed_id, $feed->getItems());
fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon()); fetch_favicon($feed_id, $feed->getSiteUrl(), $feed->getIcon());
} }

220
models/group.php Normal file
View File

@ -0,0 +1,220 @@
<?php
namespace Model\Group;
use PicoDb\Database;
/**
* Get all groups
*
* @return array
*/
function get_all()
{
return Database::get('db')
->table('groups')
->orderBy('title')
->findAll();
}
/**
* Get assoc array of group ids with assigned feeds ids
*
* @return array
*/
function get_map()
{
$result = Database::get('db')
->table('feeds_groups')
->findAll();
// TODO: add PDO::FETCH_COLUMN|PDO::FETCH_GROUP to picodb and use it instead
// of the following lines
$map = array();
foreach ($result as $row) {
$group_id = $row['group_id'];
$feed_id = $row['feed_id'];
if (isset($map[$group_id])) {
$map[$group_id][] = $feed_id;
}
else {
$map[$group_id] = array($feed_id);
}
}
return $map;
}
/**
* Get all groups assigned to feed
*
* @param integer $feed_id id of the feed
* @return array
*/
function get_feed_group_ids($feed_id)
{
return Database::get('db')
->table('groups')
->join('feeds_groups', 'group_id', 'id')
->eq('feed_id', $feed_id)
->findAllByColumn('id');
}
/**
* Get the id of a group
*
* @param string $title group name
* @return mixed group id or false if not found
*/
function get_group_id($title)
{
return Database::get('db')
->table('groups')
->eq('title', $title)
->findOneColumn('id');
}
/**
* Get all feed ids assigned to a group
*
* @param array $group_id
* @return array
*/
function get_feeds_by_group($group_id)
{
return Database::get('db')
->table('feeds_groups')
->eq('group_id', $group_id)
->findAllByColumn('feed_id');
}
/**
* Add a group to the Database
*
* Returns either the id of the new group or the id of an existing group with
* the same name
*
* @param string $title group name
* @return mixed id of the created group or false on error
*/
function create($title)
{
$data = array('title' => $title);
// check if the group already exists
$group_id = get_group_id($title);
// create group if missing
if ($group_id === false) {
Database::get('db')
->table('groups')
->insert($data);
$group_id = get_group_id($title);
}
return $group_id;
}
/**
* Add groups to feed
*
* @param integer $feed_id feed id
* @param array $group_ids array of group ids
* @return boolean true on success, false on error
*/
function add($feed_id, $group_ids)
{
foreach ($group_ids as $group_id){
$data = array('feed_id' => $feed_id, 'group_id' => $group_id);
$result = Database::get('db')
->table('feeds_groups')
->insert($data);
if ($result === false) {
return false;
}
}
return true;
}
/**
* Remove groups from feed
*
* @param integer $feed_id id of the feed
* @param array $group_ids array of group ids
* @return boolean true on success, false on error
*/
function remove($feed_id, $group_ids)
{
return Database::get('db')
->table('feeds_groups')
->eq('feed_id', $feed_id)
->in('group_id', $group_ids)
->remove();
}
/**
* Purge orphaned groups from database
*/
function purge_groups()
{
$groups = Database::get('db')
->table('groups')
->join('feeds_groups', 'group_id', 'id')
->isnull('feed_id')
->findAllByColumn('id');
if (! empty($groups)) {
Database::get('db')
->table('groups')
->in('id', $groups)
->remove();
}
}
/**
* Update feed group associations
*
* @param integer $feed_id id of the feed to update
* @param array $group_ids valid groups ids for feed
* @param string $create_group group to create and assign to feed
* @return boolean
*/
function update_feed_groups($feed_id, $group_ids, $create_group = '')
{
if ($create_group !== '') {
$id = create($create_group);
if ($id === false) {
return false;
}
if (! in_array($id, $group_ids)) {
$group_ids[] = $id;
}
}
$assigned = get_feed_group_ids($feed_id);
$superfluous = array_diff($assigned, $group_ids);
$missing = array_diff($group_ids, $assigned);
// remove no longer assigned groups from feed
if (! empty($superfluous) && ! remove($feed_id, $superfluous)) {
return false;
}
// add requested groups to feed
if (! empty($missing) && ! add($feed_id, $missing)) {
return false;
}
// cleanup
purge_groups();
return true;
}

View File

@ -4,6 +4,7 @@ namespace Model\Item;
use Model\Service; use Model\Service;
use Model\Config; use Model\Config;
use Model\Group;
use PicoDb\Database; use PicoDb\Database;
use PicoFeed\Logging\Logger; use PicoFeed\Logging\Logger;
use PicoFeed\Scraper\Scraper; use PicoFeed\Scraper\Scraper;
@ -89,7 +90,7 @@ function get_all_status()
} }
// Get all items by status // Get all items by status
function get_all_by_status($status, $offset = null, $limit = null, $order_column = 'updated', $order_direction = 'desc') function get_all_by_status($status, $feed_ids = null, $offset = null, $limit = null, $order_column = 'updated', $order_direction = 'desc')
{ {
return Database::get('db') return Database::get('db')
->table('items') ->table('items')
@ -111,6 +112,7 @@ function get_all_by_status($status, $offset = null, $limit = null, $order_column
) )
->join('feeds', 'id', 'feed_id') ->join('feeds', 'id', 'feed_id')
->eq('status', $status) ->eq('status', $status)
->in('feed_id', $feed_ids)
->orderBy($order_column, $order_direction) ->orderBy($order_column, $order_direction)
->offset($offset) ->offset($offset)
->limit($limit) ->limit($limit)
@ -118,11 +120,12 @@ function get_all_by_status($status, $offset = null, $limit = null, $order_column
} }
// Get the number of items per status // Get the number of items per status
function count_by_status($status) function count_by_status($status, $feed_ids = null)
{ {
return Database::get('db') return Database::get('db')
->table('items') ->table('items')
->eq('status', $status) ->eq('status', $status)
->in('feed_id', $feed_ids)
->count(); ->count();
} }
@ -355,6 +358,19 @@ function mark_feed_as_read($feed_id)
->update(array('status' => 'read')); ->update(array('status' => 'read'));
} }
// Mark all items of a group as read
function mark_group_as_read($group_id)
{
// workaround for missing update with join
$feed_ids = Group\get_feeds_by_group($group_id);
return Database::get('db')
->table('items')
->eq('status', 'unread')
->in('feed_id', $feed_ids)
->update(array('status' => 'read'));
}
// Mark all read items to removed after X days // Mark all read items to removed after X days
function autoflush_read() function autoflush_read()
{ {

View File

@ -5,7 +5,27 @@ namespace Schema;
use PDO; use PDO;
use Model\Config; use Model\Config;
const VERSION = 40; const VERSION = 41;
function version_41($pdo)
{
$pdo->exec('
CREATE TABLE "groups" (
id INTEGER PRIMARY KEY,
title TEXT
)
');
$pdo->exec('
CREATE TABLE "feeds_groups" (
feed_id INTEGER NOT NULL,
group_id INTEGER NOT NULL,
PRIMARY KEY(feed_id, group_id)
FOREIGN KEY(group_id) REFERENCES groups(id) ON DELETE CASCADE
FOREIGN KEY(feed_id) REFERENCES feeds(id) ON DELETE CASCADE
)
');
}
function version_40($pdo) function version_40($pdo)
{ {

View File

@ -22,6 +22,16 @@
<?= Helper\form_checkbox('cloak_referrer', t('Cloak the image referrer'), 1, $values['cloak_referrer']) ?><br /> <?= Helper\form_checkbox('cloak_referrer', t('Cloak the image referrer'), 1, $values['cloak_referrer']) ?><br />
<p class="form-help"><?= t('Downloading full content is slower because Miniflux grab the content from the original website. You should use that for subscriptions that display only a summary. This feature doesn\'t work with all websites.') ?></p> <p class="form-help"><?= t('Downloading full content is slower because Miniflux grab the content from the original website. You should use that for subscriptions that display only a summary. This feature doesn\'t work with all websites.') ?></p>
<?= Helper\form_label(t('Groups'), 'groups'); ?>
<div id="grouplist">
<?php foreach ($groups as $group): ?>
<?= Helper\form_checkbox('feed_group_ids[]', $group['title'], $group['id'], in_array($group['id'], $values['feed_group_ids']), 'hide') ?>
<?php endforeach ?>
<?= Helper\form_text('create_group', $values, array(), array('placeholder="'.t('add a new group').'"')) ?>
</div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Add') ?></button> <button type="submit" class="btn btn-blue"><?= t('Add') ?></button>
<?= t('or') ?> <a href="?action=feeds"><?= t('cancel') ?></a> <?= t('or') ?> <a href="?action=feeds"><?= t('cancel') ?></a>

View File

@ -27,7 +27,16 @@
<?= Helper\form_checkbox('cloak_referrer', t('Cloak the image referrer'), 1, $values['cloak_referrer']) ?><br /> <?= Helper\form_checkbox('cloak_referrer', t('Cloak the image referrer'), 1, $values['cloak_referrer']) ?><br />
<?= Helper\form_checkbox('enabled', t('Activated'), 1, $values['enabled']) ?> <?= Helper\form_checkbox('enabled', t('Activated'), 1, $values['enabled']) ?><br />
<?= Helper\form_label(t('Groups'), 'groups'); ?>
<div id="grouplist">
<?php foreach ($groups as $group): ?>
<?= Helper\form_checkbox('feed_group_ids[]', $group['title'], $group['id'], in_array($group['id'], $values['feed_group_ids']), 'hide') ?>
<?php endforeach ?>
<?= Helper\form_text('create_group', $values, array(), array('placeholder="'.t('add a new group').'"')) ?>
</div>
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-blue"><?= t('Save') ?></button> <button type="submit" class="btn btn-blue"><?= t('Save') ?></button>

View File

@ -4,6 +4,6 @@
<?php endif ?> <?php endif ?>
&nbsp; &nbsp;
<?php if (($nb_items - $offset) > $items_per_page): ?> <?php if (($nb_items - $offset) > $items_per_page): ?>
<a id="next-page" href="?action=<?= $menu ?>&amp;offset=<?= ($offset + $items_per_page) ?>&amp;order=<?= $order ?>&amp;direction=<?= $direction ?><?= isset($feed_id) ? '&amp;feed_id='.$feed_id : '' ?>"><?= t('Next page') ?> »</a> <a id="next-page" href="?action=<?= $menu ?>&amp;offset=<?= ($offset + $items_per_page) ?>&amp;order=<?= $order ?>&amp;direction=<?= $direction ?><?= isset($feed_id) ? '&amp;feed_id='.$feed_id : '' ?><?= isset($group_id) ? '&amp;group_id='.$group_id : '' ?>"><?= t('Next page') ?> »</a>
<?php endif ?> <?php endif ?>
</div> </div>

View File

@ -1,20 +1,33 @@
<?php if (empty($items)): ?>
<p class="alert alert-info"><?= t('Nothing to read') ?></p>
<?php else: ?>
<div class="page-header"> <div class="page-header">
<h2><?= t('Unread') ?><span id="page-counter"><?= isset($nb_items) ? $nb_items : '' ?></span></h2> <h2><?= t('Unread') ?><span id="page-counter"><?= isset($nb_items) ? $nb_items : '' ?></span></h2>
<?php if (!empty($groups)): ?>
<nav>
<ul id="grouplist">
<?php foreach ($groups as $group): ?>
<li <?= $group['id'] == $group_id ? 'class="active"' : '' ?>>
<a href="?action=unread&group_id=<?=$group['id']?>"><?=$group['title']?></a>
</li>
<?php endforeach ?>
</ul>
</nav>
<?php endif ?>
<ul> <ul>
<li> <li>
<a href="?action=unread&amp;order=updated&amp;direction=<?= $direction == 'asc' ? 'desc' : 'asc' ?>"><?= tne('sort by date %s(%s)%s', '<span class="hide-mobile">',$direction == 'desc' ? t('older first') : t('most recent first'), '</span>') ?></a> <a href="?action=unread<?= is_null($group_id) ? '' : '&amp;group_id='.$group_id ?>&amp;order=updated&amp;direction=<?= $direction == 'asc' ? 'desc' : 'asc' ?>"><?= tne('sort by date %s(%s)%s', '<span class="hide-mobile">',$direction == 'desc' ? t('older first') : t('most recent first'), '</span>') ?></a>
</li> </li>
<li> <li>
<a href="?action=mark-all-read"><?= t('mark all as read') ?></a> <a href="?action=mark-all-read<?= is_null($group_id) ? '' : '&amp;group_id='.$group_id ?>"><?= t('mark all as read') ?></a>
</li> </li>
</ul> </ul>
</div> </div>
<section class="items" id="listing"> <section class="items" id="listing">
<?php if (empty($items)): ?>
<p class="alert alert-info"><?= t('Nothing to read') ?></p>
<?php else: ?>
<?php foreach ($items as $item): ?> <?php foreach ($items as $item): ?>
<?= \PicoFarad\Template\load('item', array( <?= \PicoFarad\Template\load('item', array(
'item' => $item, 'item' => $item,
@ -28,10 +41,9 @@
<?php endforeach ?> <?php endforeach ?>
<div id="bottom-menu"> <div id="bottom-menu">
<a href="?action=mark-all-read"><?= t('mark all as read') ?></a> <a href="?action=mark-all-read<?= is_null($group_id) ? '' : '&amp;group_id='.$group_id ?>"><?= t('mark all as read') ?></a>
</div> </div>
<?= \PicoFarad\Template\load('paging', array('menu' => $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction)) ?> <?= \PicoFarad\Template\load('paging', array('menu' => $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction, 'group_id' => $group_id)) ?>
</section>
<?php endif ?> <?php endif ?>
</section>

View File

@ -18,4 +18,5 @@ return array(
$baseDir . '/models/auto_update.php', $baseDir . '/models/auto_update.php',
$baseDir . '/models/database.php', $baseDir . '/models/database.php',
$baseDir . '/models/remember_me.php', $baseDir . '/models/remember_me.php',
$baseDir . '/models/group.php',
); );