From 6dffb8706f096acdbcda367210d0b7ad41937174 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 27 Feb 2014 22:48:11 +0100 Subject: Alpha version of Google Reader compatible API https://github.com/marienfressinaud/FreshRSS/issues/13 Hardcoded passwords, no possibility to add/delete feeds or edit categories yet. --- p/api/greader.php | 552 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 552 insertions(+) create mode 100644 p/api/greader.php (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php new file mode 100644 index 000000000..2969f5935 --- /dev/null +++ b/p/api/greader.php @@ -0,0 +1,552 @@ + date('c'), 'headers' => $ALL_HEADERS, '_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE, 'INPUT' => $ORIGINAL_INPUT); + +if (PHP_INT_SIZE < 8) { //32-bit + function dec2hex($dec) { + return str_pad(gmp_strval(gmp_init($dec, 10), 16), 16, '0', STR_PAD_LEFT); + } + function hex2dec($hex) { + return gmp_strval(gmp_init($hex, 16), 10); + } +} else { //64-bit + function dec2hex($dec) { //http://code.google.com/p/google-reader-api/wiki/ItemId + return str_pad(dechex($dec), 16, '0', STR_PAD_LEFT); + } + function hex2dec($hex) { + return hexdec($hex); + } +} + +function headerVariable($headerName, $varName) { + global $ALL_HEADERS; + if (empty($ALL_HEADERS[$headerName])) { + return null; + } + parse_str($ALL_HEADERS[$headerName], $pairs); + //logMe('headerVariable(' . $headerName . ') => ' . print_r($pairs, true)); + return isset($pairs[$varName]) ? $pairs[$varName] : null; +} + +function multiplePosts($name) { //https://bugs.php.net/bug.php?id=51633 + global $ORIGINAL_INPUT; + $inputs = explode('&', $ORIGINAL_INPUT); + $result = array(); + $prefix = $name . '='; + $prefixLength = strlen($prefix); + foreach ($inputs as $input) { + if (strpos($input, $prefix) === 0) { + $result[] = urldecode(substr($input, $prefixLength)); + } + } + return $result; +} + +class MyPDO extends Minz_ModelPdo { + function prepare($sql) { + return $this->bd->prepare(str_replace('%_', $this->prefix, $sql)); + } +} + +function logMe($text) { + file_put_contents(LOG_PATH . '/api.log', $text, FILE_APPEND); +} + +function badRequest() { + logMe("badRequest()\n"); + header('HTTP/1.1 400 Bad Request'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Bad Request!'); +} + +function unauthorized() { + logMe("unauthorized()\n"); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/plain; charset=UTF-8'); + header('Google-Bad-Token: true'); + die('Unauthorized!'); +} + +function notImplemented() { + logMe("notImplemented()\n"); + header('HTTP/1.1 501 Not Implemented'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Not Implemented!'); +} + +function serviceUnavailable() { + logMe("serviceUnavailable()\n"); + header('HTTP/1.1 503 Service Unavailable'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Service Unavailable!'); +} + +function checkCompatibility() { + logMe("checkCompatibility()\n"); + header('Content-Type: text/plain; charset=UTF-8'); + $ok = true; + $ok &= function_exists('getallheaders'); + echo $ok ? 'PASS' : 'FAIL'; + exit(); +} + +function authorizationToUser() { + $auth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external + //logMe('authorizationToUser, auth => ' . $auth . "\n"); + list($userName) = explode('/', $auth); + return $userName; +} + +function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html + logMe('clientLogin(' . $email . ")\n"); + if ($pass !== TEMP_PASSWORD) { + unauthorized(); + } + header('Content-Type: text/plain; charset=UTF-8'); + $auth = $email . '/' . '0123456789'; + echo 'SID=', $auth, "\n", + 'Auth=', $auth, "\n"; + exit(); +} + +function token($user) { +//http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ https://github.com/ericmann/gReader-Library/blob/master/greader.class.php + logMe('token('. $user . ")\n"); + $token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234'; //Must have 57 characters... + echo $token, "\n"; + exit(); +} + +function checkToken($user, $token) { +//http://code.google.com/p/google-reader-api/wiki/ActionToken + logMe('checkToken(' . $token . ")\n"); + if ($token === 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234') { + return true; + } + unauthorized(); +} + +function tagList() { + logMe("tagList()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $pdo = new MyPDO(); + $stm = $pdo->prepare('SELECT c.name FROM `%_category` c'); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + + $tags = array( + array('id' => 'user/-/state/com.google/starred'), + //array('id' => 'user/-/state/com.google/broadcast', 'sortid' => '2'), + ); + + foreach ($res as $cName) { + $tags[] = array( + 'id' => 'user/-/label/' . $cName, + //'sortid' => $cName, + ); + } + + echo json_encode(array('tags' => $tags)), "\n"; + exit(); +} + +function subscriptionList() { + logMe("subscriptionList()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $pdo = new MyPDO(); + $stm = $pdo->prepare('SELECT f.id, f.name, f.url, f.website, c.id as c_id, c.name as c_name FROM `%_feed` f + INNER JOIN `%_category` c ON c.id = f.category'); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + $subscriptions = array(); + + foreach ($res as $line) { + $subscriptions[] = array( + 'id' => 'feed/' . $line['id'], + 'title' => $line['name'], + 'categories' => array( + array( + 'id' => 'user/-/label/' . $line['c_name'], + 'label' => $line['c_name'], + ), + ), + //'sortid' => $line['name'], + //'firstitemmsec' => 0, + 'url' => $line['url'], + 'htmlUrl' => $line['website'], + //'iconUrl' => '', + ); + } + + echo json_encode(array('subscriptions' => $subscriptions)), "\n"; + exit(); +} + +function unreadCount() { + logMe("unreadCount()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $pdo = new MyPDO(); + $stm = $pdo->prepare('SELECT f.id, f.lastUpdate, f.cache_nbUnreads FROM `%_feed` f'); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + $unreadcounts = array(); + $totalUnreads = 0; + $totalLastUpdate = 0; + foreach ($res as $line) { + $nbUnreads = $line['cache_nbUnreads']; + $totalUnreads += $nbUnreads; + $lastUpdate = $line['lastUpdate']; + if ($totalLastUpdate < $lastUpdate) { + $totalLastUpdate = $lastUpdate; + } + $unreadcounts[] = array( + 'id' => 'feed/' . $line['id'], + 'count' => $nbUnreads, + 'newestItemTimestampUsec' => $lastUpdate . '000000', + ); + } + + $unreadcounts[] = array( + 'id' => 'user/-/state/com.google/reading-list', + 'count' => $totalUnreads, + 'newestItemTimestampUsec' => $totalLastUpdate . '000000', + ); + + echo json_encode(array( + 'max' => $totalUnreads, + 'unreadcounts' => $unreadcounts, + )), "\n"; + exit(); +} + +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target) +{//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + logMe('streamContents(' . $include_target . ")\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $feedDAO = new FreshRSS_FeedDAO(); + $feedCategoryNames = $feedDAO->listCategoryNames(); + + switch ($path) { + case 'reading-list': + $type = 'A'; + break; + case 'starred': + $type = 's'; + break; + case 'label': + $type = 'c'; + break; + default: + $type = 'A'; + break; + } + + switch ($exclude_target) { + case 'user/-/state/com.google/read': + $state = 'not_read'; + break; + default: + $state = 'all'; + break; + } + + $entryDAO = new FreshRSS_EntryDAO(); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + + $items = array(); + foreach ($entries as $entry) { + $f_id = $entry->feed(); + $c_name = isset($feedCategoryNames[$f_id]) ? $feedCategoryNames[$f_id] : '_'; + $item = array( + 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId + 'crawlTimeMsec' => substr($entry->id(), 0, -3), + 'published' => $entry->date(true), + 'title' => $entry->title(), + 'summary' => array('content' => $entry->content()), + 'alternate' => array( + array('href' => $entry->link()), + ), + 'categories' => array( + 'user/-/state/com.google/reading-list', + 'user/-/label/' . $c_name, + ), + 'origin' => array( + 'streamId' => 'feed/' . $f_id, + //'title' => $line['f_name'], + //'htmlUrl' => $line['f_website'], + ), + ); + if ($entry->author() != '') { + $item['author'] = $entry->author(); + } + if ($entry->isRead()) { + $item['categories'][] = 'user/-/state/com.google/read'; + } + if ($entry->isFavorite()) { + $item['categories'][] = 'user/-/state/com.google/starred'; + } + $items[] = $item; + } + + echo json_encode(array( + 'id' => 'user/-/state/com.google/reading-list', + 'updated' => time(), + 'items' => $items, + )), "\n"; + exit(); +} + +function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) +{//http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + logMe('streamContentsItemsIds(' . $streamId . ")\n"); + + $type = 'A'; + $id = ''; + if ($streamId === 'user/-/state/com.google/reading-list') { + $type = 'A'; + } elseif ('user/-/state/com.google/starred') { + $type = 's'; + } elseif (strpos($streamId, 'feed/') === 0) { + $type = 'f'; + $id = basename($streamId); + } elseif (strpos($streamId, 'user/-/label/') === 0) { + $type = 'C'; + $c_name = basename($streamId); + notImplemented(); //TODO + } + + switch ($exclude_target) { + case 'user/-/state/com.google/read': + $state = 'not_read'; + break; + default: + $state = 'all'; + break; + } + + $entryDAO = new FreshRSS_EntryDAO(); + $entries = $entryDAO->listWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + + $itemRefs = array(); + foreach ($entries as $entry) { + $f_id = $entry->feed(); + $itemRefs[] = array( + 'id' => $entry->id(), //64-bit decimal + //'timestampUsec' => $entry->dateAdded(true), + /*'directStreamIds' => array( + 'feed/' . $entry->feed() + ),*/ + ); + } + + echo json_encode(array( + 'itemRefs' => $itemRefs, + )), "\n"; + exit(); +} + +function editTag($e_ids, $a, $r) { + logMe("editTag()\n"); + $entryDAO = new FreshRSS_EntryDAO(); + + foreach ($e_ids as $e_id) { //TODO: User WHERE...IN + $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + switch ($a) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_id, true); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_id, true); + break; + /*case 'user/-/state/com.google/tracking-kept-unread': + break; + case 'user/-/state/com.google/like': + break; + case 'user/-/state/com.google/broadcast': + break;*/ + } + switch ($r) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_id, false); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_id, false); + break; + } + } + + echo 'OK'; + exit(); +} + +function markAllAsRead($streamId, $olderThanId) { + logMe('markAllAsRead(' . $streamId . ")\n"); + $entryDAO = new FreshRSS_EntryDAO(); + if (strpos($streamId, 'feed/') === 0) { + $f_id = basename($streamId); + $entryDAO->markReadFeed($f_id, $olderThanId); + } elseif (strpos($streamId, 'user/-/label/') === 0) { + $c_name = basename($streamId); + $entryDAO->markReadCatName($c_name, $olderThanId); + } elseif ($streamId === 'user/-/state/com.google/reading-list') { + $entryDAO->markReadEntries($olderThanId, false, -1); + } + + echo 'OK'; + exit(); +} + +logMe('----------------------------------------------------------------'."\n"); +logMe(print_r($debugInfo, true)); + +$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : $_SERVER['PATH_INFO']; +$pathInfos = explode('/', $pathInfo); + +logMe('pathInfos => ' . print_r($pathInfos, true)); + +Minz_Configuration::init(); + +if (!Minz_Configuration::apiEnabled()) { + serviceUnavailable(); +} + +Minz_Session::init('FreshRSS'); + +$user = authorizationToUser(); +$conf = null; + +logMe('User => ' . $user . "\n"); + +if ($user != null) { + try { + $conf = new FreshRSS_Configuration($user); + } catch (Exception $e) { + logMe($e->getMessage()); + $user = null; + badRequest(); + } +} + +Minz_Session::_param('currentUser', $user); + +if (count($pathInfos)<3) badRequest(); +elseif ($pathInfos[1] === 'accounts') { + if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) + clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); +} +elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfos[3]) && $pathInfos[3] === '0' && isset($pathInfos[4])) { + if ($user == null) { + unauthorized(); + } + $timestamp = isset($_GET['ck']) ? intval($_GET['ck']) : 0; //ck=[unix timestamp] : Use the current Unix time here, helps Google with caching. + switch ($pathInfos[4]) { + case 'stream': + $exclude_target = isset($_GET['xt']) ? $_GET['xt'] : ''; //xt=[exclude target] : Used to exclude certain items from the feed. For example, using xt=user/-/state/com.google/read will exclude items that the current user has marked as read, or xt=feed/[feedurl] will exclude items from a particular feed (obviously not useful in this request, but xt appears in other listing requests). + $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. + $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. + $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; //ot=[unix timestamp] : The time from which you want to retrieve items. Only items that have been crawled by Google Reader after this time will be returned. + if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6]) && isset($pathInfos[7])) { + if ($pathInfos[6] === 'feed') { + $include_target = $pathInfos[7]; + StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target); + } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { + if ($pathInfos[8] === 'state') { + if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { + if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { + $include_target = ''; + streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target); + } + } + } elseif ($pathInfos[8] === 'label') { + $include_target = $pathInfos[9]; + streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target); + } + } + } elseif ($pathInfos[5] === 'items') { + if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { + $streamId = $_GET['s']; //StreamId for which to fetch the item IDs. The parameter may be repeated to fetch the item IDs from multiple streams at once (more efficient from a backend perspective than multiple requests). + streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target); + } + } + break; + case 'tag': + if (isset($pathInfos[5]) && $pathInfos[5] === 'list') { + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + tagList($_GET['output']); + } + break; + case 'subscription': + if (isset($pathInfos[5]) && $pathInfos[5] === 'list') { + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + subscriptionList($_GET['output']); + } + break; + case 'unread-count': + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + $all = isset($_GET['all']) ? $_GET['all'] : ''; + unreadCount($all); + break; + case 'edit-tag': //http://blog.martindoms.com/2010/01/20/using-the-google-reader-api-part-3/ + $token = isset($_POST['T']) ? trim($_POST['T']) : ''; + checkToken($user, $token); + $a = isset($_POST['a']) ? $_POST['a'] : ''; //Add: user/-/state/com.google/read user/-/state/com.google/starred + $r = isset($_POST['r']) ? $_POST['r'] : ''; //Remove: user/-/state/com.google/read user/-/state/com.google/starred + $e_ids = multiplePosts('i'); //item IDs + editTag($e_ids, $a, $r); + break; + case 'mark-all-as-read': + $token = isset($_POST['T']) ? trim($_POST['T']) : ''; + checkToken($user, $token); + $streamId = $_POST['s']; //StreamId + $ts = isset($_POST['ts']) ? $_POST['ts'] : '0'; //Older than timestamp in nanoseconds + if (!ctype_digit($ts)) { + $ts = '0'; + } + markAllAsRead($streamId, $ts); + break; + case 'token': + Token($user); + break; + } +} elseif ($pathInfos[1] === 'check' && $pathInfos[2] === 'compatibility') { + checkCompatibility(); +} + +badRequest(); -- cgit v1.2.3 From ed27a69c3aa9a93552cebb587cf211a137fb71ca Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 27 Feb 2014 23:41:59 +0100 Subject: API: Better temporary security https://github.com/marienfressinaud/FreshRSS/issues/13 Slightly better security while waiting for a proper authentification system --- p/api/greader.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index 2969f5935..4122b12b9 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -21,6 +21,7 @@ Server-side API compatible with Google Reader API layer 2 */ define('TEMP_PASSWORD', 'temp123'); //Change to another ASCII password +define('TEMP_AUTH', 'XtofqkkOkCULRLH8'); //Change to another random ASCII auth require('../../constants.php'); require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader @@ -119,10 +120,14 @@ function checkCompatibility() { } function authorizationToUser() { - $auth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external - //logMe('authorizationToUser, auth => ' . $auth . "\n"); - list($userName) = explode('/', $auth); - return $userName; + $headerAuth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external + if ($headerAuth != '') { + $headerAuthX = explode('/', $headerAuth, 2); + if ((count($headerAuthX) === 2) && ($headerAuthX[1] === TEMP_AUTH)) { + return $headerAuthX[0]; + } + } + return null; } function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html @@ -131,7 +136,7 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 unauthorized(); } header('Content-Type: text/plain; charset=UTF-8'); - $auth = $email . '/' . '0123456789'; + $auth = $email . '/' . TEMP_AUTH; echo 'SID=', $auth, "\n", 'Auth=', $auth, "\n"; exit(); -- cgit v1.2.3 From d79da54c984fb4bb94bf4226d4318bfd408628db Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 27 Feb 2014 23:53:06 +0100 Subject: API: sanitize username https://github.com/marienfressinaud/FreshRSS/issues/13 --- p/api/greader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index 4122b12b9..291bcdf1f 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -124,7 +124,10 @@ function authorizationToUser() { if ($headerAuth != '') { $headerAuthX = explode('/', $headerAuth, 2); if ((count($headerAuthX) === 2) && ($headerAuthX[1] === TEMP_AUTH)) { - return $headerAuthX[0]; + $user = $headerAuthX[0]; + if (ctype_alnum($user)) { + return $user; + } } } return null; -- cgit v1.2.3 From f44683b5671b323ba96f0c4cd47ba9458e934679 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 28 Feb 2014 20:22:50 +0100 Subject: API streamContents for categories and feeds https://github.com/marienfressinaud/FreshRSS/issues/13 --- app/Controllers/configureController.php | 2 +- app/Models/CategoryDAO.php | 6 +++--- app/Models/EntryDAO.php | 4 ++-- app/Models/FeedDAO.php | 6 +++--- lib/lib_opml.php | 2 +- p/api/greader.php | 22 ++++++++++++++++------ 6 files changed, 26 insertions(+), 16 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index f831f25d0..41a7920f0 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -44,7 +44,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 'name' => $cat->name (), ); - if ($catDAO->searchByName ($newCat) == false) { + if ($catDAO->searchByName ($newCat) == null) { $catDAO->addCategory ($values); } } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 5355228a5..f3c02e3e4 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -64,7 +64,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { if (isset ($cat[0])) { return $cat[0]; } else { - return false; + return null; } } public function searchByName ($name) { @@ -80,7 +80,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { if (isset ($cat[0])) { return $cat[0]; } else { - return false; + return null; } } @@ -120,7 +120,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { public function checkDefault () { $def_cat = $this->searchById (1); - if ($def_cat === false) { + if ($def_cat == null) { $cat = new FreshRSS_Category (Minz_Translate::t ('default_category')); $cat->_id (1); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b25ae444b..3fc7a05ef 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -307,7 +307,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : false; + return isset ($entries[0]) ? $entries[0] : null; } public function searchById ($id) { @@ -320,7 +320,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : false; + return isset ($entries[0]) ? $entries[0] : null; } public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 79d8cff90..fb4a847a0 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -170,7 +170,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { if (isset ($feed[$id])) { return $feed[$id]; } else { - return false; + return null; } } public function searchByUrl ($url) { @@ -186,7 +186,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { if (isset ($feed)) { return $feed; } else { - return false; + return null; } } @@ -198,7 +198,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listCategoryNames() { + public function arrayCategoryNames() { $sql = 'SELECT f.id, c.name as c_name FROM `' . $this->prefix . 'feed` f ' . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; $stm = $this->bd->prepare ($sql); diff --git a/lib/lib_opml.php b/lib/lib_opml.php index 9feb12ae0..05e54d85e 100644 --- a/lib/lib_opml.php +++ b/lib/lib_opml.php @@ -58,7 +58,7 @@ function opml_import ($xml) { $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); $catDAO = new FreshRSS_CategoryDAO (); $cat = $catDAO->searchByName ($title); - if ($cat === false) { + if ($cat == null) { $cat = new FreshRSS_Category ($title); $values = array ( 'name' => $cat->name () diff --git a/p/api/greader.php b/p/api/greader.php index 291bcdf1f..e99e1c0c8 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -266,7 +266,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex header('Content-Type: application/json; charset=UTF-8'); $feedDAO = new FreshRSS_FeedDAO(); - $feedCategoryNames = $feedDAO->listCategoryNames(); + $arrayFeedCategoryNames = $feedDAO->arrayCategoryNames(); switch ($path) { case 'reading-list': @@ -275,8 +275,14 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex case 'starred': $type = 's'; break; + case 'feed': + $type = 'f'; + break; case 'label': $type = 'c'; + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($include_target); + $include_target = $cat == null ? -1 : $cat->id(); break; default: $type = 'A'; @@ -298,7 +304,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $items = array(); foreach ($entries as $entry) { $f_id = $entry->feed(); - $c_name = isset($feedCategoryNames[$f_id]) ? $feedCategoryNames[$f_id] : '_'; + $c_name = isset($arrayFeedCategoryNames[$f_id]) ? $arrayFeedCategoryNames[$f_id] : '_'; $item = array( 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId 'crawlTimeMsec' => substr($entry->id(), 0, -3), @@ -352,9 +358,11 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude $type = 'f'; $id = basename($streamId); } elseif (strpos($streamId, 'user/-/label/') === 0) { - $type = 'C'; + $type = 'c'; $c_name = basename($streamId); - notImplemented(); //TODO + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($c_name); + $id = $cat == null ? -1 : $cat->id(); } switch ($exclude_target) { @@ -441,7 +449,7 @@ function markAllAsRead($streamId, $olderThanId) { logMe('----------------------------------------------------------------'."\n"); logMe(print_r($debugInfo, true)); -$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : $_SERVER['PATH_INFO']; +$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']); $pathInfos = explode('/', $pathInfo); logMe('pathInfos => ' . print_r($pathInfos, true)); @@ -471,7 +479,9 @@ if ($user != null) { Minz_Session::_param('currentUser', $user); -if (count($pathInfos)<3) badRequest(); +if (count($pathInfos) < 3) { + badRequest(); +} elseif ($pathInfos[1] === 'accounts') { if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); -- cgit v1.2.3 From 29b3bbfe284a6e56413a2e89b740ffc4172c6847 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 14:45:58 +0100 Subject: API: Real password system https://github.com/marienfressinaud/FreshRSS/issues/13 Expiring token not implemented yet --- app/Controllers/usersController.php | 12 +++++ app/Models/Configuration.php | 4 ++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/configure/users.phtml | 12 ++++- p/api/greader.php | 89 ++++++++++++++++++++++--------------- 6 files changed, 82 insertions(+), 37 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index bb4f34c5e..b03989cd7 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -32,6 +32,18 @@ class FreshRSS_users_Controller extends Minz_ActionController { } Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); + $passwordPlain = Minz_Request::param('apiPasswordPlain', false); + if ($passwordPlain != '') { + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); + $passwordPlain = ''; + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $ok &= ($passwordHash != ''); + $this->view->conf->_apiPasswordHash($passwordHash); + } + if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { $this->view->conf->_mail_login(Minz_Request::param('mail_login', false)); } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 48efe3bf6..827a1d166 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -10,6 +10,7 @@ class FreshRSS_Configuration { 'mail_login' => '', 'token' => '', 'passwordHash' => '', //CRYPT_BLOWFISH + 'apiPasswordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', 'default_view' => 'not_read', @@ -165,6 +166,9 @@ class FreshRSS_Configuration { public function _passwordHash ($value) { $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } + public function _apiPasswordHash ($value) { + $this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; + } public function _mail_login ($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { diff --git a/app/i18n/en.php b/app/i18n/en.php index e67447520..d504ffc11 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -176,6 +176,7 @@ return array ( 'current_user' => 'Current user', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', 'password_form' => 'Password
(for the Web-form login method)', + 'password_api' => 'Password API
(e.g., for mobile apps)', 'persona_connection_email' => 'Login mail address
(for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 2bd4fabab..c5581a78b 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -175,6 +175,7 @@ return array ( 'current_user' => 'Utilisateur actuel', 'password_form' => 'Mot de passe
(pour connexion par formulaire)', + 'password_api' => 'Mot de passe API
(ex. : pour applis mobiles)', 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', 'persona_connection_email' => 'Adresse courriel de connexion
(pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 0677db881..f5c7dff17 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -20,7 +20,15 @@
- + /> + +
+
+ +
+ +
+ />
@@ -85,7 +93,7 @@ conf->token; ?>
- />
diff --git a/p/api/greader.php b/p/api/greader.php index e99e1c0c8..035a031dd 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -20,9 +20,6 @@ Server-side API compatible with Google Reader API layer 2 * https://github.com/theoldreader/api */ -define('TEMP_PASSWORD', 'temp123'); //Change to another ASCII password -define('TEMP_AUTH', 'XtofqkkOkCULRLH8'); //Change to another random ASCII auth - require('../../constants.php'); require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader @@ -119,14 +116,28 @@ function checkCompatibility() { exit(); } -function authorizationToUser() { +function authorizationToUserConf() { $headerAuth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external if ($headerAuth != '') { $headerAuthX = explode('/', $headerAuth, 2); - if ((count($headerAuthX) === 2) && ($headerAuthX[1] === TEMP_AUTH)) { + if (count($headerAuthX) === 2) { $user = $headerAuthX[0]; if (ctype_alnum($user)) { - return $user; + try { + $conf = new FreshRSS_Configuration($user); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + unauthorized(); + } + if ($headerAuthX[1] === sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash)) { + return $conf; + } else { + logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n"); + Minz_Log::record('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1], Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } } } @@ -135,28 +146,45 @@ function authorizationToUser() { function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html logMe('clientLogin(' . $email . ")\n"); - if ($pass !== TEMP_PASSWORD) { - unauthorized(); + if (ctype_alnum($email)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($email); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + Minz_Log::record('Invalid API user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) { + header('Content-Type: text/plain; charset=UTF-8'); + $auth = $email . '/' . sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash); + echo 'SID=', $auth, "\n", + 'Auth=', $auth, "\n"; + exit(); + } else { + Minz_Log::record('Password API mismatch for user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } - header('Content-Type: text/plain; charset=UTF-8'); - $auth = $email . '/' . TEMP_AUTH; - echo 'SID=', $auth, "\n", - 'Auth=', $auth, "\n"; - exit(); + die(); } -function token($user) { +function token($conf) { //http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ https://github.com/ericmann/gReader-Library/blob/master/greader.class.php - logMe('token('. $user . ")\n"); - $token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234'; //Must have 57 characters... + logMe('token('. $conf->user . ")\n"); //TODO: Implement real token that expires + $token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z'); //Must have 57 characters echo $token, "\n"; exit(); } -function checkToken($user, $token) { +function checkToken($conf, $token) { //http://code.google.com/p/google-reader-api/wiki/ActionToken logMe('checkToken(' . $token . ")\n"); - if ($token === 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234') { + if ($token === str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z')) { return true; } unauthorized(); @@ -462,32 +490,23 @@ if (!Minz_Configuration::apiEnabled()) { Minz_Session::init('FreshRSS'); -$user = authorizationToUser(); -$conf = null; +$conf = authorizationToUserConf(); +$user = $conf == null ? '' : $conf->user; logMe('User => ' . $user . "\n"); -if ($user != null) { - try { - $conf = new FreshRSS_Configuration($user); - } catch (Exception $e) { - logMe($e->getMessage()); - $user = null; - badRequest(); - } -} - Minz_Session::_param('currentUser', $user); if (count($pathInfos) < 3) { badRequest(); } elseif ($pathInfos[1] === 'accounts') { - if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) + if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) { clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); + } } elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfos[3]) && $pathInfos[3] === '0' && isset($pathInfos[4])) { - if ($user == null) { + if ($user == '') { unauthorized(); } $timestamp = isset($_GET['ck']) ? intval($_GET['ck']) : 0; //ck=[unix timestamp] : Use the current Unix time here, helps Google with caching. @@ -543,7 +562,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'edit-tag': //http://blog.martindoms.com/2010/01/20/using-the-google-reader-api-part-3/ $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $a = isset($_POST['a']) ? $_POST['a'] : ''; //Add: user/-/state/com.google/read user/-/state/com.google/starred $r = isset($_POST['r']) ? $_POST['r'] : ''; //Remove: user/-/state/com.google/read user/-/state/com.google/starred $e_ids = multiplePosts('i'); //item IDs @@ -551,7 +570,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'mark-all-as-read': $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $streamId = $_POST['s']; //StreamId $ts = isset($_POST['ts']) ? $_POST['ts'] : '0'; //Older than timestamp in nanoseconds if (!ctype_digit($ts)) { @@ -560,7 +579,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo markAllAsRead($streamId, $ts); break; case 'token': - Token($user); + Token($conf); break; } } elseif ($pathInfos[1] === 'check' && $pathInfos[2] === 'compatibility') { -- cgit v1.2.3 From 71f7ce1be5833b54b0f4e1f37e6557425c364725 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 15:47:15 +0100 Subject: API: SQL optimisation https://github.com/marienfressinaud/FreshRSS/issues/13 --- app/Controllers/entryController.php | 4 +++- app/Models/EntryDAO.php | 43 ++++++++++++++++++++++++++++--------- app/Models/FeedDAO.php | 6 +++--- p/api/greader.php | 11 +++------- 4 files changed, 42 insertions(+), 22 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index 1756c91e5..ca7122a7c 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -137,11 +137,13 @@ class FreshRSS_entry_Controller extends Minz_ActionController { if ($nb > 0) { $nbTotal += $nb; Minz_Log::record($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); - $feedDAO->updateLastUpdate($feed->id()); + //$feedDAO->updateLastUpdate($feed->id()); } } } + $feedDAO->updateCachedValues(); + invalidateHttpCache(); $notif = array( diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 3fc7a05ef..e90b9a7fe 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -35,11 +35,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } - public function markFavorite ($id, $is_favorite = true) { + public function markFavorite($ids, $is_favorite = true) { + if (!is_array($ids)) { + $ids = array($ids); + } $sql = 'UPDATE `' . $this->prefix . 'entry` e ' . 'SET e.is_favorite = ? ' - . 'WHERE e.id=?'; - $values = array ($is_favorite ? 1 : 0, $id); + . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)'; + $values = array ($is_favorite ? 1 : 0); + $values = array_merge($values, $ids); $stm = $this->bd->prepare ($sql); if ($stm && $stm->execute ($values)) { return $stm->rowCount(); @@ -49,6 +53,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return false; } } + public function markRead ($id, $is_read = true) { $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' . 'SET e.is_read = ?,' @@ -64,6 +69,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return false; } } + public function markReadEntries ($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { if ($idMax == 0) { $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' @@ -323,7 +329,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { $where = ''; $joinFeed = false; $values = array(); @@ -432,14 +438,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } + return array($values, + 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' + . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') + . 'WHERE ' . $where + . $search + . 'ORDER BY e1.id ' . $order + . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + } + + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' - . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') - . 'WHERE ' . $where - . $search - . 'ORDER BY e1.id ' . $order - . ($limit > 0 ? ' LIMIT ' . $limit : '') //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + . 'INNER JOIN (' + . $sql . ') e2 ON e2.id = e.id ' . 'ORDER BY e.id ' . $order; @@ -449,6 +463,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } + public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + + $stm = $this->bd->prepare($sql); + $stm->execute($values); + + return $stm->fetchAll(PDO::FETCH_COLUMN, 0); + } + public function listLastGuidsByFeed($id, $n) { $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n); $stm = $this->bd->prepare ($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index fb4a847a0..f9e1ad9e8 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -242,6 +242,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $res[0]['count']; } + public function countNotRead ($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; $stm = $this->bd->prepare ($sql); @@ -251,6 +252,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $res[0]['count']; } + public function updateCachedValues () { //For one single feed, call updateLastUpdate($id) $sql = 'UPDATE `' . $this->prefix . 'feed` f ' . 'INNER JOIN (' @@ -263,9 +265,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; $stm = $this->bd->prepare ($sql); - $values = array ($feed_id); - - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm->errorInfo(); diff --git a/p/api/greader.php b/p/api/greader.php index 035a031dd..d1846fdaf 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -403,17 +403,12 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude } $entryDAO = new FreshRSS_EntryDAO(); - $entries = $entryDAO->listWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); $itemRefs = array(); - foreach ($entries as $entry) { - $f_id = $entry->feed(); + foreach ($ids as $id) { $itemRefs[] = array( - 'id' => $entry->id(), //64-bit decimal - //'timestampUsec' => $entry->dateAdded(true), - /*'directStreamIds' => array( - 'feed/' . $entry->feed() - ),*/ + 'id' => $id, //64-bit decimal ); } -- cgit v1.2.3 From 00774f5a0bf2eacbb1825ccbf07e3fbc7b114b4d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 2 Mar 2014 11:54:52 +0100 Subject: API : SQL optimisation WHERE ... IN, and better compatibility EasyRSS https://github.com/marienfressinaud/FreshRSS/issues/13 --- app/Models/EntryDAO.php | 73 +++++++++++++++++++++++++++++++------- app/Models/FeedDAO.php | 9 +++-- p/api/greader.php | 94 ++++++++++++++++++++++++++++--------------------- 3 files changed, 120 insertions(+), 56 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index e90b9a7fe..cc52ea120 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -54,19 +54,66 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } - public function markRead ($id, $is_read = true) { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = ?,' - . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' - . 'WHERE e.id=?'; - $values = array ($is_read ? 1 : 0, $id); - $stm = $this->bd->prepare ($sql); - if ($stm && $stm->execute ($values)) { - return $stm->rowCount(); + public function markRead($ids, $is_read = true) { + if (is_array($ids)) { + if (count($ids) < 6) { //Speed heuristics + $affected = 0; + foreach ($ids as $id) { + $affected += $this->markRead($id, $is_read); + } + return $affected; + } + + $this->bd->beginTransaction(); + $sql = 'UPDATE `' . $this->prefix . 'entry` e ' + . 'SET e.is_read = ? ' + . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)'; + $values = array($is_read ? 1 : 0); + $values = array_merge($values, $ids); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm->errorInfo(); + Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + + if ($affected > 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'INNER JOIN (' + . 'SELECT e.id_feed, ' + . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, ' + . 'COUNT(e.id) AS nbEntries ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'GROUP BY e.id_feed' + . ') x ON x.id_feed=f.id ' + . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute())) { + $info = $stm->errorInfo(); + Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + } + + $this->bd->commit(); + return $affected; } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' + . 'SET e.is_read = ?,' + . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' + . 'WHERE e.id=?'; + $values = array($is_read ? 1 : 0, $ids); + $stm = $this->bd->prepare($sql); + if ($stm && $stm->execute($values)) { + return $stm->rowCount(); + } else { + $info = $stm->errorInfo(); + Minz_Log::record('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } } } @@ -463,7 +510,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); $stm = $this->bd->prepare($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index f9e1ad9e8..ca25c3aeb 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -198,15 +198,18 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function arrayCategoryNames() { - $sql = 'SELECT f.id, c.name as c_name FROM `' . $this->prefix . 'feed` f ' + public function arrayFeedCategoryNames() { //For API + $sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f ' . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; $stm = $this->bd->prepare ($sql); $stm->execute (); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $feedCategoryNames = array(); foreach ($res as $line) { - $feedCategoryNames[$line['id']] = $line['c_name']; + $feedCategoryNames[$line['id']] = array( + 'name' => $line['name'], + 'c_name' => $line['c_name'], + ); } return $feedCategoryNames; } diff --git a/p/api/greader.php b/p/api/greader.php index d1846fdaf..f26217279 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -294,7 +294,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex header('Content-Type: application/json; charset=UTF-8'); $feedDAO = new FreshRSS_FeedDAO(); - $arrayFeedCategoryNames = $feedDAO->arrayCategoryNames(); + $arrayFeedCategoryNames = $feedDAO->arrayFeedCategoryNames(); switch ($path) { case 'reading-list': @@ -332,10 +332,17 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $items = array(); foreach ($entries as $entry) { $f_id = $entry->feed(); - $c_name = isset($arrayFeedCategoryNames[$f_id]) ? $arrayFeedCategoryNames[$f_id] : '_'; + if (isset($arrayFeedCategoryNames[$f_id])) { + $c_name = $arrayFeedCategoryNames[$f_id]['c_name']; + $f_name = $arrayFeedCategoryNames[$f_id]['name']; + } else { + $c_name = '_'; + $f_name = '_'; + } $item = array( 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId 'crawlTimeMsec' => substr($entry->id(), 0, -3), + 'timestampUsec' => $entry->id(), //EasyRSS 'published' => $entry->date(true), 'title' => $entry->title(), 'summary' => array('content' => $entry->content()), @@ -348,7 +355,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex ), 'origin' => array( 'streamId' => 'feed/' . $f_id, - //'title' => $line['f_name'], + 'title' => $f_name, //EasyRSS //'htmlUrl' => $line['f_website'], ), ); @@ -420,32 +427,34 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude function editTag($e_ids, $a, $r) { logMe("editTag()\n"); + + foreach ($e_ids as $i => $e_id) { + $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + $entryDAO = new FreshRSS_EntryDAO(); - foreach ($e_ids as $e_id) { //TODO: User WHERE...IN - $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' - switch ($a) { - case 'user/-/state/com.google/read': - $entryDAO->markRead($e_id, true); - break; - case 'user/-/state/com.google/starred': - $entryDAO->markFavorite($e_id, true); - break; - /*case 'user/-/state/com.google/tracking-kept-unread': - break; - case 'user/-/state/com.google/like': - break; - case 'user/-/state/com.google/broadcast': - break;*/ - } - switch ($r) { - case 'user/-/state/com.google/read': - $entryDAO->markRead($e_id, false); - break; - case 'user/-/state/com.google/starred': - $entryDAO->markFavorite($e_id, false); - break; - } + switch ($a) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_ids, true); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_ids, true); + break; + /*case 'user/-/state/com.google/tracking-kept-unread': + break; + case 'user/-/state/com.google/like': + break; + case 'user/-/state/com.google/broadcast': + break;*/ + } + switch ($r) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_ids, false); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_ids, false); + break; } echo 'OK'; @@ -511,22 +520,27 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; //ot=[unix timestamp] : The time from which you want to retrieve items. Only items that have been crawled by Google Reader after this time will be returned. - if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6]) && isset($pathInfos[7])) { - if ($pathInfos[6] === 'feed') { - $include_target = $pathInfos[7]; - StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target); - } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { - if ($pathInfos[8] === 'state') { - if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { - if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { - $include_target = ''; - streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target); + if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6])) { + if (isset($pathInfos[7])) { + if ($pathInfos[6] === 'feed') { + $include_target = $pathInfos[7]; + StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target); + } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { + if ($pathInfos[8] === 'state') { + if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { + if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { + $include_target = ''; + streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target); + } } + } elseif ($pathInfos[8] === 'label') { + $include_target = $pathInfos[9]; + streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target); } - } elseif ($pathInfos[8] === 'label') { - $include_target = $pathInfos[9]; - streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target); } + } else { //EasyRSS + $include_target = ''; + streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target); } } elseif ($pathInfos[5] === 'items') { if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { -- cgit v1.2.3 From fc6769c1b10314b50be4a3d970c5c4917be6305c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 3 Mar 2014 21:25:14 +0100 Subject: API: Add continuation mode https://github.com/marienfressinaud/FreshRSS/issues/443 Needed for e.g. EasyRSS --- app/Controllers/indexController.php | 2 +- app/Models/EntryDAO.php | 22 +++++++++++++--------- p/api/greader.php | 30 ++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 18 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 0905e591a..2f263dff0 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -126,7 +126,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $keepHistoryDefault = $this->view->conf->keep_history_default; try { - $entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault); + $entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault); // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index cc52ea120..e4cf128ea 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -376,7 +376,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { $where = ''; $joinFeed = false; $values = array(); @@ -429,11 +429,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; - if (intval($keepHistoryDefault) === 0) { - $where .= ' AND f.keep_history <> -2'; //default + $where .= 'AND (e1.id >= ' . $date_min . '000000'; + if ($showOlderUnreadsorFavorites) { //Lax date constraint + $where .= ' OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; + if (intval($keepHistoryDefault) === 0) { + $where .= ' AND f.keep_history <> -2'; //default + } + $where .= ')'; } - $where .= ')) '; + $where .= ') '; $joinFeed = true; } $search = ''; @@ -494,8 +498,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' @@ -510,8 +514,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $stm = $this->bd->prepare($sql); $stm->execute($values); diff --git a/p/api/greader.php b/p/api/greader.php index f26217279..b1ed34d27 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -288,7 +288,7 @@ function unreadCount() { exit(); } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target) +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) {//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContents(' . $include_target . ")\n"); header('Content-Type: application/json; charset=UTF-8'); @@ -326,8 +326,12 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex break; } + if (!empty($continuation)) { + $count++; //Shift by one element + } + $entryDAO = new FreshRSS_EntryDAO(); - $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, '', $start_time); $items = array(); foreach ($entries as $entry) { @@ -371,11 +375,20 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $items[] = $item; } - echo json_encode(array( + if (!empty($continuation)) { + array_shift($items); //Discard first element that was already sent in the previous response + } + + $response = array( 'id' => 'user/-/state/com.google/reading-list', 'updated' => time(), 'items' => $items, - )), "\n"; + ); + if ((count($entries) >= $count) && (!empty($entry))) { + $response['continuation'] = $entry->id(); + } + + echo json_encode($response), "\n"; exit(); } @@ -520,27 +533,28 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; //ot=[unix timestamp] : The time from which you want to retrieve items. Only items that have been crawled by Google Reader after this time will be returned. + $continuation = isset($_GET['c']) ? $_GET['c'] : ''; //Continuation token. If a StreamContents response does not represent all items in a timestamp range, it will have a continuation attribute. The same request can be re-issued with the value of that attribute put in this parameter to get more items if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6])) { if (isset($pathInfos[7])) { if ($pathInfos[6] === 'feed') { $include_target = $pathInfos[7]; - StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target); + StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { if ($pathInfos[8] === 'state') { if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { $include_target = ''; - streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target); + streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } } elseif ($pathInfos[8] === 'label') { $include_target = $pathInfos[9]; - streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target); + streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } } else { //EasyRSS $include_target = ''; - streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target); + streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } elseif ($pathInfos[5] === 'items') { if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { -- cgit v1.2.3 From 75ce6a55bdecb2b669267c7e674b89e63c20d6ad Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 3 Mar 2014 22:10:46 +0100 Subject: API: better unread-count https://github.com/marienfressinaud/FreshRSS/issues/443 For e.g. EasyRSS --- p/api/greader.php | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index b1ed34d27..ab2a210a2 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -249,30 +249,36 @@ function subscriptionList() { exit(); } -function unreadCount() { +function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#unread-count logMe("unreadCount()\n"); header('Content-Type: application/json; charset=UTF-8'); - $pdo = new MyPDO(); - $stm = $pdo->prepare('SELECT f.id, f.lastUpdate, f.cache_nbUnreads FROM `%_feed` f'); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - $unreadcounts = array(); $totalUnreads = 0; $totalLastUpdate = 0; - foreach ($res as $line) { - $nbUnreads = $line['cache_nbUnreads']; - $totalUnreads += $nbUnreads; - $lastUpdate = $line['lastUpdate']; - if ($totalLastUpdate < $lastUpdate) { - $totalLastUpdate = $lastUpdate; + + $categoryDAO = new FreshRSS_CategoryDAO(); + foreach ($categoryDAO->listCategories(true, true) as $cat) { + $catLastUpdate = 0; + foreach ($cat->feeds() as $feed) { + $lastUpdate = $feed->lastUpdate(); + $unreadcounts[] = array( + 'id' => 'feed/' . $feed->id(), + 'count' => $feed->nbNotRead(), + 'newestItemTimestampUsec' => $lastUpdate . '000000', + ); + if ($catLastUpdate < $lastUpdate) { + $catLastUpdate = $lastUpdate; + } } $unreadcounts[] = array( - 'id' => 'feed/' . $line['id'], - 'count' => $nbUnreads, - 'newestItemTimestampUsec' => $lastUpdate . '000000', + 'id' => 'user/-/label/' . $cat->name(), + 'count' => $cat->nbNotRead(), + 'newestItemTimestampUsec' => $catLastUpdate . '000000', ); + $totalUnreads += $cat->nbNotRead(); + if ($totalLastUpdate < $catLastUpdate) { + $totalLastUpdate = $catLastUpdate; + } } $unreadcounts[] = array( @@ -288,8 +294,7 @@ function unreadCount() { exit(); } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) -{//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContents(' . $include_target . ")\n"); header('Content-Type: application/json; charset=UTF-8'); @@ -392,8 +397,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex exit(); } -function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) -{//http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed +function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) { //http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContentsItemsIds(' . $streamId . ")\n"); $type = 'A'; -- cgit v1.2.3 From c88fa62dd8728db8f58f599808f2d2be49c78f64 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 4 Mar 2014 20:51:27 +0100 Subject: API: better test for server compatibility https://github.com/marienfressinaud/FreshRSS/issues/443 --- p/api/greader.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index ab2a210a2..adc400790 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -110,9 +110,13 @@ function serviceUnavailable() { function checkCompatibility() { logMe("checkCompatibility()\n"); header('Content-Type: text/plain; charset=UTF-8'); - $ok = true; - $ok &= function_exists('getallheaders'); - echo $ok ? 'PASS' : 'FAIL'; + if (!function_exists('getallheaders')) { + die('FAIL getallheaders!'); + } + if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { + die('FAIL 64-bit or GMP extension!'); + } + echo 'PASS'; exit(); } -- cgit v1.2.3 From dffce6249778fb299229f8198252c91b5d34ceef Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 4 Mar 2014 22:32:45 +0100 Subject: API: Start of compatibility with nginx https://github.com/marienfressinaud/FreshRSS/issues/443 --- p/api/greader.php | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index adc400790..9b03bea3e 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -5,7 +5,7 @@ Server-side API compatible with Google Reader API layer 2 for the FreshRSS project http://freshrss.org == Credits == -* 2014-02: Released by Alexandre Alapetite http://alexandre.alapetite.fr +* 2014-03: Released by Alexandre Alapetite http://alexandre.alapetite.fr under GNU AGPL 3 license http://www.gnu.org/licenses/agpl-3.0.html == Documentation == @@ -24,6 +24,19 @@ require('../../constants.php'); require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader $ORIGINAL_INPUT = file_get_contents('php://input'); + +if (!function_exists('getallheaders')) { //nginx http://php.net/getallheaders#84262 + function getallheaders() { + $headers = ''; + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) === 'HTTP_') { + $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + return $headers; + } +} + $ALL_HEADERS = getallheaders(); $debugInfo = array('date' => date('c'), 'headers' => $ALL_HEADERS, '_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE, 'INPUT' => $ORIGINAL_INPUT); @@ -110,9 +123,6 @@ function serviceUnavailable() { function checkCompatibility() { logMe("checkCompatibility()\n"); header('Content-Type: text/plain; charset=UTF-8'); - if (!function_exists('getallheaders')) { - die('FAIL getallheaders!'); - } if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { die('FAIL 64-bit or GMP extension!'); } @@ -178,7 +188,8 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 } function token($conf) { -//http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ https://github.com/ericmann/gReader-Library/blob/master/greader.class.php +//http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ +//https://github.com/ericmann/gReader-Library/blob/master/greader.class.php logMe('token('. $conf->user . ")\n"); //TODO: Implement real token that expires $token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z'); //Must have 57 characters echo $token, "\n"; @@ -298,7 +309,9 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl exit(); } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContents(' . $include_target . ")\n"); header('Content-Type: application/json; charset=UTF-8'); @@ -401,7 +414,10 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex exit(); } -function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) { //http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed +function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) { +//http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContentsItemsIds(' . $streamId . ")\n"); $type = 'A'; -- cgit v1.2.3 From 0694739969dcd8531d11545d27826470146e9975 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 4 Mar 2014 22:49:54 +0100 Subject: API: put back test getallheaders https://github.com/marienfressinaud/FreshRSS/issues/443 --- p/api/greader.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index 9b03bea3e..19241d55b 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -25,7 +25,9 @@ require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader $ORIGINAL_INPUT = file_get_contents('php://input'); -if (!function_exists('getallheaders')) { //nginx http://php.net/getallheaders#84262 +$nativeGetallheaders = function_exists('getallheaders'); + +if (!$nativeGetallheaders) { //nginx http://php.net/getallheaders#84262 function getallheaders() { $headers = ''; foreach ($_SERVER as $name => $value) { @@ -126,6 +128,10 @@ function checkCompatibility() { if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { die('FAIL 64-bit or GMP extension!'); } + global $nativeGetallheaders; + if ((!$nativeGetallheaders) && isset($_SERVER['SERVER_SOFTWARE']) && (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) { + die('FAIL getallheaders! (probably)'); + } echo 'PASS'; exit(); } -- cgit v1.2.3 From ed328ae69c186148d30d458268ddc66be3dd7e72 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 10 Mar 2014 17:58:11 +0100 Subject: API: Optimisation nginx + less debug info https://github.com/marienfressinaud/FreshRSS/issues/443 Faster access to headers in nginx Show detailed debug info only in the case of errors --- p/api/greader.php | 63 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 31 deletions(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index 19241d55b..ba091db90 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -25,24 +25,6 @@ require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader $ORIGINAL_INPUT = file_get_contents('php://input'); -$nativeGetallheaders = function_exists('getallheaders'); - -if (!$nativeGetallheaders) { //nginx http://php.net/getallheaders#84262 - function getallheaders() { - $headers = ''; - foreach ($_SERVER as $name => $value) { - if (substr($name, 0, 5) === 'HTTP_') { - $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; - } - } - return $headers; - } -} - -$ALL_HEADERS = getallheaders(); - -$debugInfo = array('date' => date('c'), 'headers' => $ALL_HEADERS, '_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE, 'INPUT' => $ORIGINAL_INPUT); - if (PHP_INT_SIZE < 8) { //32-bit function dec2hex($dec) { return str_pad(gmp_strval(gmp_init($dec, 10), 16), 16, '0', STR_PAD_LEFT); @@ -60,12 +42,17 @@ if (PHP_INT_SIZE < 8) { //32-bit } function headerVariable($headerName, $varName) { - global $ALL_HEADERS; - if (empty($ALL_HEADERS[$headerName])) { - return null; + $header = ''; + $upName = 'HTTP_' . strtoupper($headerName); + if (isset($_SERVER[$upName])) { + $header = $_SERVER[$upName]; + } elseif (function_exists('getallheaders')) { + $ALL_HEADERS = getallheaders(); + if (isset($ALL_HEADERS[$headerName])) { + $header = $ALL_HEADERS[$headerName]; + } } - parse_str($ALL_HEADERS[$headerName], $pairs); - //logMe('headerVariable(' . $headerName . ') => ' . print_r($pairs, true)); + parse_str($header, $pairs); return isset($pairs[$varName]) ? $pairs[$varName] : null; } @@ -93,8 +80,24 @@ function logMe($text) { file_put_contents(LOG_PATH . '/api.log', $text, FILE_APPEND); } +function debugInfo() { + if (function_exists('getallheaders')) { + $ALL_HEADERS = getallheaders(); + } else { //nginx http://php.net/getallheaders#84262 + $ALL_HEADERS = ''; + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) === 'HTTP_') { + $ALL_HEADERS[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + } + global $ORIGINAL_INPUT; + return print_r(array('date' => date('c'), 'headers' => $ALL_HEADERS, '_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE, 'INPUT' => $ORIGINAL_INPUT), true); +} + function badRequest() { logMe("badRequest()\n"); + logMe(debugInfo()); header('HTTP/1.1 400 Bad Request'); header('Content-Type: text/plain; charset=UTF-8'); die('Bad Request!'); @@ -102,6 +105,7 @@ function badRequest() { function unauthorized() { logMe("unauthorized()\n"); + logMe(debugInfo()); header('HTTP/1.1 401 Unauthorized'); header('Content-Type: text/plain; charset=UTF-8'); header('Google-Bad-Token: true'); @@ -110,6 +114,7 @@ function unauthorized() { function notImplemented() { logMe("notImplemented()\n"); + logMe(debugInfo()); header('HTTP/1.1 501 Not Implemented'); header('Content-Type: text/plain; charset=UTF-8'); die('Not Implemented!'); @@ -128,8 +133,7 @@ function checkCompatibility() { if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { die('FAIL 64-bit or GMP extension!'); } - global $nativeGetallheaders; - if ((!$nativeGetallheaders) && isset($_SERVER['SERVER_SOFTWARE']) && (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) { + if ((!function_exists('getallheaders')) && isset($_SERVER['SERVER_SOFTWARE']) && (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) { die('FAIL getallheaders! (probably)'); } echo 'PASS'; @@ -318,7 +322,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed - logMe('streamContents(' . $include_target . ")\n"); + logMe("streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation)\n"); header('Content-Type: application/json; charset=UTF-8'); $feedDAO = new FreshRSS_FeedDAO(); @@ -424,7 +428,7 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude //http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed - logMe('streamContentsItemsIds(' . $streamId . ")\n"); + logMe("streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target)\n"); $type = 'A'; $id = ''; @@ -505,7 +509,7 @@ function editTag($e_ids, $a, $r) { } function markAllAsRead($streamId, $olderThanId) { - logMe('markAllAsRead(' . $streamId . ")\n"); + logMe("markAllAsRead($streamId, $olderThanId)\n"); $entryDAO = new FreshRSS_EntryDAO(); if (strpos($streamId, 'feed/') === 0) { $f_id = basename($streamId); @@ -522,13 +526,10 @@ function markAllAsRead($streamId, $olderThanId) { } logMe('----------------------------------------------------------------'."\n"); -logMe(print_r($debugInfo, true)); $pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']); $pathInfos = explode('/', $pathInfo); -logMe('pathInfos => ' . print_r($pathInfos, true)); - Minz_Configuration::init(); if (!Minz_Configuration::apiEnabled()) { -- cgit v1.2.3 From ab1bec28c2a1e9534a66baa25a12b4639cbed726 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 10 Mar 2014 21:06:58 +0100 Subject: API: Better compatibility with Apache/PHP-CGI https://github.com/marienfressinaud/FreshRSS/issues/443#issuecomment-37226210 --- p/api/.htaccess | 4 ++++ p/api/greader.php | 1 + 2 files changed, 5 insertions(+) create mode 100644 p/api/.htaccess (limited to 'p/api/greader.php') diff --git a/p/api/.htaccess b/p/api/.htaccess new file mode 100644 index 000000000..41b653d96 --- /dev/null +++ b/p/api/.htaccess @@ -0,0 +1,4 @@ + + RewriteEngine on + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] + diff --git a/p/api/greader.php b/p/api/greader.php index ba091db90..8ce1c5b9a 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -526,6 +526,7 @@ function markAllAsRead($streamId, $olderThanId) { } logMe('----------------------------------------------------------------'."\n"); +//logMe(debugInfo()); $pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']); $pathInfos = explode('/', $pathInfo); -- cgit v1.2.3 From f48fc2755c44d4968bce87cad13e1b4546aa7b2d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 10 Mar 2014 21:26:52 +0100 Subject: API: New test for getallheaders() problem https://github.com/marienfressinaud/FreshRSS/issues/443 --- p/api/greader.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'p/api/greader.php') diff --git a/p/api/greader.php b/p/api/greader.php index 8ce1c5b9a..741c4fbd4 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -133,7 +133,9 @@ function checkCompatibility() { if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { die('FAIL 64-bit or GMP extension!'); } - if ((!function_exists('getallheaders')) && isset($_SERVER['SERVER_SOFTWARE']) && (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) { + if ((!array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) && //Apache mod_rewrite trick should be fine + (empty($_SERVER['SERVER_SOFTWARE']) || (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) && //nginx should be fine + ((!function_exists('getallheaders')) || (stripos(php_sapi_name(), 'cgi') !== false))) { //Main problem is Apache/CGI mode die('FAIL getallheaders! (probably)'); } echo 'PASS'; -- cgit v1.2.3 From 86066b1659e33eb5fdfbcae5fb7f0bd93604d442 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 13 Apr 2014 07:28:41 -0400 Subject: Add a new status for 'ALL' I made the conversion in every file I can think of. It should not have any reference to the string 'all' for the state context --- app/Controllers/importExportController.php | 4 ++-- app/Controllers/indexController.php | 8 ++++---- app/Models/Configuration.php | 3 ++- app/Models/EntryDAO.php | 9 ++++++--- app/views/configure/reading.phtml | 2 +- p/api/greader.php | 8 ++++---- 6 files changed, 19 insertions(+), 15 deletions(-) (limited to 'p/api/greader.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index a9b103a34..b8253b7bd 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -370,7 +370,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->type = 'starred'; $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( - 's', '', 'all', 'ASC', + 's', '', FreshRSS_Configuration::STATE_ALL, 'ASC', $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { @@ -379,7 +379,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( - 'f', $feed->id(), 'all', 'ASC', + 'f', $feed->id(), FreshRSS_Configuration::STATE_ALL, 'ASC', $this->view->conf->posts_per_page ); $this->view->feed = $feed; diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 243d887ac..48471b480 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -85,7 +85,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $state_param = Minz_Request::param ('state', null); $filter = Minz_Request::param ('search', ''); if (!empty($filter)) { - $state = 'all'; //Search always in read and unread articles + $state = FreshRSS_Configuration::STATE_ALL; //Search always in read and unread articles } $this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order); $nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page); @@ -108,7 +108,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { break; } if (!$hasUnread && ($state_param === null)) { - $this->view->state = $state = 'all'; + $this->view->state = $state = FreshRSS_Configuration::STATE_ALL; } } @@ -127,8 +127,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { // on essaye de récupérer tous les articles if ($state === FreshRSS_Configuration::STATE_NOT_READ && empty($entries) && ($state_param === null)) { Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); - $this->view->state = 'all'; - $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); + $this->view->state = FreshRSS_Configuration::STATE_ALL; + $entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); } if (count($entries) <= $nb) { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index f9ea47be6..e693542e0 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -1,6 +1,7 @@ data['default_view'] = $value === 'all' ? 'all' : self::STATE_NOT_READ; + $this->data['default_view'] = $value === self::STATE_ALL ? self::STATE_ALL : self::STATE_NOT_READ; } public function _display_posts ($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 2f5a9e1f6..b9cbfd584 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -406,7 +406,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = null , $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + if (!$state) { + $state = FreshRSS_Configuration::STATE_ALL; + } $where = ''; $joinFeed = false; $values = array(); @@ -532,7 +535,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + public function listWhere($type = 'a', $id = '', $state = null, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' @@ -548,7 +551,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API + public function listIdsWhere($type = 'a', $id = '', $state = null, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $stm = $this->bd->prepare($sql); diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index b778a4d22..b277397b1 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -32,7 +32,7 @@