aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-08-24 15:29:09 +0200
committerGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-08-24 15:29:09 +0200
commit07444280d1a03daa7880c1539bde3a6e80945dab (patch)
treec328a11f59e07d12e624576ed86b11ada1b9d198 /app
parent1d2527b8bc2a125cc8b8508402417f60d314e330 (diff)
parentd1f79fee69a3667913f419cd9726ffb11f410bd0 (diff)
Merge branch 'dev' into beta
Conflicts: README.md
Diffstat (limited to 'app')
-rwxr-xr-xapp/Controllers/configureController.php2
-rw-r--r--app/Controllers/errorController.php52
-rw-r--r--app/Controllers/importExportController.php272
-rwxr-xr-xapp/Controllers/indexController.php55
-rw-r--r--app/Controllers/statsController.php53
-rw-r--r--app/Controllers/usersController.php4
-rw-r--r--app/FreshRSS.php49
-rw-r--r--app/Models/Configuration.php8
-rw-r--r--app/Models/EntryDAO.php14
-rw-r--r--app/Models/EntryDAOSQLite.php4
-rw-r--r--app/Models/Feed.php12
-rw-r--r--app/Models/StatsDAO.php132
-rw-r--r--app/Models/StatsDAOSQLite.php28
-rw-r--r--app/Models/UserDAO.php39
-rw-r--r--app/SQL/install.sql.sqlite.php1
-rw-r--r--app/i18n/en.php57
-rw-r--r--app/i18n/fr.php35
-rw-r--r--app/i18n/install.en.php4
-rw-r--r--app/i18n/install.fr.php4
-rw-r--r--app/install.php28
-rw-r--r--app/layout/aside_flux.phtml17
-rw-r--r--app/layout/aside_stats.phtml3
-rw-r--r--app/layout/layout.phtml2
-rw-r--r--app/layout/nav_menu.phtml18
-rw-r--r--app/views/configure/reading.phtml35
-rw-r--r--app/views/error/index.phtml13
-rw-r--r--app/views/helpers/export/opml.phtml6
-rw-r--r--app/views/helpers/javascript_vars.phtml3
-rwxr-xr-xapp/views/helpers/pagination.phtml2
-rw-r--r--app/views/helpers/view/normal_view.phtml17
-rw-r--r--app/views/helpers/view/reader_view.phtml18
-rw-r--r--app/views/importExport/export.phtml0
-rw-r--r--app/views/importExport/index.phtml31
-rw-r--r--app/views/index/formLogin.phtml23
-rw-r--r--app/views/javascript/actualize.phtml21
-rw-r--r--app/views/stats/idle.phtml20
-rw-r--r--app/views/stats/main.phtml127
-rw-r--r--app/views/stats/repartition.phtml114
38 files changed, 874 insertions, 449 deletions
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 79f40b30b..bb96bfae3 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -184,6 +184,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL));
$this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
$this->view->conf->_display_posts(Minz_Request::param('display_posts', false));
+ $this->view->conf->_display_categories(Minz_Request::param('display_categories', false));
+ $this->view->conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false));
$this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
$this->view->conf->_lazyload(Minz_Request::param('lazyload', false));
$this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false));
diff --git a/app/Controllers/errorController.php b/app/Controllers/errorController.php
index dc9a2ee25..922650b3d 100644
--- a/app/Controllers/errorController.php
+++ b/app/Controllers/errorController.php
@@ -1,26 +1,38 @@
<?php
class FreshRSS_error_Controller extends Minz_ActionController {
- public function indexAction () {
- switch (Minz_Request::param ('code')) {
- case 403:
- $this->view->code = 'Error 403 - Forbidden';
- break;
- case 404:
- $this->view->code = 'Error 404 - Not found';
- break;
- case 500:
- $this->view->code = 'Error 500 - Internal Server Error';
- break;
- case 503:
- $this->view->code = 'Error 503 - Service Unavailable';
- break;
- default:
- $this->view->code = 'Error 404 - Not found';
+ public function indexAction() {
+ switch (Minz_Request::param('code')) {
+ case 403:
+ $this->view->code = 'Error 403 - Forbidden';
+ break;
+ case 404:
+ $this->view->code = 'Error 404 - Not found';
+ break;
+ case 500:
+ $this->view->code = 'Error 500 - Internal Server Error';
+ break;
+ case 503:
+ $this->view->code = 'Error 503 - Service Unavailable';
+ break;
+ default:
+ $this->view->code = 'Error 404 - Not found';
}
-
- $this->view->logs = Minz_Request::param ('logs');
-
- Minz_View::prependTitle ($this->view->code . ' · ');
+
+ $errors = Minz_Request::param('logs', array());
+ $this->view->errorMessage = trim(implode($errors));
+ if ($this->view->errorMessage == '') {
+ switch(Minz_Request::param('code')) {
+ case 403:
+ $this->view->errorMessage = Minz_Translate::t('forbidden_access');
+ break;
+ case 404:
+ default:
+ $this->view->errorMessage = Minz_Translate::t('page_not_found');
+ break;
+ }
+ }
+
+ Minz_View::prependTitle($this->view->code . ' · ');
}
}
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index ba172cc6d..5adf3878a 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -5,7 +5,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
if (!$this->view->loginOk) {
Minz_Error::error(
403,
- array('error' => array(Minz_Translate::t('access_denied')))
+ array('error' => array(_t('access_denied')))
);
}
@@ -20,33 +20,51 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->view->categories = $this->catDAO->listCategories();
$this->view->feeds = $this->feedDAO->listFeeds();
- Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · ');
+ Minz_View::prependTitle(_t('import_export') . ' · ');
}
public function importAction() {
- if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) {
- @set_time_limit(300);
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
- $file = $_FILES['file'];
- $type_file = $this->guessFileType($file['name']);
+ $file = $_FILES['file'];
+ $status_file = $file['error'];
- $list_files = array(
- 'opml' => array(),
- 'json_starred' => array(),
- 'json_feed' => array()
- );
+ if ($status_file !== 0) {
+ Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file);
+ Minz_Request::bad(_t('file_cannot_be_uploaded'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+
+ @set_time_limit(300);
- // We try to list all files according to their type
- // A zip file is first opened and then its files are listed
- $list = array();
- if ($type_file === 'zip') {
- $zip = zip_open($file['tmp_name']);
+ $type_file = $this->guessFileType($file['name']);
- while (($zipfile = zip_read($zip)) !== false) {
- $type_zipfile = $this->guessFileType(
- zip_entry_name($zipfile)
- );
+ $list_files = array(
+ 'opml' => array(),
+ 'json_starred' => array(),
+ 'json_feed' => array()
+ );
+
+ // We try to list all files according to their type
+ $list = array();
+ if ($type_file === 'zip' && extension_loaded('zip')) {
+ $zip = zip_open($file['tmp_name']);
+
+ if (!is_resource($zip)) {
+ // zip_open cannot open file: something is wrong
+ Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip);
+ Minz_Request::bad(_t('zip_error'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+ while (($zipfile = zip_read($zip)) !== false) {
+ if (!is_resource($zipfile)) {
+ // zip_entry() can also return an error code!
+ Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile);
+ } else {
+ $type_zipfile = $this->guessFileType(zip_entry_name($zipfile));
if ($type_file !== 'unknown') {
$list_files[$type_zipfile][] = zip_entry_read(
$zipfile,
@@ -54,59 +72,37 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
);
}
}
-
- zip_close($zip);
- } elseif ($type_file !== 'unknown') {
- $list_files[$type_file][] = file_get_contents(
- $file['tmp_name']
- );
- }
-
- // Import different files.
- // OPML first(so categories and feeds are imported)
- // Starred articles then so the "favourite" status is already set
- // And finally all other files.
- $error = false;
- foreach ($list_files['opml'] as $opml_file) {
- $error = $this->importOpml($opml_file);
- }
- foreach ($list_files['json_starred'] as $article_file) {
- $error = $this->importArticles($article_file, true);
- }
- foreach ($list_files['json_feed'] as $article_file) {
- $error = $this->importArticles($article_file);
}
- // And finally, we get import status and redirect to the home page
- $notif = null;
- if ($error === true) {
- $content_notif = Minz_Translate::t(
- 'feeds_imported_with_errors'
- );
- } else {
- $content_notif = Minz_Translate::t(
- 'feeds_imported'
- );
- }
-
- Minz_Session::_param('notification', array(
- 'type' => 'good',
- 'content' => $content_notif
- ));
- Minz_Session::_param('actualize_feeds', true);
+ zip_close($zip);
+ } elseif ($type_file === 'zip') {
+ // Zip extension is not loaded
+ Minz_Request::bad(_t('no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
+ } elseif ($type_file !== 'unknown') {
+ $list_files[$type_file][] = file_get_contents($file['tmp_name']);
+ }
- Minz_Request::forward(array(
- 'c' => 'index',
- 'a' => 'index'
- ), true);
+ // Import file contents.
+ // OPML first(so categories and feeds are imported)
+ // Starred articles then so the "favourite" status is already set
+ // And finally all other files.
+ $error = false;
+ foreach ($list_files['opml'] as $opml_file) {
+ $error = $this->importOpml($opml_file);
+ }
+ foreach ($list_files['json_starred'] as $article_file) {
+ $error = $this->importArticles($article_file, true);
+ }
+ foreach ($list_files['json_feed'] as $article_file) {
+ $error = $this->importArticles($article_file);
}
- // What are you doing? you have to call this controller
- // with a POST request!
- Minz_Request::forward(array(
- 'c' => 'importExport',
- 'a' => 'index'
- ));
+ // And finally, we get import status and redirect to the home page
+ Minz_Session::_param('actualize_feeds', true);
+ $content_notif = $error === true ? _t('feeds_imported_with_errors') :
+ _t('feeds_imported');
+ Minz_Request::good($content_notif);
}
private function guessFileType($filename) {
@@ -120,7 +116,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
substr_compare($filename, '.xml', -4) === 0) {
return 'opml';
- } elseif (strcmp($filename, 'starred.json') === 0) {
+ } elseif (substr_compare($filename, '.json', -5) === 0 &&
+ strpos($filename, 'starred') !== false) {
return 'json_starred';
} elseif (substr_compare($filename, '.json', -5) === 0 &&
strpos($filename, 'feed_') === 0) {
@@ -176,15 +173,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
// We get different useful information
- $url = html_chars_utf8($feed_elt['xmlUrl']);
- $name = html_chars_utf8($feed_elt['text']);
+ $url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']);
+ $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']);
$website = '';
if (isset($feed_elt['htmlUrl'])) {
- $website = html_chars_utf8($feed_elt['htmlUrl']);
+ $website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']);
}
$description = '';
if (isset($feed_elt['description'])) {
- $description = html_chars_utf8($feed_elt['description']);
+ $description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description']);
}
$error = false;
@@ -210,7 +207,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
private function addCategoryOpml($cat_elt, $parent_cat) {
// Create a new Category object
- $cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text']));
+ $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text']));
$id = $this->catDAO->addCategoryObject($cat);
$error = ($id === false);
@@ -287,7 +284,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$url = $origin[$key];
$name = $origin['title'];
$website = $origin['htmlUrl'];
- $error = false;
+
try {
// Create a Feed object and add it in DB
$feed = new FreshRSS_Feed($url);
@@ -311,44 +308,53 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
public function exportAction() {
- if (Minz_Request::isPost()) {
- $this->view->_useLayout(false);
-
- $export_opml = Minz_Request::param('export_opml', false);
- $export_starred = Minz_Request::param('export_starred', false);
- $export_feeds = Minz_Request::param('export_feeds', false);
-
- // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
- $file = tempnam('tmp', 'zip');
- $zip = new ZipArchive();
- $zip->open($file, ZipArchive::OVERWRITE);
-
- // Stuff with content
- if ($export_opml) {
- $zip->addFromString(
- 'feeds.opml', $this->generateOpml()
- );
- }
- if ($export_starred) {
- $zip->addFromString(
- 'starred.json', $this->generateArticles('starred')
- );
- }
- foreach ($export_feeds as $feed_id) {
- $feed = $this->feedDAO->searchById($feed_id);
- $zip->addFromString(
- 'feed_' . $feed->category() . '_' . $feed->id() . '.json',
- $this->generateArticles('feed', $feed)
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
+
+ $this->view->_useLayout(false);
+
+ $export_opml = Minz_Request::param('export_opml', false);
+ $export_starred = Minz_Request::param('export_starred', false);
+ $export_feeds = Minz_Request::param('export_feeds', array());
+
+ $export_files = array();
+ if ($export_opml) {
+ $export_files['feeds.opml'] = $this->generateOpml();
+ }
+
+ if ($export_starred) {
+ $export_files['starred.json'] = $this->generateArticles('starred');
+ }
+
+ foreach ($export_feeds as $feed_id) {
+ $feed = $this->feedDAO->searchById($feed_id);
+ if ($feed) {
+ $filename = 'feed_' . $feed->category() . '_'
+ . $feed->id() . '.json';
+ $export_files[$filename] = $this->generateArticles(
+ 'feed', $feed
);
}
+ }
- // Close and send to user
- $zip->close();
- header('Content-Type: application/zip');
- header('Content-Length: ' . filesize($file));
- header('Content-Disposition: attachment; filename="freshrss_export.zip"');
- readfile($file);
- unlink($file);
+ $nb_files = count($export_files);
+ if ($nb_files > 1) {
+ // If there are more than 1 file to export, we need a zip archive.
+ try {
+ $this->exportZip($export_files);
+ } catch (Exception $e) {
+ # Oops, there is no Zip extension!
+ Minz_Request::bad(_t('export_no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+ } elseif ($nb_files === 1) {
+ // Only one file? Guess its type and export it.
+ $filename = key($export_files);
+ $type = $this->guessFileType($filename);
+ $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type);
+ } else {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
}
@@ -367,7 +373,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->view->categories = $this->catDAO->listCategories();
if ($type == 'starred') {
- $this->view->list_title = Minz_Translate::t('starred_list');
+ $this->view->list_title = _t('starred_list');
$this->view->type = 'starred';
$unread_fav = $this->entryDAO->countUnreadReadFavorites();
$this->view->entries = $this->entryDAO->listWhere(
@@ -375,9 +381,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$unread_fav['all']
);
} elseif ($type == 'feed' && !is_null($feed)) {
- $this->view->list_title = Minz_Translate::t(
- 'feed_list', $feed->name()
- );
+ $this->view->list_title = _t('feed_list', $feed->name());
$this->view->type = 'feed/' . $feed->id();
$this->view->entries = $this->entryDAO->listWhere(
'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
@@ -388,4 +392,44 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
return $this->view->helperToString('export/articles');
}
+
+ private function exportZip($files) {
+ if (!extension_loaded('zip')) {
+ throw new Exception();
+ }
+
+ // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
+ $zip_file = tempnam('tmp', 'zip');
+ $zip = new ZipArchive();
+ $zip->open($zip_file, ZipArchive::OVERWRITE);
+
+ foreach ($files as $filename => $content) {
+ $zip->addFromString($filename, $content);
+ }
+
+ // Close and send to user
+ $zip->close();
+ header('Content-Type: application/zip');
+ header('Content-Length: ' . filesize($zip_file));
+ header('Content-Disposition: attachment; filename="freshrss_export.zip"');
+ readfile($zip_file);
+ unlink($zip_file);
+ }
+
+ private function exportFile($filename, $content, $type) {
+ if ($type === 'unknown') {
+ return;
+ }
+
+ $content_type = '';
+ if ($type === 'opml') {
+ $content_type = "text/opml";
+ } elseif ($type === 'json_feed' || $type === 'json_starred') {
+ $content_type = "text/json";
+ }
+
+ header('Content-Type: ' . $content_type . '; charset=utf-8');
+ header('Content-disposition: attachment; filename=' . $filename);
+ print($content);
+ }
}
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 9a46bde6c..b0b051119 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -69,9 +69,6 @@ class FreshRSS_index_Controller extends Minz_ActionController {
// mise à jour des titres
$this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title();
- if ($this->view->nb_not_read > 0) {
- Minz_View::prependTitle('(' . formatNumber($this->view->nb_not_read) . ') ');
- }
Minz_View::prependTitle(
($this->nb_not_read_cat > 0 ? '(' . formatNumber($this->nb_not_read_cat) . ') ' : '') .
$this->view->currentName .
@@ -79,14 +76,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
);
// On récupère les différents éléments de filtrage
- $this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view);
+ $this->view->state = Minz_Request::param('state', $this->view->conf->default_view);
$state_param = Minz_Request::param ('state', null);
$filter = Minz_Request::param ('search', '');
$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order);
$nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page);
$first = Minz_Request::param ('next', '');
- if ($state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all?
+ if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all?
switch ($getType) {
case 'a':
$hasUnread = $this->view->nb_not_read > 0;
@@ -107,7 +104,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
break;
}
if (!$hasUnread && ($state_param === null)) {
- $this->view->state = $state = FreshRSS_Entry::STATE_ALL;
+ $this->view->state = FreshRSS_Entry::STATE_ALL;
}
}
@@ -120,11 +117,11 @@ 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, true, $keepHistoryDefault);
+ $entries = $entryDAO->listWhere($getType, $getId, $this->view->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
- if ($state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
+ if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
$feedDAO = FreshRSS_Factory::createFeedDao();
try {
@@ -135,6 +132,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$this->view->state = FreshRSS_Entry::STATE_ALL;
$entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault);
}
+ Minz_Request::_param('state', $this->view->state);
if (count($entries) <= $nb) {
$this->view->nextId = '';
@@ -298,6 +296,41 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Session::_param('passwordHash');
}
+ private static function makeLongTermCookie($username, $passwordHash) {
+ do {
+ $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true));
+ $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt';
+ } while (file_exists($tokenFile));
+ if (@file_put_contents($tokenFile, $username . "\t" . $passwordHash) === false) {
+ return false;
+ }
+ $expire = time() + 2629744; //1 month //TODO: Use a configuration instead
+ Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
+ Minz_Session::_param('token', $token);
+ return $token;
+ }
+
+ private static function deleteLongTermCookie() {
+ Minz_Session::deleteLongTermCookie('FreshRSS_login');
+ $token = Minz_Session::param('token', null);
+ if (ctype_alnum($token)) {
+ @unlink(DATA_PATH . '/tokens/' . $token . '.txt');
+ }
+ Minz_Session::_param('token');
+ if (rand(0, 10) === 1) {
+ self::purgeTokens();
+ }
+ }
+
+ private static function purgeTokens() {
+ $oldest = time() - 2629744; //1 month //TODO: Use a configuration instead
+ foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $fileInfo) {
+ if ($fileInfo->getExtension() === 'txt' && $fileInfo->getMTime() < $oldest) {
+ @unlink($fileInfo->getPathname());
+ }
+ }
+ }
+
public function formLoginAction () {
if (Minz_Request::isPost()) {
$ok = false;
@@ -315,6 +348,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
if ($ok) {
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $s);
+ if (Minz_Request::param('keep_logged_in', false)) {
+ self::makeLongTermCookie($username, $s);
+ } else {
+ self::deleteLongTermCookie();
+ }
} else {
Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING);
}
@@ -374,6 +412,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Session::_param('currentUser');
Minz_Session::_param('mail');
Minz_Session::_param('passwordHash');
+ self::deleteLongTermCookie();
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
}
}
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index 9009468bc..98f46f0d2 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -4,9 +4,9 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
public function indexAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
- Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+ Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$this->view->repartition = $statsDAO->calculateEntryRepartition();
- $this->view->count = ($statsDAO->calculateEntryCount());
+ $this->view->count = $statsDAO->calculateEntryCount();
$this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
$this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
$this->view->topFeed = $statsDAO->calculateTopFeed();
@@ -15,7 +15,13 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
public function idleAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
$feeds = $statsDAO->calculateFeedLastDate();
- $idleFeeds = array();
+ $idleFeeds = array(
+ 'last_year' => array(),
+ 'last_6_month' => array(),
+ 'last_3_month' => array(),
+ 'last_month' => array(),
+ 'last_week' => array(),
+ );
$now = new \DateTime();
$feedDate = clone $now;
$lastWeek = clone $now;
@@ -34,26 +40,37 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
if ($feedDate >= $lastWeek) {
continue;
}
- if ($feedDate < $lastWeek) {
- $idleFeeds['last_week'][] = $feed['name'];
- }
- if ($feedDate < $lastMonth) {
- $idleFeeds['last_month'][] = $feed['name'];
- }
- if ($feedDate < $last3Month) {
- $idleFeeds['last_3_month'][] = $feed['name'];
- }
- if ($feedDate < $last6Month) {
- $idleFeeds['last_6_month'][] = $feed['name'];
- }
if ($feedDate < $lastYear) {
- $idleFeeds['last_year'][] = $feed['name'];
+ $idleFeeds['last_year'][] = $feed;
+ } elseif ($feedDate < $last6Month) {
+ $idleFeeds['last_6_month'][] = $feed;
+ } elseif ($feedDate < $last3Month) {
+ $idleFeeds['last_3_month'][] = $feed;
+ } elseif ($feedDate < $lastMonth) {
+ $idleFeeds['last_month'][] = $feed;
+ } elseif ($feedDate < $lastWeek) {
+ $idleFeeds['last_week'][] = $feed;
}
}
- $this->view->idleFeeds = array_reverse($idleFeeds);
+ $this->view->idleFeeds = $idleFeeds;
}
-
+
+ public function repartitionAction() {
+ $statsDAO = FreshRSS_Factory::createStatsDAO();
+ $categoryDAO = new FreshRSS_CategoryDAO();
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+ $id = Minz_Request::param ('id', null);
+ $this->view->categories = $categoryDAO->listCategories();
+ $this->view->feed = $feedDAO->searchById($id);
+ $this->view->days = $statsDAO->getDays();
+ $this->view->months = $statsDAO->getMonths();
+ $this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+ $this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+ $this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+ }
+
public function firstAction() {
if (!$this->view->loginOk) {
Minz_Error::error(
diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php
index 35fa3675f..a9e6c32bc 100644
--- a/app/Controllers/usersController.php
+++ b/app/Controllers/usersController.php
@@ -100,7 +100,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
public function createAction() {
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
$db = Minz_Configuration::dataBase();
- require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language);
if (!in_array($new_user_language, $this->view->conf->availableLanguages())) {
@@ -172,7 +172,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
public function deleteAction() {
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
$db = Minz_Configuration::dataBase();
- require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$username = Minz_Request::param('username');
$ok = ctype_alnum($username);
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index 84cf3429b..30f711e20 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -6,17 +6,49 @@ class FreshRSS extends Minz_FrontController {
}
$loginOk = $this->accessControl(Minz_Session::param('currentUser', ''));
$this->loadParamsView();
+ if (Minz_Request::isPost() && (empty($_SERVER['HTTP_REFERER']) ||
+ Minz_Request::getDomainName() !== parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST))) {
+ $loginOk = false; //Basic protection against XSRF attacks
+ Minz_Error::error(
+ 403,
+ array('error' => array(Minz_Translate::t('access_denied') . ' [HTTP_REFERER=' .
+ htmlspecialchars(empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']) . ']'))
+ );
+ }
+ Minz_View::_param('loginOk', $loginOk);
$this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests
$this->loadNotifications();
}
+ private static function getCredentialsFromLongTermCookie() {
+ $token = Minz_Session::getLongTermCookie('FreshRSS_login');
+ if (!ctype_alnum($token)) {
+ return array();
+ }
+ $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt';
+ $mtime = @filemtime($tokenFile);
+ if ($mtime + 2629744 < time()) { //1 month //TODO: Use a configuration instead
+ @unlink($tokenFile);
+ return array(); //Expired or token does not exist
+ }
+ $credentials = @file_get_contents($tokenFile);
+ return $credentials === false ? array() : explode("\t", $credentials, 2);
+ }
+
private function accessControl($currentUser) {
if ($currentUser == '') {
switch (Minz_Configuration::authType()) {
case 'form':
- $currentUser = Minz_Configuration::defaultUser();
- Minz_Session::_param('passwordHash');
- $loginOk = false;
+ $credentials = self::getCredentialsFromLongTermCookie();
+ if (isset($credentials[1])) {
+ $currentUser = trim($credentials[0]);
+ Minz_Session::_param('passwordHash', trim($credentials[1]));
+ }
+ $loginOk = $currentUser != '';
+ if (!$loginOk) {
+ $currentUser = Minz_Configuration::defaultUser();
+ Minz_Session::_param('passwordHash');
+ }
break;
case 'http_auth':
$currentUser = httpAuthUser();
@@ -95,7 +127,6 @@ class FreshRSS extends Minz_FrontController {
break;
}
}
- Minz_View::_param ('loginOk', $loginOk);
return $loginOk;
}
@@ -127,13 +158,9 @@ class FreshRSS extends Minz_FrontController {
Minz_View::appendScript('https://login.persona.org/include.js');
break;
}
- $includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader');
- Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad);
- if ($includeLazyLoad) {
- Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js')));
- }
- Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
- Minz_View::appendScript (Minz_Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
+ Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')));
+ Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
+ Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
}
private function loadNotifications () {
diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php
index 7596c54cd..4c804a9fb 100644
--- a/app/Models/Configuration.php
+++ b/app/Models/Configuration.php
@@ -17,6 +17,8 @@ class FreshRSS_Configuration {
'default_view' => FreshRSS_Entry::STATE_NOT_READ,
'auto_load_more' => true,
'display_posts' => false,
+ 'display_categories' => false,
+ 'hide_read_feeds' => true,
'onread_jump_next' => true,
'lazyload' => true,
'sticky_post' => true,
@@ -141,6 +143,12 @@ class FreshRSS_Configuration {
public function _display_posts ($value) {
$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
}
+ public function _display_categories ($value) {
+ $this->data['display_categories'] = ((bool)$value) && $value !== 'no';
+ }
+ public function _hide_read_feeds($value) {
+ $this->data['hide_read_feeds'] = (bool)$value;
+ }
public function _onread_jump_next ($value) {
$this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no';
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 8c001e73b..75a8aeba4 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -17,7 +17,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
public function addEntry($valuesTmp, $preparedStatement = null) {
- $stm = $preparedStatement === null ? addEntryPrepare() : $preparedStatement;
+ $stm = $preparedStatement === null ?
+ FreshRSS_EntryDAO::addEntryPrepare() :
+ $preparedStatement;
$values = array(
$valuesTmp['id'],
@@ -63,7 +65,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
if (!isset($existingGuids[$entry->guid()]) &&
- ($feedHistory != 0 || $eDate >= $date_min)) {
+ ($feedHistory != 0 || $eDate >= $date_min || $entry->isFavorite())) {
$values = $entry->toArray();
$useDeclaredDate = empty($existingGuids);
@@ -173,7 +175,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
- Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
+ Minz_Log::record('Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
}
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
@@ -201,7 +203,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
public function markReadCat($id, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
- Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
+ Minz_Log::record('Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
}
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
@@ -224,11 +226,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
public function markReadFeed($id, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
- Minz_Log::record($nb . 'Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG);
+ Minz_Log::record('Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG);
}
$this->bd->beginTransaction();
- $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=1 '
. 'WHERE id_feed=? AND is_read=0 AND id <= ?';
$values = array($id, $idMax);
diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index 3dabce4b2..9dc395c3c 100644
--- a/app/Models/EntryDAOSQLite.php
+++ b/app/Models/EntryDAOSQLite.php
@@ -72,7 +72,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
- Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
+ Minz_Log::record('Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
}
$sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?';
@@ -98,7 +98,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
public function markReadCat($id, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
- Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
+ Minz_Log::record('Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
}
$sql = 'UPDATE `' . $this->prefix . 'entry` '
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 576f37760..2a5ea45ac 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -28,6 +28,12 @@ class FreshRSS_Feed extends Minz_Model {
}
}
+ public static function example() {
+ $f = new FreshRSS_Feed('http://example.net/', false);
+ $f->faviconPrepare();
+ return $f;
+ }
+
public function id() {
return $this->id;
}
@@ -277,11 +283,11 @@ class FreshRSS_Feed extends Minz_Model {
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
- $content .= '<br /><img src="' . $elink . '" alt="" />';
+ $content .= '<br /><img lazyload="" postpone="" src="' . $elink . '" alt="" />';
} elseif (strpos($mime, 'audio/') === 0) {
- $content .= '<br /><audio src="' . $elink . '" controls="controls" />';
+ $content .= '<br /><audio lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
} elseif (strpos($mime, 'video/') === 0) {
- $content .= '<br /><video src="' . $elink . '" controls="controls" />';
+ $content .= '<br /><video lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
}
}
}
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 66f5104b3..89be76a26 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -85,9 +85,83 @@ SQL;
* @return array
*/
protected function initEntryCountArray() {
+ return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
+ }
+
+ /**
+ * Calculates the number of article per hour of the day per feed
+ *
+ * @param integer $feed id
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
+ }
+
+ /**
+ * Calculates the number of article per day of week per feed
+ *
+ * @param integer $feed id
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
+ }
+
+ /**
+ * Calculates the number of article per month per feed
+ *
+ * @param integer $feed
+ * @return string
+ */
+ public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
+ return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
+ }
+
+ /**
+ * Calculates the number of article per period per feed
+ *
+ * @param string $period format string to use for grouping
+ * @param integer $feed id
+ * @return string
+ */
+ protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+ if ($feed) {
+ $restrict = "WHERE e.id_feed = {$feed}";
+ } else {
+ $restrict = '';
+ }
+ $sql = <<<SQL
+SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+ foreach ($res as $value) {
+ $repartition[(int) $value['period']] = (int) $value['count'];
+ }
+
+ return $this->convertToSerie($repartition);
+ }
+
+ /**
+ * Initialize an array for statistics depending on a range
+ *
+ * @param integer $min
+ * @param integer $max
+ * @return array
+ */
+ protected function initStatsArray($min, $max) {
return array_map(function () {
return 0;
- }, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
+ }, array_flip(range($min, $max)));
}
/**
@@ -170,7 +244,8 @@ SQL;
*/
public function calculateFeedLastDate() {
$sql = <<<SQL
-SELECT MAX(f.name) AS name
+SELECT MAX(f.id) as id
+, MAX(f.name) AS name
, MAX(date) AS last_date
FROM {$this->prefix}feed AS f,
{$this->prefix}entry AS e
@@ -204,4 +279,57 @@ SQL;
return json_encode($serie);
}
+ /**
+ * Gets days ready for graphs
+ *
+ * @return string
+ */
+ public function getDays() {
+ return $this->convertToTranslatedJson(array(
+ 'sun',
+ 'mon',
+ 'tue',
+ 'wed',
+ 'thu',
+ 'fri',
+ 'sat',
+ ));
+ }
+
+ /**
+ * Gets months ready for graphs
+ *
+ * @return string
+ */
+ public function getMonths() {
+ return $this->convertToTranslatedJson(array(
+ 'jan',
+ 'feb',
+ 'mar',
+ 'apr',
+ 'may',
+ 'jun',
+ 'jul',
+ 'aug',
+ 'sep',
+ 'oct',
+ 'nov',
+ 'dec',
+ ));
+ }
+
+ /**
+ * Translates array content and encode it as JSON
+ *
+ * @param array $data
+ * @return string
+ */
+ private function convertToTranslatedJson($data = array()) {
+ $translated = array_map(function ($a) {
+ return Minz_Translate::t($a);
+ }, $data);
+
+ return json_encode($translated);
+ }
+
}
diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php
index dea590c92..6cb54ddf6 100644
--- a/app/Models/StatsDAOSQLite.php
+++ b/app/Models/StatsDAOSQLite.php
@@ -28,10 +28,36 @@ SQL;
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($res as $value) {
- $count[(int)$value['day']] = (int) $value['count'];
+ $count[(int) $value['day']] = (int) $value['count'];
}
return $this->convertToSerie($count);
}
+ protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
+ if ($feed) {
+ $restrict = "WHERE e.id_feed = {$feed}";
+ } else {
+ $restrict = '';
+ }
+ $sql = <<<SQL
+SELECT strftime('{$period}', e.date, 'unixepoch') AS period
+, COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+{$restrict}
+GROUP BY period
+ORDER BY period ASC
+SQL;
+
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetchAll(PDO::FETCH_NAMED);
+
+ foreach ($res as $value) {
+ $repartition[(int) $value['period']] = (int) $value['count'];
+ }
+
+ return $this->convertToSerie($repartition);
+ }
+
}
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index dcf847a62..9f64fb4a7 100644
--- a/app/Models/UserDAO.php
+++ b/app/Models/UserDAO.php
@@ -3,19 +3,22 @@
class FreshRSS_UserDAO extends Minz_ModelPdo {
public function createUser($username) {
$db = Minz_Configuration::dataBase();
- require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
-
- if (defined('SQL_CREATE_TABLES')) {
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
+
+ $userPDO = new Minz_ModelPdo($username);
+
+ $ok = false;
+ if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
$sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category'));
- $stm = $c->prepare($sql);
+ $stm = $userPDO->bd->prepare($sql);
$ok = $stm && $stm->execute();
- } else {
+ } else { //E.g. SQLite
global $SQL_CREATE_TABLES;
if (is_array($SQL_CREATE_TABLES)) {
$ok = true;
foreach ($SQL_CREATE_TABLES as $instruction) {
$sql = sprintf($instruction, '', Minz_Translate::t('default_category'));
- $stm = $c->prepare($sql);
+ $stm = $userPDO->bd->prepare($sql);
$ok &= ($stm && $stm->execute());
}
}
@@ -24,7 +27,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
if ($ok) {
return true;
} else {
- $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
return false;
}
@@ -32,16 +35,22 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
public function deleteUser($username) {
$db = Minz_Configuration::dataBase();
- require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
+ require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
- $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
- $stm = $this->bd->prepare($sql);
- if ($stm && $stm->execute()) {
- return true;
+ if ($db['type'] === 'sqlite') {
+ return unlink(DATA_PATH . '/' . $username . '.sqlite');
} else {
- $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
- return false;
+ $userPDO = new Minz_ModelPdo($username);
+
+ $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
+ $stm = $userPDO->bd->prepare($sql);
+ if ($stm && $stm->execute()) {
+ return true;
+ } else {
+ $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
+ Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+ return false;
+ }
}
}
}
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index b90a5ef5e..7988ada04 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -1,4 +1,5 @@
<?php
+global $SQL_CREATE_TABLES;
$SQL_CREATE_TABLES = array(
'CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
diff --git a/app/i18n/en.php b/app/i18n/en.php
index 8634f99b5..be0cdc642 100644
--- a/app/i18n/en.php
+++ b/app/i18n/en.php
@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Login',
+ 'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
'login_with_persona' => 'Login with Persona',
'logout' => 'Logout',
'search' => 'Search words or #tags',
@@ -48,6 +49,10 @@ return array (
'stats' => 'Statistics',
'stats_idle' => 'Idle feeds',
'stats_main' => 'Main statistics',
+ 'stats_repartition' => 'Articles repartition',
+ 'stats_entry_per_hour' => 'Per hour',
+ 'stats_entry_per_day_of_week' => 'Per day of week',
+ 'stats_entry_per_month' => 'Per month',
'last_week' => 'Last week',
'last_month' => 'Last month',
@@ -177,10 +182,15 @@ return array (
'focus_search' => 'Access search box',
'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
+ 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)',
'import' => 'Import',
+ 'file_cannot_be_uploaded' => 'File cannot be uploaded!',
+ 'zip_error' => 'An error occured during Zip import.',
+ 'no_zip_extension' => 'Zip extension is not present on your server.',
'export' => 'Export',
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
+ 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.',
'starred_list' => 'List of favourite articles',
'feed_list' => 'List of %s articles',
'or' => 'or',
@@ -257,6 +267,8 @@ return array (
'sort_order' => 'Sort order',
'auto_load_more' => 'Load next articles at the page bottom',
'display_articles_unfolded' => 'Show articles unfolded by default',
+ 'display_categories_unfolded' => 'Show categories folded by default',
+ 'hide_read_feeds' => 'Hide categories &amp; feeds with no unread article (only in “unread articles” display mode)',
'after_onread' => 'After “mark all as read”,',
'jump_next' => 'jump to next unread sibling (feed or category)',
'article_icons' => 'Article icons',
@@ -339,20 +351,41 @@ return array (
'login_required' => 'Login required:',
'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!',
+ 'notif_title_new_articles' => 'FreshRSS: new articles!',
+ 'notif_body_new_articles' => 'There are \d new articles to read on FreshRSS.',
// DATE
- 'january' => 'january',
- 'february' => 'february',
- 'march' => 'march',
- 'april' => 'april',
- 'may' => 'may',
- 'june' => 'june',
- 'july' => 'july',
- 'august' => 'august',
- 'september' => 'september',
- 'october' => 'october',
- 'november' => 'november',
- 'december' => 'december',
+ 'january' => 'January',
+ 'february' => 'February',
+ 'march' => 'March',
+ 'april' => 'April',
+ 'may' => 'May',
+ 'june' => 'June',
+ 'july' => 'July',
+ 'august' => 'August',
+ 'september' => 'September',
+ 'october' => 'October',
+ 'november' => 'November',
+ 'december' => 'December',
+ 'january' => 'Jan',
+ 'february' => 'Feb',
+ 'march' => 'Mar',
+ 'april' => 'Apr',
+ 'may' => 'May',
+ 'june' => 'Jun',
+ 'july' => 'Jul',
+ 'august' => 'Aug',
+ 'september' => 'Sep',
+ 'october' => 'Oct',
+ 'november' => 'Nov',
+ 'december' => 'Dec',
+ 'sun' => 'Sun',
+ 'mon' => 'Mon',
+ 'tue' => 'Tue',
+ 'wed' => 'Wed',
+ 'thu' => 'Thu',
+ 'fri' => 'Fri',
+ 'sat' => 'Sat',
// special format for date() function
'Jan' => '\J\a\n\u\a\r\y',
'Feb' => '\F\e\b\r\u\a\r\y',
diff --git a/app/i18n/fr.php b/app/i18n/fr.php
index e04078dba..08f12234e 100644
--- a/app/i18n/fr.php
+++ b/app/i18n/fr.php
@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Connexion',
+ 'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>',
'login_with_persona' => 'Connexion avec Persona',
'logout' => 'Déconnexion',
'search' => 'Rechercher des mots ou des #tags',
@@ -48,6 +49,10 @@ return array (
'stats' => 'Statistiques',
'stats_idle' => 'Flux inactifs',
'stats_main' => 'Statistiques principales',
+ 'stats_repartition' => 'Répartition des articles',
+ 'stats_entry_per_hour' => 'Par heure',
+ 'stats_entry_per_day_of_week' => 'Par jour de la semaine',
+ 'stats_entry_per_month' => 'Par mois',
'last_week' => 'La dernière semaine',
'last_month' => 'Le dernier mois',
@@ -177,10 +182,15 @@ return array (
'focus_search' => 'Accéder à la recherche',
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
+ 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)',
'import' => 'Importer',
+ 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé!',
+ 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.',
+ 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.',
'export' => 'Exporter',
'export_opml' => 'Exporter la liste des flux (OPML)',
'export_starred' => 'Exporter les favoris',
+ 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.',
'starred_list' => 'Liste des articles favoris',
'feed_list' => 'Liste des articles de %s',
'or' => 'ou',
@@ -224,7 +234,7 @@ return array (
'persona_connection_email' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)',
'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux',
- 'unsafe_autologin' => 'Autoriser les connexion automatiques non-sûres au format : ',
+ 'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ',
'api_enabled' => 'Autoriser l’accès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>',
'auth_token' => 'Jeton d’identification',
'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>',
@@ -257,6 +267,8 @@ return array (
'sort_order' => 'Ordre de tri',
'auto_load_more' => 'Charger les articles suivants en bas de page',
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
+ 'display_categories_unfolded' => 'Afficher les catégories pliées par défaut',
+ 'hide_read_feeds' => 'Cacher les catégories &amp; flux sans article non-lu (uniquement en affichage “articles non lus”)',
'after_onread' => 'Après “marquer tout comme lu”,',
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
'article_icons' => 'Icônes d’article',
@@ -339,6 +351,8 @@ return array (
'login_required' => 'Accès protégé par mot de passe :',
'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !',
+ 'notif_title_new_articles' => 'FreshRSS : nouveaux articles !',
+ 'notif_body_new_articles' => 'Il y a \d nouveaux articles à lire sur FreshRSS.',
// DATE
'january' => 'janvier',
@@ -353,6 +367,25 @@ return array (
'october' => 'octobre',
'november' => 'novembre',
'december' => 'décembre',
+ 'jan' => 'jan.',
+ 'feb' => 'fév.',
+ 'mar' => 'mar.',
+ 'apr' => 'avr.',
+ 'may' => 'mai.',
+ 'jun' => 'juin',
+ 'jul' => 'jui.',
+ 'aug' => 'août',
+ 'sep' => 'sep.',
+ 'oct' => 'oct.',
+ 'nov' => 'nov.',
+ 'dec' => 'déc.',
+ 'sun' => 'dim.',
+ 'mon' => 'lun.',
+ 'tue' => 'mar.',
+ 'wed' => 'mer.',
+ 'thu' => 'jeu.',
+ 'fri' => 'ven.',
+ 'sat' => 'sam.',
// format spécial pour la fonction date()
'Jan' => '\j\a\n\v\i\e\r',
'Feb' => '\f\é\v\r\i\e\r',
diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php
index 553a79921..50208fcef 100644
--- a/app/i18n/install.en.php
+++ b/app/i18n/install.en.php
@@ -28,8 +28,8 @@ return array (
'minz_is_nok' => 'You lack the Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.',
'curl_is_ok' => 'You have version %s of cURL',
'curl_is_nok' => 'You lack cURL (php5-curl package)',
- 'pdomysql_is_ok' => 'You have PDO and its driver for MySQL',
- 'pdomysql_is_nok' => 'You lack PDO or its driver for MySQL (php5-mysql package)',
+ 'pdo_is_ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite)',
+ 'pdo_is_nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite)',
'dom_is_ok' => 'You have the required library to browse the DOM',
'dom_is_nok' => 'You lack a required library to browse the DOM (php-xml package)',
'pcre_is_ok' => 'You have the required library for regular expressions (PCRE)',
diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php
index 470d83e1a..9c039f904 100644
--- a/app/i18n/install.fr.php
+++ b/app/i18n/install.fr.php
@@ -28,8 +28,8 @@ return array (
'minz_is_nok' => 'Vous ne disposez pas de la librairie Minz. Vous devriez exécuter le script <em>build.sh</em> ou bien <a href="https://github.com/marienfressinaud/MINZ">la télécharger sur Github</a> et installer dans le répertoire <em>%s</em> le contenu de son répertoire <em>/lib</em>.',
'curl_is_ok' => 'Vous disposez de cURL dans sa version %s',
'curl_is_nok' => 'Vous ne disposez pas de cURL (paquet php5-curl)',
- 'pdomysql_is_ok' => 'Vous disposez de PDO et de son driver pour MySQL (paquet php5-mysql)',
- 'pdomysql_is_nok' => 'Vous ne disposez pas de PDO ou de son driver pour MySQL',
+ 'pdo_is_ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite)',
+ 'pdo_is_nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite)',
'dom_is_ok' => 'Vous disposez du nécessaire pour parcourir le DOM',
'dom_is_nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml)',
'pcre_is_ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE)',
diff --git a/app/install.php b/app/install.php
index 3767e3d91..eaa1100c1 100644
--- a/app/install.php
+++ b/app/install.php
@@ -249,11 +249,11 @@ function saveStep3 () {
'base_url' => '',
'title' => $_SESSION['title'],
'default_user' => $_SESSION['default_user'],
- 'auth_type' => $_SESSION['auth_type'],
'allow_anonymous' => isset($_SESSION['allow_anonymous']) ? $_SESSION['allow_anonymous'] : false,
- 'allow_anonymous_refresh' => false,
- 'unsafe_autologin_enabled' => false,
- 'api_enabled' => false,
+ 'allow_anonymous_refresh' => isset($_SESSION['allow_anonymous_refresh']) ? $_SESSION['allow_anonymous_refresh'] : false,
+ 'auth_type' => $_SESSION['auth_type'],
+ 'api_enabled' => isset($_SESSION['api_enabled']) ? $_SESSION['api_enabled'] : false,
+ 'unsafe_autologin_enabled' => isset($_SESSION['unsafe_autologin_enabled']) ? $_SESSION['unsafe_autologin_enabled'] : false,
),
'db' => array(
'type' => $_SESSION['bd_type'],
@@ -499,7 +499,7 @@ function checkStep0 () {
if ($ini_array) {
$ini_general = isset($ini_array['general']) ? $ini_array['general'] : null;
if ($ini_general) {
- $keys = array('environment', 'salt', 'title', 'default_user', 'allow_anonymous', 'auth_type');
+ $keys = array('environment', 'salt', 'title', 'default_user', 'allow_anonymous', 'allow_anonymous_refresh', 'auth_type', 'api_enabled', 'unsafe_autologin_enabled');
foreach ($keys as $key) {
if ((empty($_SESSION[$key])) && isset($ini_general[$key])) {
$_SESSION[$key] = $ini_general[$key];
@@ -574,7 +574,9 @@ function checkStep1 () {
$php = version_compare (PHP_VERSION, '5.2.1') >= 0;
$minz = file_exists (LIB_PATH . '/Minz');
$curl = extension_loaded ('curl');
- $pdo = extension_loaded ('pdo_mysql');
+ $pdo_mysql = extension_loaded ('pdo_mysql');
+ $pdo_sqlite = extension_loaded ('pdo_sqlite');
+ $pdo = $pdo_mysql || $pdo_sqlite;
$pcre = extension_loaded ('pcre');
$ctype = extension_loaded ('ctype');
$dom = class_exists('DOMDocument');
@@ -588,7 +590,9 @@ function checkStep1 () {
'php' => $php ? 'ok' : 'ko',
'minz' => $minz ? 'ok' : 'ko',
'curl' => $curl ? 'ok' : 'ko',
- 'pdo-mysql' => $pdo ? 'ok' : 'ko',
+ 'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko',
+ 'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko',
+ 'pdo' => $pdo ? 'ok' : 'ko',
'pcre' => $pcre ? 'ok' : 'ko',
'ctype' => $ctype ? 'ok' : 'ko',
'dom' => $dom ? 'ok' : 'ko',
@@ -766,10 +770,10 @@ function printStep1 () {
<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('minz_is_nok', LIB_PATH . '/Minz'); ?></p>
<?php } ?>
- <?php if ($res['pdo-mysql'] == 'ok') { ?>
- <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pdomysql_is_ok'); ?></p>
+ <?php if ($res['pdo'] == 'ok') { ?>
+ <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pdo_is_ok'); ?></p>
<?php } else { ?>
- <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pdomysql_is_nok'); ?></p>
+ <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pdo_is_nok'); ?></p>
<?php } ?>
<?php if ($res['curl'] == 'ok') { ?>
@@ -923,14 +927,18 @@ function printStep3 () {
<label class="group-name" for="type"><?php echo _t ('bdd_type'); ?></label>
<div class="group-controls">
<select name="type" id="type" onchange="mySqlShowHide()">
+ <?php if (extension_loaded('pdo_mysql')) {?>
<option value="mysql"
<?php echo (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql') ? 'selected="selected"' : ''; ?>>
MySQL
</option>
+ <?php }?>
+ <?php if (extension_loaded('pdo_sqlite')) {?>
<option value="sqlite"
<?php echo (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'sqlite') ? 'selected="selected"' : ''; ?>>
SQLite
</option>
+ <?php }?>
</select>
</div>
</div>
diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml
index 817dae676..432d6fdb7 100644
--- a/app/layout/aside_flux.phtml
+++ b/app/layout/aside_flux.phtml
@@ -1,4 +1,4 @@
-<div class="aside aside_flux" id="aside_flux">
+<div class="aside aside_flux<?php if ($this->conf->hide_read_feeds && ($this->state & FreshRSS_Entry::STATE_NOT_READ) && !($this->state & FreshRSS_Entry::STATE_READ)) echo ' state_unread'; ?>" id="aside_flux">
<a class="toggle_aside" href="#close"><?php echo FreshRSS_Themes::icon('close'); ?></a>
<ul class="categories">
@@ -41,11 +41,17 @@
foreach ($this->cat_aside as $cat) {
$feeds = $cat->feeds ();
if (!empty ($feeds)) {
- ?><li><?php
$c_active = false;
- if ($this->get_c == $cat->id ()) {
- $c_active = true;
+ if ($this->conf->display_categories) {
+ if ($this->get_c == $cat->id () && $this->get_f) {
+ $c_active = true;
+ }
+ } else {
+ if ($this->get_c == $cat->id ()) {
+ $c_active = true;
+ }
}
+ ?><li data-unread="<?php echo $cat->nbNotRead(); ?>"<?php if ($c_active) echo ' class="active"'; ?>><?php
?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php
?><a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name (); ?></a><?php
?><a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a><?php
@@ -55,7 +61,7 @@
$feed_id = $feed->id ();
$nbEntries = $feed->nbEntries ();
$f_active = ($this->get_f == $feed_id);
- ?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"><?php
+ ?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>"><?php
?><div class="dropdown"><?php
?><div class="dropdown-target"></div><?php
?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a><?php
@@ -77,6 +83,7 @@
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close">❌</a></li>
<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('filter'); ?></a></li>
+ <li class="item"><a href="<?php echo _url ('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
<li class="item"><a target="_blank" href="http://example.net/"><?php echo Minz_Translate::t ('see_website'); ?></a></li>
<?php if ($this->loginOk) { ?>
<li class="separator"></li>
diff --git a/app/layout/aside_stats.phtml b/app/layout/aside_stats.phtml
index 32a3f5dee..fbfb9d84d 100644
--- a/app/layout/aside_stats.phtml
+++ b/app/layout/aside_stats.phtml
@@ -6,4 +6,7 @@
<li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>">
<a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a>
</li>
+ <li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>">
+ <a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a>
+ </li>
</ul>
diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml
index d2e1e4b3b..96a88d245 100644
--- a/app/layout/layout.phtml
+++ b/app/layout/layout.phtml
@@ -16,7 +16,7 @@
?>
<link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display(array('c' => Minz_Request::controllerName(), 'a' => Minz_Request::actionName(), 'params' => $params)); ?>" />
<?php } ?>
- <link rel="shortcut icon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
+ <link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" />
<?php
if (isset($this->url)) {
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index 29ea9032c..25833c16d 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -164,11 +164,15 @@
break;
}
}
- if ($this->order === 'ASC') {
- $idMax = 0;
- } else {
- $p = isset($this->entries[0]) ? $this->entries[0] : null;
- $idMax = $p === null ? '0' : $p->id();
+
+ $p = isset($this->entries[0]) ? $this->entries[0] : null;
+ $idMax = $p === null ? (time() - 1) . '000000' : $p->id();
+
+ if ($this->order === 'ASC') { //In this case we do not know but we guess idMax
+ $idMax2 = (time() - 1) . '000000';
+ if (strcmp($idMax2, $idMax) > 0) {
+ $idMax = $idMax2;
+ }
}
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax));
@@ -221,7 +225,9 @@
<?php
$url_output['params']['output'] = 'rss';
- $url_output['params']['token'] = $this->conf->token;
+ if ($this->conf->token) {
+ $url_output['params']['token'] = $this->conf->token;
+ }
?>
<a class="view_rss btn" target="_blank" title="<?php echo Minz_Translate::t ('rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
<?php echo FreshRSS_Themes::icon('rss'); ?>
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index 4d439e83d..5a26501a4 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -9,7 +9,7 @@
<div class="form-group">
<label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('articles_per_page'); ?></label>
<div class="group-controls">
- <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" />
+ <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" min="5" max="50" />
</div>
</div>
@@ -44,10 +44,9 @@
<div class="form-group">
<div class="group-controls">
- <label class="checkbox" for="auto_load_more">
- <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> />
- <?php echo Minz_Translate::t ('auto_load_more'); ?>
- <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
+ <label class="checkbox" for="hide_read_feeds">
+ <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo $this->conf->hide_read_feeds ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t('hide_read_feeds'); ?>
</label>
</div>
</div>
@@ -64,9 +63,9 @@
<div class="form-group">
<div class="group-controls">
- <label class="checkbox" for="lazyload">
- <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> />
- <?php echo Minz_Translate::t ('img_with_lazyload'); ?>
+ <label class="checkbox" for="display_categories">
+ <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo $this->conf->display_categories ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t ('display_categories_unfolded'); ?>
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
</label>
</div>
@@ -84,6 +83,26 @@
<div class="form-group">
<div class="group-controls">
+ <label class="checkbox" for="auto_load_more">
+ <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t ('auto_load_more'); ?>
+ <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="group-controls">
+ <label class="checkbox" for="lazyload">
+ <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t ('img_with_lazyload'); ?>
+ <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="group-controls">
<label class="checkbox" for="reading_confirm">
<input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo $this->conf->reading_confirm ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('reading_confirm'); ?>
diff --git a/app/views/error/index.phtml b/app/views/error/index.phtml
index 6a09c3aa2..ef4fbd39d 100644
--- a/app/views/error/index.phtml
+++ b/app/views/error/index.phtml
@@ -1,18 +1,9 @@
<div class="post">
<div class="alert alert-error">
<h1 class="alert-head"><?php echo $this->code; ?></h1>
-
<p>
- <?php
- switch(Minz_Request::param ('code')) {
- case 403:
- echo Minz_Translate::t ('forbidden_access');
- break;
- case 404:
- default:
- echo Minz_Translate::t ('page_not_found');
- } ?><br />
- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
+ <?php echo $this->errorMessage; ?><br />
+ <a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a>
</p>
</div>
</div>
diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml
index f667d093c..8622d9144 100644
--- a/app/views/helpers/export/opml.phtml
+++ b/app/views/helpers/export/opml.phtml
@@ -18,9 +18,9 @@ foreach ($this->categories as $key => $cat) {
$opml_array['body'][$key]['@outlines'][] = array(
'text' => htmlspecialchars_decode($feed->name()),
'type' => 'rss',
- 'xmlUrl' => $feed->url(),
- 'htmlUrl' => $feed->website(),
- 'description' => $feed->description()
+ 'xmlUrl' => htmlspecialchars_decode($feed->url()),
+ 'htmlUrl' => htmlspecialchars_decode($feed->website()),
+ 'description' => htmlspecialchars_decode($feed->description()),
);
}
}
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml
index 6e0a20de3..7144c519a 100644
--- a/app/views/helpers/javascript_vars.phtml
+++ b/app/views/helpers/javascript_vars.phtml
@@ -10,7 +10,6 @@ echo 'var ',
',auto_mark_site=', $mark['site'] ? 'true' : 'false',
',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false',
',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false',
- ',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false',
',does_lazyload=', $this->conf->lazyload ? 'true' : 'false',
',sticky_post=', $this->conf->sticky_post ? 'true' : 'false';
@@ -50,6 +49,8 @@ echo 'authType="', $authType, '",',
'url_logout="', _url ('index', 'logout'), '",';
echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n";
+echo 'str_notif_title_articles="', Minz_Translate::t('notif_title_new_articles'), '"', ",\n";
+echo 'str_notif_body_articles="', Minz_Translate::t('notif_body_new_articles'), '"', ",\n";
$autoActualise = Minz_Session::param('actualize_feeds', false);
echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n";
diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml
index f38913c06..f237e1f3e 100755
--- a/app/views/helpers/pagination.phtml
+++ b/app/views/helpers/pagination.phtml
@@ -14,7 +14,7 @@
<?php } elseif ($markReadUrl) { ?>
<a id="bigMarkAsRead" href="<?php echo $markReadUrl; ?>"<?php if ($this->conf->reading_confirm) { echo ' class="confirm"';} ?>>
<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
- <span class="bigTick">✔</span><br />
+ <span class="bigTick">✓</span><br />
<?php echo Minz_Translate::t ('mark_all_read'); ?>
</a>
<?php } else { ?>
diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml
index 6f172d579..87bf2e22a 100644
--- a/app/views/helpers/view/normal_view.phtml
+++ b/app/views/helpers/view/normal_view.phtml
@@ -81,7 +81,12 @@ if (!empty($this->entries)) {
}
}
$feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache
- if (empty($feed)) $feed = $item->feed (true);
+ if ($feed == null) {
+ $feed = $item->feed(true);
+ if ($feed == null) {
+ $feed = FreshRSS_Feed::example();
+ }
+ }
?><li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li>
<li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li>
<?php if ($topline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
@@ -92,13 +97,9 @@ if (!empty($this->entries)) {
<div class="content <?php echo $content_width; ?>">
<h1 class="title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></h1>
<?php
- $author = $item->author ();
- echo $author != '' ? '<div class="author">' . Minz_Translate::t ('by_author', $author) . '</div>' : '';
- if ($lazyload) {
- echo $hidePosts ? lazyIframe(lazyimg($item->content())) : lazyimg($item->content());
- } else {
- echo $item->content();
- }
+ $author = $item->author();
+ echo $author != '' ? '<div class="author">' . Minz_Translate::t('by_author', $author) . '</div>' : '',
+ $lazyload && $hidePosts ? lazyimg($item->content()) : $item->content();
?>
</div>
<ul class="horizontal-list bottom"><?php
diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml
index e37c78cb4..665f72849 100644
--- a/app/views/helpers/view/reader_view.phtml
+++ b/app/views/helpers/view/reader_view.phtml
@@ -21,19 +21,13 @@ if (!empty($this->entries)) {
</a>
<h1 class="title"><?php echo $item->title (); ?></h1>
- <div class="author">
- <?php $author = $item->author (); ?>
- <?php echo $author != '' ? Minz_Translate::t ('by_author', $author) . ' — ' : ''; ?>
- <?php echo $item->date (); ?>
- </div>
+ <div class="author"><?php
+ $author = $item->author();
+ echo $author != '' ? Minz_Translate::t('by_author', $author) . ' — ' : '',
+ $item->date();
+ ?></div>
- <?php
- if ($lazyload) {
- echo lazyimg($item->content ());
- } else {
- echo $item->content();
- }
- ?>
+ <?php echo $item->content(); ?>
</div>
</div>
</div>
diff --git a/app/views/importExport/export.phtml b/app/views/importExport/export.phtml
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/views/importExport/export.phtml
diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml
index 309058959..35371faca 100644
--- a/app/views/importExport/index.phtml
+++ b/app/views/importExport/index.phtml
@@ -1,12 +1,14 @@
-<?php $this->partial ('aside_feed'); ?>
+<?php $this->partial('aside_feed'); ?>
<div class="post ">
- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
+ <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
<form method="post" action="<?php echo _url('importExport', 'import'); ?>" enctype="multipart/form-data">
- <legend><?php echo Minz_Translate::t ('import'); ?></legend>
+ <legend><?php echo _t('import'); ?></legend>
<div class="form-group">
- <label class="group-name" for="file"><?php echo Minz_Translate::t ('file_to_import'); ?></label>
+ <label class="group-name" for="file">
+ <?php echo extension_loaded('zip') ? _t('file_to_import') : _t('file_to_import_no_zip'); ?>
+ </label>
<div class="group-controls">
<input type="file" name="file" id="file" />
</div>
@@ -14,27 +16,34 @@
<div class="form-group form-actions">
<div class="group-controls">
- <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('import'); ?></button>
+ <button type="submit" class="btn btn-important"><?php echo _t('import'); ?></button>
</div>
</div>
</form>
<?php if (count($this->feeds) > 0) { ?>
<form method="post" action="<?php echo _url('importExport', 'export'); ?>">
- <legend><?php echo Minz_Translate::t ('export'); ?></legend>
+ <legend><?php echo _t('export'); ?></legend>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="export_opml">
<input type="checkbox" name="export_opml" id="export_opml" value="1" checked="checked" />
- <?php echo Minz_Translate::t ('export_opml'); ?>
+ <?php echo _t('export_opml'); ?>
</label>
<label class="checkbox" for="export_starred">
- <input type="checkbox" name="export_starred" id="export_starred" value="1" checked="checked" />
- <?php echo Minz_Translate::t ('export_starred'); ?>
+ <input type="checkbox" name="export_starred" id="export_starred" value="1" <?php echo extension_loaded('zip') ? 'checked="checked"' : ''; ?> />
+ <?php echo _t('export_starred'); ?>
</label>
- <select name="export_feeds[]" size="<?php echo min(10, count($this->feeds)); ?>" multiple="multiple">
+ <?php
+ $select_args = '';
+ if (extension_loaded('zip')) {
+ $select_args = ' size="' . min(10, count($this->feeds)) .'" multiple="multiple"';
+ }
+ ?>
+ <select name="export_feeds[]"<?php echo $select_args; ?>>
+ <?php echo extension_loaded('zip') ? '' : '<option></option>'; ?>
<?php foreach ($this->feeds as $feed) { ?>
<option value="<?php echo $feed->id(); ?>"><?php echo $feed->name(); ?></option>
<?php } ?>
@@ -44,7 +53,7 @@
<div class="form-group form-actions">
<div class="group-controls">
- <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('export'); ?></button>
+ <button type="submit" class="btn btn-important"><?php echo _t('export'); ?></button>
</div>
</div>
</form>
diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml
index cc925ea59..b79c1b614 100644
--- a/app/views/index/formLogin.phtml
+++ b/app/views/index/formLogin.phtml
@@ -1,32 +1,39 @@
<div class="prompt">
- <h1><?php echo Minz_Translate::t('login'); ?></h1><?php
+ <h1><?php echo _t('login'); ?></h1><?php
switch (Minz_Configuration::authType()) {
case 'form':
?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
<div>
- <label for="username"><?php echo Minz_Translate::t('username'); ?></label>
+ <label for="username"><?php echo _t('username'); ?></label>
<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
</div>
<div>
- <label for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
+ <label for="passwordPlain"><?php echo _t('password'); ?></label>
<input type="password" id="passwordPlain" required="required" />
<input type="hidden" id="challenge" name="challenge" /><br />
- <noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
+ <noscript><strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript>
</div>
<div>
- <button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
+ <label class="checkbox" for="keep_logged_in">
+ <input type="checkbox" name="keep_logged_in" id="keep_logged_in" value="1" />
+ <?php echo _t('keep_logged_in'); ?>
+ </label>
+ <br />
+ </div>
+ <div>
+ <button id="loginButton" type="submit" class="btn btn-important"><?php echo _t('login'); ?></button>
</div>
</form><?php
break;
case 'persona':
?><p>
- <?php echo FreshRSS_Themes::icon('login'); ?>
- <a class="signin" href="#"><?php echo Minz_Translate::t('login_with_persona'); ?></a>
+ <?php echo _i('login'); ?>
+ <a class="signin" href="#"><?php echo _t('login_with_persona'); ?></a>
</p><?php
break;
} ?>
- <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p>
+ <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p>
</div>
diff --git a/app/views/javascript/actualize.phtml b/app/views/javascript/actualize.phtml
index d08dc47d1..74cef4998 100644
--- a/app/views/javascript/actualize.phtml
+++ b/app/views/javascript/actualize.phtml
@@ -1,25 +1,24 @@
"use strict";
-var feeds = [<?php
- foreach ($this->feeds as $feed) {
- echo "'", Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'), "',\n";
- }
- ?>],
+var feeds = [<?php foreach ($this->feeds as $feed) { ?>{<?php
+ ?>url: "<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'); ?>",<?php
+ ?>title: "<?php echo $feed->name(); ?>"<?php
+?>},<?php } ?>],
feed_processed = 0,
feed_count = feeds.length;
function initProgressBar(init) {
if (init) {
$("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\
- <?php echo _t('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
- <progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feed_count + "\"></progress>\
+ <?php echo _t('refresh'); ?><br /><span class=\"title\">/</span><br />\
+ <span class=\"progress\">0 / " + feed_count + "</span>\
</div>");
} else {
window.location.reload();
}
}
-function updateProgressBar(i) {
- $("#actualizeProgressBar").val(i);
+function updateProgressBar(i, title_feed) {
$("#actualizeProgress .progress").html(i + " / " + feed_count);
+ $("#actualizeProgress .title").html(title_feed);
}
function updateFeeds() {
@@ -43,10 +42,10 @@ function updateFeed() {
$.ajax({
type: 'POST',
- url: feed,
+ url: feed['url'],
}).complete(function (data) {
feed_processed++;
- updateProgressBar(feed_processed);
+ updateProgressBar(feed_processed, feed['title']);
if (feed_processed === feed_count) {
initProgressBar(false);
diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml
index 356fea20f..2ba5237f7 100644
--- a/app/views/stats/idle.phtml
+++ b/app/views/stats/idle.phtml
@@ -1,19 +1,25 @@
<?php $this->partial('aside_stats'); ?>
<div class="post content">
- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
+ <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
- <h1><?php echo _t ('stats_idle'); ?></h1>
+ <h1><?php echo _t('stats_idle'); ?></h1>
- <?php foreach ($this->idleFeeds as $period => $feeds){ ?>
+ <?php
+ foreach ($this->idleFeeds as $period => $feeds) {
+ if (!empty($feeds)) {
+ ?>
<div class="stat">
- <h2><?php echo _t ($period); ?></h2>
+ <h2><?php echo _t($period); ?></h2>
<ul>
- <?php foreach ($feeds as $feed){ ?>
- <li><?php echo $feed; ?></li>
+ <?php foreach ($feeds as $feed) { ?>
+ <li><a href="<?php echo _url('configure', 'feed', 'id', $feed['id']); ?>" title="<?php echo date('Y-m-d', $feed['last_date']); ?>"><?php echo $feed['name']; ?></a></li>
<?php } ?>
</ul>
</div>
- <?php } ?>
+ <?php
+ }
+ }
+ ?>
</div>
diff --git a/app/views/stats/main.phtml b/app/views/stats/main.phtml
deleted file mode 100644
index fe372e221..000000000
--- a/app/views/stats/main.phtml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?php $this->partial('aside_stats'); ?>
-
-<div class="post content">
- <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
-
- <h1><?php echo Minz_Translate::t ('stats_main'); ?></h1>
-
- <div class="stat">
- <h2><?php echo Minz_Translate::t ('stats_entry_repartition'); ?></h2>
- <table>
- <thead>
- <tr>
- <th> </th>
- <th><?php echo Minz_Translate::t ('main_stream'); ?></th>
- <th><?php echo Minz_Translate::t ('all_feeds'); ?></th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <th><?php echo Minz_Translate::t ('status_total'); ?></th>
- <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
- <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
- </tr>
- <tr>
- <th><?php echo Minz_Translate::t ('status_read'); ?></th>
- <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
- <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
- </tr>
- <tr>
- <th><?php echo Minz_Translate::t ('status_unread'); ?></th>
- <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
- <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
- </tr>
- <tr>
- <th><?php echo Minz_Translate::t ('status_favorites'); ?></th>
- <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
- <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td>
- </tr>
- </tbody>
- </table>
- </div>
-
- <div class="stat">
- <h2><?php echo Minz_Translate::t ('stats_entry_per_day'); ?></h2>
- <div id="statsEntryPerDay" style="height: 300px"></div>
- </div>
-
- <div class="stat">
- <h2><?php echo Minz_Translate::t ('stats_feed_per_category'); ?></h2>
- <div id="statsFeedPerCategory" style="height: 300px"></div>
- <div id="statsFeedPerCategoryLegend"></div>
- </div>
-
- <div class="stat">
- <h2><?php echo Minz_Translate::t ('stats_entry_per_category'); ?></h2>
- <div id="statsEntryPerCategory" style="height: 300px"></div>
- <div id="statsEntryPerCategoryLegend"></div>
- </div>
-
- <div class="stat">
- <h2><?php echo Minz_Translate::t ('stats_top_feed'); ?></h2>
- <table>
- <thead>
- <tr>
- <th><?php echo Minz_Translate::t ('feed'); ?></th>
- <th><?php echo Minz_Translate::t ('category'); ?></th>
- <th><?php echo Minz_Translate::t ('stats_entry_count'); ?></th>
- </tr>
- </thead>
- <tbody>
- <?php foreach ($this->topFeed as $feed): ?>
- <tr>
- <td><?php echo $feed['name']; ?></td>
- <td><?php echo $feed['category']; ?></td>
- <td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
- </tr>
- <?php endforeach;?>
- </tbody>
- </table>
- </div>
-</div>
-
-<script>
-"use strict";
-function initStats() {
- if (!window.Flotr) {
- if (window.console) {
- console.log('FreshRSS waiting for Flotr…');
- }
- window.setTimeout(initStats, 50);
- return;
- }
- // Entry per day
- Flotr.draw(document.getElementById('statsEntryPerDay'),
- [<?php echo $this->count ?>],
- {
- grid: {verticalLines: false},
- bars: {horizontal: false, show: true},
- xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
- yaxis: {min: 0},
- mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
- });
- // Feed per category
- Flotr.draw(document.getElementById('statsFeedPerCategory'),
- <?php echo $this->feedByCategory ?>,
- {
- grid: {verticalLines: false, horizontalLines: false},
- pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
- xaxis: {showLabels: false},
- yaxis: {showLabels: false},
- mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
- legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
- });
- // Entry per category
- Flotr.draw(document.getElementById('statsEntryPerCategory'),
- <?php echo $this->entryByCategory ?>,
- {
- grid: {verticalLines: false, horizontalLines: false},
- pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
- xaxis: {showLabels: false},
- yaxis: {showLabels: false},
- mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
- legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
- });
-}
-initStats();
-</script>
diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml
new file mode 100644
index 000000000..9d2eb28e4
--- /dev/null
+++ b/app/views/stats/repartition.phtml
@@ -0,0 +1,114 @@
+<?php $this->partial('aside_stats'); ?>
+
+<div class="post content">
+ <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
+
+ <h1><?php echo _t('stats_repartition'); ?></h1>
+
+ <select id="feed_select">
+ <option data-url="<?php echo _url('stats', 'repartition')?>"><?php echo _t('all_feeds')?></option>
+ <?php foreach ($this->categories as $category) {
+ $feeds = $category->feeds();
+ if (!empty($feeds)) {
+ echo '<optgroup label=', $category->name(), '>';
+ foreach ($feeds as $feed) {
+ if ($this->feed && $feed->id() == $this->feed->id()){
+ echo '<option value ="', $feed->id(), '" selected data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
+ } else {
+ echo '<option value ="', $feed->id(), '" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
+ }
+ }
+ echo '</optgroup>';
+ }
+ }?>
+ </select>
+
+ <?php if ($this->feed) {?>
+ <a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
+ <?php echo _t('administration'); ?>
+ </a>
+ <?php }?>
+
+ <div class="stat">
+ <h2><?php echo _t('stats_entry_per_hour'); ?></h2>
+ <div id="statsEntryPerHour" style="height: 300px"></div>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2>
+ <div id="statsEntryPerDayOfWeek" style="height: 300px"></div>
+ </div>
+
+ <div class="stat">
+ <h2><?php echo _t('stats_entry_per_month'); ?></h2>
+ <div id="statsEntryPerMonth" style="height: 300px"></div>
+ </div>
+</div>
+
+<script>
+"use strict";
+function initStats() {
+ if (!window.Flotr) {
+ if (window.console) {
+ console.log('FreshRSS waiting for Flotr…');
+ }
+ window.setTimeout(initStats, 50);
+ return;
+ }
+ // Entry per hour
+ Flotr.draw(document.getElementById('statsEntryPerHour'),
+ [<?php echo $this->repartitionHour ?>],
+ {
+ grid: {verticalLines: false},
+ bars: {horizontal: false, show: true},
+ xaxis: {noTicks: 23,
+ tickFormatter: function(x) {
+ var x = parseInt(x);
+ return x + 1;
+ },
+ min: -0.9,
+ max: 23.9,
+ tickDecimals: 0},
+ yaxis: {min: 0},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+ });
+ // Entry per day of week
+ Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
+ [<?php echo $this->repartitionDayOfWeek ?>],
+ {
+ grid: {verticalLines: false},
+ bars: {horizontal: false, show: true},
+ xaxis: {noTicks: 6,
+ tickFormatter: function(x) {
+ var x = parseInt(x),
+ days = <?php echo $this->days?>;
+ return days[x];
+ },
+ min: -0.9,
+ max: 6.9,
+ tickDecimals: 0},
+ yaxis: {min: 0},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+ });
+ // Entry per month
+ Flotr.draw(document.getElementById('statsEntryPerMonth'),
+ [<?php echo $this->repartitionMonth ?>],
+ {
+ grid: {verticalLines: false},
+ bars: {horizontal: false, show: true},
+ xaxis: {noTicks: 12,
+ tickFormatter: function(x) {
+ var x = parseInt(x),
+ months = <?php echo $this->months?>;
+ return months[(x - 1)];
+ },
+ min: 0.1,
+ max: 12.9,
+ tickDecimals: 0},
+ yaxis: {min: 0},
+ mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
+ });
+
+}
+initStats();
+</script>