Merge branch 'dev' into beta

This commit is contained in:
Marien Fressinaud
2014-06-13 18:51:52 +02:00
151 changed files with 5858 additions and 4232 deletions

View File

@@ -1,5 +1,54 @@
# Journal des modifications
## 2014-06-13 FreshRSS 0.7.2
* API compatible with Google Reader API level 2
* FreshRSS can now be used from e.g.:
* (Android) News+ https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader
* (Android) EasyRSS https://github.com/Alkarex/EasyRSS
* Basic support for audio and video podcasts
* Searching
* New search filters date: and pubdate: accepting ISO 8601 date intervals such as `date:2013-2014` or `pubdate:P1W`
* Possibility to combine search filters, e.g. `date:2014-05 intitle:FreshRSS intitle:Open great reader #Internet`
* Change nav menu with more buttons instead of dropdown menus and add some filters
* New system of import / export
* Support OPML, Json (like Google Reader) and Zip archives
* Can export and import articles (specific option for favorites)
* Refactor "Origine" theme
* Some improvements
* Based on a template file (other themes will use it too)
## 2014-02-19 FreshRSS 0.7.1
* Mise à jour des flux plus rapide grâce à une meilleure utilisation du cache
* Utilisation dune signature MD5 du contenu intéressant pour les flux nimplémentant pas les requêtes conditionnelles
* Modification des raccourcis
* "s" partage directement si un seul moyen de partage
* Moyens de partage accessibles par "1", "2", "3", etc.
* Premier article : Home ; Dernier article : End
* Ajout du déplacement au sein des catégories / flux (via modificateurs shift et alt)
* UI
* Séparation des descriptions des raccourcis par groupes
* Revue rapide de la page de connexion
* Amélioration de l'affichage des notifications sur mobile
* Revue du système de rafraîchissement des flux
* Meilleure gestion de la file de flux à rafraîchir en JSON
* Rafraîchissement uniquement pour les flux non rafraîchis récemment
* Possibilité donnée aux anonymes de rafraîchir les flux
* SimplePie
* Mise à jour de la lib
* Corrige fuite de mémoire
* Meilleure tolérance aux flux invalides
* Corrections divers
* Ne déplie plus l'article lors du clic sur l'icône lien externe
* Ne boucle plus à la fin de la navigation dans les articles
* Suppression du champ category.color inutile
* Corrige bug redirection infinie (Persona)
* Amélioration vérification de la requête POST
* Ajout d'un verrou lorsqu'une action mark_read ou mark_favorite est en cours
## 2014-01-29 FreshRSS 0.7
* Nouveau mode multi-utilisateur

View File

@@ -8,12 +8,19 @@ Il permet de gérer plusieurs utilisateurs, et dispose dun mode de lecture an
* Site officiel : http://freshrss.org
* Démo : http://demo.freshrss.org/
* Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
* Version actuelle : 0.7
* Date de publication 2014-01-29
* Version actuelle : 0.8-dev
* Date de publication 2014-0x-xx
* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
# Note sur les branches
**Ce logiciel est encore en développement !** Veuillez vous assurer d'utiliser la branche qui vous correspond :
* Utilisez [la branche master](https://github.com/marienfressinaud/FreshRSS/tree/master/) si vous visez la stabilité.
* [La branche beta](https://github.com/marienfressinaud/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois.
* Pour les développeurs et ceux qui savent ce qu'ils font, [la branche dev](https://github.com/marienfressinaud/FreshRSS/tree/dev) vous ouvre les bras !
# Disclaimer
Cette application a été développée pour sadapter à des besoins personnels et non professionnels.
Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
@@ -25,9 +32,9 @@ Privilégiez pour cela des demandes sur GitHub
* Serveur modeste, par exemple sous Linux ou Windows
* Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées)
* Serveur Web Apache2 ou Nginx (non testé sur les autres)
* PHP 5.2+ (PHP 5.3.7+ recommandé)
* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv), [Zip](http://php.net/zip)
* MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir)
* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
* Fonctionne aussi sur mobile
@@ -42,7 +49,8 @@ Privilégiez pour cela des demandes sur GitHub
5. Tout devrait fonctionner :) En cas de problème, nhésitez pas à me contacter.
# Contrôle daccès
Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter laccès à votre FreshRSS :
Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter laccès à votre FreshRSS. Au choix :
* En utilisant lidentification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé fonctionne avec certaines versions de PHP 5.3.3+)
* En utilisant lidentification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
* En utilisant un contrôle daccès HTTP défini par votre serveur Web
* Voir par exemple la [documentation dApache sur lauthentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
@@ -80,6 +88,7 @@ mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/)
* [jQuery](http://jquery.com/)
* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
* [flotr2](http://www.humblesoftware.com/flotr2)
## Uniquement pour certaines options
* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js)

View File

@@ -29,7 +29,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$cat = new FreshRSS_Category ($name);
$values = array (
'name' => $cat->name (),
'color' => $cat->color ()
);
$catDAO->updateCategory ($ids[$key], $values);
} elseif ($ids[$key] != $defaultId) {
@@ -43,10 +42,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$values = array (
'id' => $cat->id (),
'name' => $cat->name (),
'color' => $cat->color ()
);
if ($catDAO->searchByName ($newCat) == false) {
if ($catDAO->searchByName ($newCat) == null) {
$catDAO->addCategory ($values);
}
}
@@ -64,7 +62,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->categories = $catDAO->listCategories (false);
$this->view->defaultCategory = $catDAO->getDefault ();
$this->view->feeds = $feedDAO->listFeeds ();
$this->view->flux = false;
Minz_View::prependTitle (Minz_Translate::t ('categories_management') . ' · ');
}
@@ -116,7 +113,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
if ($feedDAO->updateFeed ($id, $values)) {
$this->view->flux->_category ($cat);
$this->view->flux->faviconPrepare();
$notif = array (
'type' => 'good',
'content' => Minz_Translate::t ('feed_updated')
@@ -143,25 +140,12 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
public function displayAction () {
if (Minz_Request::isPost()) {
$this->view->conf->_language(Minz_Request::param('language', 'en'));
$this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
$this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
$this->view->conf->_default_view (Minz_Request::param('default_view', 'a'));
$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->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
$this->view->conf->_lazyload (Minz_Request::param('lazyload', false));
$this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
$this->view->conf->_mark_when (array(
'article' => Minz_Request::param('mark_open_article', false),
'site' => Minz_Request::param('mark_open_site', false),
'scroll' => Minz_Request::param('mark_scroll', false),
'reception' => Minz_Request::param('mark_upon_reception', false),
));
$themeId = Minz_Request::param('theme', '');
if ($themeId == '') {
$themeId = FreshRSS_Themes::defaultTheme;
}
$this->view->conf->_theme($themeId);
$this->view->conf->_content_width(Minz_Request::param('content_width', 'thin'));
$this->view->conf->_topline_read(Minz_Request::param('topline_read', false));
$this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false));
$this->view->conf->_topline_date(Minz_Request::param('topline_date', false));
@@ -189,21 +173,48 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->themes = FreshRSS_Themes::get();
Minz_View::prependTitle (Minz_Translate::t ('display_configuration') . ' · ');
}
public function readingAction () {
if (Minz_Request::isPost()) {
$this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
$this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
$this->view->conf->_default_view (Minz_Request::param('default_view', 'a'));
$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->_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));
$this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
$this->view->conf->_mark_when (array(
'article' => Minz_Request::param('mark_open_article', false),
'site' => Minz_Request::param('mark_open_site', false),
'scroll' => Minz_Request::param('mark_scroll', false),
'reception' => Minz_Request::param('mark_upon_reception', false),
));
$this->view->conf->save();
Minz_Session::_param ('language', $this->view->conf->language);
Minz_Translate::reset ();
invalidateHttpCache();
$notif = array (
'type' => 'good',
'content' => Minz_Translate::t ('configuration_updated')
);
Minz_Session::_param ('notification', $notif);
Minz_Request::forward (array ('c' => 'configure', 'a' => 'reading'), true);
}
Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' · ');
}
public function sharingAction () {
if (Minz_Request::isPost ()) {
$this->view->conf->_sharing (array(
'shaarli' => Minz_Request::param ('shaarli', false),
'wallabag' => Minz_Request::param ('wallabag', false),
'diaspora' => Minz_Request::param ('diaspora', false),
'twitter' => Minz_Request::param ('twitter', false),
'g+' => Minz_Request::param ('g+', false),
'facebook' => Minz_Request::param ('facebook', false),
'email' => Minz_Request::param ('email', false),
'print' => Minz_Request::param ('print', false),
));
$params = Minz_Request::params();
$this->view->conf->_sharing ($params['share']);
$this->view->conf->save();
invalidateHttpCache();
@@ -219,74 +230,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
Minz_View::prependTitle (Minz_Translate::t ('sharing') . ' · ');
}
public function importExportAction () {
require_once(LIB_PATH . '/lib_opml.php');
$catDAO = new FreshRSS_CategoryDAO ();
$this->view->categories = $catDAO->listCategories ();
$this->view->req = Minz_Request::param ('q');
if ($this->view->req == 'export') {
Minz_View::_title ('freshrss_feeds.opml');
$this->view->_useLayout (false);
header('Content-Type: application/xml; charset=utf-8');
header('Content-disposition: attachment; filename=freshrss_feeds.opml');
$feedDAO = new FreshRSS_FeedDAO ();
$catDAO = new FreshRSS_CategoryDAO ();
$list = array ();
foreach ($catDAO->listCategories () as $key => $cat) {
$list[$key]['name'] = $cat->name ();
$list[$key]['feeds'] = $feedDAO->listByCategory ($cat->id ());
}
$this->view->categories = $list;
} elseif ($this->view->req == 'import' && Minz_Request::isPost ()) {
if ($_FILES['file']['error'] == 0) {
invalidateHttpCache();
// on parse le fichier OPML pour récupérer les catégories et les flux associés
try {
list ($categories, $feeds) = opml_import (
file_get_contents ($_FILES['file']['tmp_name'])
);
// On redirige vers le controller feed qui va se charger d'insérer les flux en BDD
// les flux sont mis au préalable dans des variables de Request
Minz_Request::_param ('q', 'null');
Minz_Request::_param ('categories', $categories);
Minz_Request::_param ('feeds', $feeds);
Minz_Request::forward (array ('c' => 'feed', 'a' => 'massiveImport'));
} catch (FreshRSS_Opml_Exception $e) {
Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
$notif = array (
'type' => 'bad',
'content' => Minz_Translate::t ('bad_opml_file')
);
Minz_Session::_param ('notification', $notif);
Minz_Request::forward (array (
'c' => 'configure',
'a' => 'importExport'
), true);
}
}
}
$feedDAO = new FreshRSS_FeedDAO ();
$this->view->feeds = $feedDAO->listFeeds ();
// au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste
$this->view->flux = false;
Minz_View::prependTitle (Minz_Translate::t ('import_export_opml') . ' · ');
}
public function shortcutAction () {
$list_keys = array ('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter',
'escape', 'f', 'g', 'h', 'i', 'insert', 'j', 'k', 'l', 'left',
'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left',
'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right',
's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y',
'z', '0', '1', '2', '3', '4', '5', '6', '7', '8',

View File

@@ -100,6 +100,9 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
$entryDAO = new FreshRSS_EntryDAO();
$entryDAO->optimizeTable();
$feedDAO = new FreshRSS_FeedDAO();
$feedDAO->updateCachedValues();
invalidateHttpCache();
$notif = array (
@@ -137,11 +140,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(

View File

@@ -3,28 +3,51 @@
class FreshRSS_feed_Controller extends Minz_ActionController {
public function firstAction () {
if (!$this->view->loginOk) {
$token = $this->view->conf->token; //TODO: check the token logic again, and if it is still needed
// Token is useful in the case that anonymous refresh is forbidden
// and CRON task cannot be used with php command so the user can
// set a CRON task to refresh his feeds by using token inside url
$token = $this->view->conf->token;
$token_param = Minz_Request::param ('token', '');
$token_is_ok = ($token != '' && $token == $token_param);
$action = Minz_Request::actionName ();
if (!($token_is_ok && $action === 'actualize')) {
if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) &&
$action === 'actualize')
) {
Minz_Error::error (
403,
array ('error' => array (Minz_Translate::t ('access_denied')))
);
}
}
$this->catDAO = new FreshRSS_CategoryDAO ();
$this->catDAO->checkDefault ();
}
public function addAction () {
@set_time_limit(300);
$url = Minz_Request::param('url_rss', false);
if ($url === false) {
Minz_Request::forward(array(
'c' => 'configure',
'a' => 'feed'
), true);
}
$feedDAO = new FreshRSS_FeedDAO ();
$this->catDAO = new FreshRSS_CategoryDAO ();
$this->catDAO->checkDefault ();
if (Minz_Request::isPost()) {
@set_time_limit(300);
if (Minz_Request::isPost ()) {
$url = Minz_Request::param ('url_rss');
$cat = Minz_Request::param ('category', false);
if ($cat === 'nc') {
$new_cat = Minz_Request::param ('new_category');
if (empty($new_cat['name'])) {
$cat = false;
} else {
$cat = $this->catDAO->addCategory($new_cat);
}
}
if ($cat === false) {
$def_cat = $this->catDAO->getDefault ();
$cat = $def_cat->id ();
@@ -47,7 +70,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$feed->load(true);
$feedDAO = new FreshRSS_FeedDAO ();
$values = array (
'url' => $feed->url (),
'category' => $feed->category (),
@@ -123,7 +145,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
$notif = array (
'type' => 'bad',
'content' => Minz_Translate::t ('internal_problem_feed')
'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
);
Minz_Session::_param ('notification', $notif);
} catch (Minz_FileNotExistException $e) {
@@ -131,7 +153,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
$notif = array (
'type' => 'bad',
'content' => Minz_Translate::t ('internal_problem_feed')
'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
);
Minz_Session::_param ('notification', $notif);
}
@@ -141,6 +163,38 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
}
// GET request so we must ask confirmation to user
Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · ');
$this->view->categories = $this->catDAO->listCategories();
$this->view->feed = new FreshRSS_Feed($url);
try {
// We try to get some more information about the feed
$this->view->feed->load(true);
$this->view->load_ok = true;
} catch (Exception $e) {
$this->view->load_ok = false;
}
$feed = $feedDAO->searchByUrl($this->view->feed->url());
if ($feed) {
// Already subscribe so we redirect to the feed configuration page
$notif = array(
'type' => 'bad',
'content' => Minz_Translate::t(
'already_subscribed', $feed->name()
)
);
Minz_Session::_param('notification', $notif);
Minz_Request::forward(array(
'c' => 'configure',
'a' => 'feed',
'params' => array(
'id' => $feed->id()
)
), true);
}
}
public function truncateAction () {
@@ -189,38 +243,51 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$flux_update = 0;
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
foreach ($feeds as $feed) {
if (!$feed->lock()) {
Minz_Log::record('Feed already being actualized: ' . $feed->url(), Minz_Log::NOTICE);
continue;
}
try {
$url = $feed->url();
$feedHistory = $feed->keepHistory();
$feed->load(false);
$entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
$hasTransaction = false;
//For this feed, check last n entry GUIDs already in database
$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
$useDeclaredDate = empty($existingGuids);
if (count($entries) > 0) {
//For this feed, check last n entry GUIDs already in database
$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
$useDeclaredDate = empty($existingGuids);
$feedHistory = $feed->keepHistory();
if ($feedHistory == -2) { //default
$feedHistory = $this->view->conf->keep_history_default;
}
if ($feedHistory == -2) { //default
$feedHistory = $this->view->conf->keep_history_default;
}
// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
// La BDD refusera l'ajout car (id_feed, guid) doit être unique
$feedDAO->beginTransaction ();
foreach ($entries as $entry) {
$eDate = $entry->date (true);
if ((!isset ($existingGuids[$entry->guid ()])) &&
(($feedHistory != 0) || ($eDate >= $date_min))) {
$values = $entry->toArray ();
//Use declared date at first import, otherwise use discovery date
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
min(time(), $eDate) . uSecString() :
uTimeString();
$values['is_read'] = $is_read;
$entryDAO->addEntry ($values);
$hasTransaction = true;
$feedDAO->beginTransaction();
// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
// La BDD refusera l'ajout car (id_feed, guid) doit être unique
foreach ($entries as $entry) {
$eDate = $entry->date (true);
if ((!isset ($existingGuids[$entry->guid ()])) &&
(($feedHistory != 0) || ($eDate >= $date_min))) {
$values = $entry->toArray ();
//Use declared date at first import, otherwise use discovery date
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
min(time(), $eDate) . uSecString() :
uTimeString();
$values['is_read'] = $is_read;
$entryDAO->addEntry ($values);
}
}
}
if (($feedHistory >= 0) && (rand(0, 30) === 1)) {
if (!$hasTransaction) {
$feedDAO->beginTransaction();
}
$nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10));
if ($nb > 0) {
Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG);
@@ -228,18 +295,23 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
// on indique que le flux vient d'être mis à jour en BDD
$feedDAO->updateLastUpdate ($feed->id ());
$feedDAO->commit ();
$feedDAO->updateLastUpdate ($feed->id (), 0, $hasTransaction);
if ($hasTransaction) {
$feedDAO->commit();
}
$flux_update++;
if ($feed->url() !== $url) { //URL has changed (auto-discovery)
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
}
$feed->faviconPrepare();
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
$feedDAO->updateLastUpdate ($feed->id (), 1);
}
$feed->faviconPrepare();
$feed->unlock();
unset($feed);
// On arrête à 10 flux pour ne pas surcharger le serveur
// sauf si le paramètre $force est à vrai
$i++;
@@ -251,6 +323,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$url = array ();
if ($flux_update === 1) {
// on a mis un seul flux à jour
$feed = reset ($feeds);
$notif = array (
'type' => 'good',
'content' => Minz_Translate::t ('feed_actualized', $feed->name ())
@@ -264,8 +337,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
} else {
// aucun flux n'a été mis à jour, oups
$notif = array (
'type' => 'bad',
'content' => Minz_Translate::t ('no_feed_actualized')
'type' => 'good',
'content' => Minz_Translate::t ('no_feed_to_refresh')
);
}
@@ -295,77 +368,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
public function massiveImportAction () {
@set_time_limit(300);
$entryDAO = new FreshRSS_EntryDAO ();
$feedDAO = new FreshRSS_FeedDAO ();
$categories = Minz_Request::param ('categories', array (), true);
$feeds = Minz_Request::param ('feeds', array (), true);
// on ajoute les catégories en masse dans une fonction à part
$this->addCategories ($categories);
// on calcule la date des articles les plus anciens qu'on accepte
$nb_month_old = $this->view->conf->old_entries;
$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
// la variable $error permet de savoir si une erreur est survenue
// Le but est de ne pas arrêter l'import même en cas d'erreur
// L'utilisateur sera mis au courant s'il y a eu des erreurs, mais
// ne connaîtra pas les détails. Ceux-ci seront toutefois logguées
$error = false;
$i = 0;
foreach ($feeds as $feed) {
try {
$values = array (
'id' => $feed->id (),
'url' => $feed->url (),
'category' => $feed->category (),
'name' => $feed->name (),
'website' => $feed->website (),
'description' => $feed->description (),
'lastUpdate' => 0,
'httpAuth' => $feed->httpAuth ()
);
// ajout du flux que s'il n'est pas déjà en BDD
if (!$feedDAO->searchByUrl ($values['url'])) {
$id = $feedDAO->addFeed ($values);
if ($id) {
$feed->_id ($id);
$feed->faviconPrepare();
} else {
$error = true;
}
}
} catch (FreshRSS_Feed_Exception $e) {
$error = true;
Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
}
}
if ($error) {
$res = Minz_Translate::t ('feeds_imported_with_errors');
} else {
$res = Minz_Translate::t ('feeds_imported');
}
$notif = array (
'type' => 'good',
'content' => $res
);
Minz_Session::_param ('notification', $notif);
Minz_Session::_param ('actualize_feeds', true);
// et on redirige vers la page d'accueil
Minz_Request::forward (array (
'c' => 'index',
'a' => 'index'
), true);
}
public function deleteAction () {
if (Minz_Request::isPost ()) {
$type = Minz_Request::param ('type', 'feed');
@@ -409,17 +411,4 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
}
}
private function addCategories ($categories) {
foreach ($categories as $cat) {
if (!$this->catDAO->searchByName ($cat->name ())) {
$values = array (
'id' => $cat->id (),
'name' => $cat->name (),
'color' => $cat->color ()
);
$catDAO->addCategory ($values);
}
}
}
}

View File

@@ -0,0 +1,390 @@
<?php
class FreshRSS_importExport_Controller extends Minz_ActionController {
public function firstAction() {
if (!$this->view->loginOk) {
Minz_Error::error(
403,
array('error' => array(Minz_Translate::t('access_denied')))
);
}
require_once(LIB_PATH . '/lib_opml.php');
$this->catDAO = new FreshRSS_CategoryDAO();
$this->entryDAO = new FreshRSS_EntryDAO();
$this->feedDAO = new FreshRSS_FeedDAO();
}
public function indexAction() {
$this->view->categories = $this->catDAO->listCategories();
$this->view->feeds = $this->feedDAO->listFeeds();
Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · ');
}
public function importAction() {
if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) {
@set_time_limit(300);
$file = $_FILES['file'];
$type_file = $this->guessFileType($file['name']);
$list_files = array(
'opml' => array(),
'json_starred' => array(),
'json_feed' => array()
);
// 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']);
while (($zipfile = zip_read($zip)) !== false) {
$type_zipfile = $this->guessFileType(
zip_entry_name($zipfile)
);
if ($type_file !== 'unknown') {
$list_files[$type_zipfile][] = zip_entry_read(
$zipfile,
zip_entry_filesize($zipfile)
);
}
}
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);
Minz_Request::forward(array(
'c' => 'index',
'a' => 'index'
), true);
}
// What are you doing? you have to call this controller
// with a POST request!
Minz_Request::forward(array(
'c' => 'importExport',
'a' => 'index'
));
}
private function guessFileType($filename) {
// A *very* basic guess file type function. Only based on filename
// That's could be improved but should be enough, at least for a first
// implementation.
// TODO: improve this function?
if (substr_compare($filename, '.zip', -4) === 0) {
return 'zip';
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
substr_compare($filename, '.xml', -4) === 0) {
return 'opml';
} elseif (strcmp($filename, 'starred.json') === 0) {
return 'json_starred';
} elseif (substr_compare($filename, '.json', -5) === 0 &&
strpos($filename, 'feed_') === 0) {
return 'json_feed';
} else {
return 'unknown';
}
}
private function importOpml($opml_file) {
$opml_array = array();
try {
$opml_array = libopml_parse_string($opml_file);
} catch (LibOPML_Exception $e) {
Minz_Log::warning($e->getMessage());
return true;
}
$this->catDAO->checkDefault();
return $this->addOpmlElements($opml_array['body']);
}
private function addOpmlElements($opml_elements, $parent_cat = null) {
$error = false;
foreach ($opml_elements as $elt) {
$res = false;
if (isset($elt['xmlUrl'])) {
$res = $this->addFeedOpml($elt, $parent_cat);
} else {
$res = $this->addCategoryOpml($elt, $parent_cat);
}
if (!$error && $res) {
// oops: there is at least one error!
$error = $res;
}
}
return $error;
}
private function addFeedOpml($feed_elt, $parent_cat) {
if (is_null($parent_cat)) {
// This feed has no parent category so we get the default one
$parent_cat = $this->catDAO->getDefault()->name();
}
$cat = $this->catDAO->searchByName($parent_cat);
if (!$cat) {
return true;
}
// We get different useful information
$url = html_chars_utf8($feed_elt['xmlUrl']);
$name = html_chars_utf8($feed_elt['text']);
$website = '';
if (isset($feed_elt['htmlUrl'])) {
$website = html_chars_utf8($feed_elt['htmlUrl']);
}
$description = '';
if (isset($feed_elt['description'])) {
$description = html_chars_utf8($feed_elt['description']);
}
$error = false;
try {
// Create a Feed object and add it in DB
$feed = new FreshRSS_Feed($url);
$feed->_category($cat->id());
$feed->_name($name);
$feed->_website($website);
$feed->_description($description);
// addFeedObject checks if feed is already in DB so nothing else to
// check here
$id = $this->feedDAO->addFeedObject($feed);
$error = ($id === false);
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::warning($e->getMessage());
$error = true;
}
return $error;
}
private function addCategoryOpml($cat_elt, $parent_cat) {
// Create a new Category object
$cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text']));
$id = $this->catDAO->addCategoryObject($cat);
$error = ($id === false);
if (isset($cat_elt['@outlines'])) {
// Our cat_elt contains more categories or more feeds, so we
// add them recursively.
// Note: FreshRSS does not support yet category arborescence
$res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name());
if (!$error && $res) {
$error = true;
}
}
return $error;
}
private function importArticles($article_file, $starred = false) {
$article_object = json_decode($article_file, true);
if (is_null($article_object)) {
Minz_Log::warning('Try to import a non-JSON file');
return true;
}
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
$google_compliant = (
strpos($article_object['id'], 'com.google') !== false
);
$error = false;
foreach ($article_object['items'] as $item) {
$feed = $this->addFeedArticles($item['origin'], $google_compliant);
if (is_null($feed)) {
$error = true;
continue;
}
$author = isset($item['author']) ? $item['author'] : '';
$key_content = ($google_compliant && !isset($item['content'])) ?
'summary' : 'content';
$tags = $item['categories'];
if ($google_compliant) {
$tags = array_filter($tags, function($var) {
return strpos($var, '/state/com.google') === false;
});
}
$entry = new FreshRSS_Entry(
$feed->id(), $item['id'], $item['title'], $author,
$item[$key_content]['content'], $item['alternate'][0]['href'],
$item['published'], $is_read, $starred
);
$entry->_tags($tags);
$id = $this->entryDAO->addEntryObject(
$entry, $this->view->conf, $feed->keepHistory()
);
if (!$error && ($id === false)) {
$error = true;
}
}
return $error;
}
private function addFeedArticles($origin, $google_compliant) {
$default_cat = $this->catDAO->getDefault();
$return = null;
$key = $google_compliant ? 'htmlUrl' : 'feedUrl';
$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);
$feed->_category($default_cat->id());
$feed->_name($name);
$feed->_website($website);
// addFeedObject checks if feed is already in DB so nothing else to
// check here
$id = $this->feedDAO->addFeedObject($feed);
if ($id !== false) {
$feed->_id($id);
$return = $feed;
}
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::warning($e->getMessage());
}
return $return;
}
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)
);
}
// 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);
}
}
private function generateOpml() {
$list = array();
foreach ($this->catDAO->listCategories() as $key => $cat) {
$list[$key]['name'] = $cat->name();
$list[$key]['feeds'] = $this->feedDAO->listByCategory($cat->id());
}
$this->view->categories = $list;
return $this->view->helperToString('export/opml');
}
private function generateArticles($type, $feed = NULL) {
$this->view->categories = $this->catDAO->listCategories();
if ($type == 'starred') {
$this->view->list_title = Minz_Translate::t('starred_list');
$this->view->type = 'starred';
$unread_fav = $this->entryDAO->countUnreadReadFavorites();
$this->view->entries = $this->entryDAO->listWhere(
's', '', FreshRSS_Entry::STATE_ALL, 'ASC',
$unread_fav['all']
);
} elseif ($type == 'feed' && !is_null($feed)) {
$this->view->list_title = Minz_Translate::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',
$this->view->conf->posts_per_page
);
$this->view->feed = $feed;
}
return $this->view->helperToString('export/articles');
}
}

View File

@@ -5,27 +5,32 @@ class FreshRSS_index_Controller extends Minz_ActionController {
public function indexAction () {
$output = Minz_Request::param ('output');
$token = '';
$token = $this->view->conf->token;
// check if user is logged in
if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous())
{
$token = $this->view->conf->token;
if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous()) {
$token_param = Minz_Request::param ('token', '');
$token_is_ok = ($token != '' && $token === $token_param);
if (!($output === 'rss' && $token_is_ok)) {
if ($output === 'rss' && !$token_is_ok) {
Minz_Error::error (
403,
array ('error' => array (Minz_Translate::t ('access_denied')))
);
return;
} elseif ($output !== 'rss') {
// "hard" redirection is not required, just ask dispatcher to
// forward to the login form without 302 redirection
Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'));
return;
}
$params['token'] = $token;
}
// construction of RSS url of this feed
$params = Minz_Request::params ();
$params['output'] = 'rss';
if (isset ($params['search'])) {
$params['search'] = urlencode ($params['search']);
}
$this->view->rss_url = array (
$this->view->url = array (
'c' => 'index',
'a' => 'index',
'params' => $params
@@ -75,20 +80,22 @@ 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);
$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_Entry::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);
$first = Minz_Request::param ('next', '');
if ($state === 'not_read') { //Any unread article in this category at all?
if ($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;
break;
case 's':
// This is deprecated. The favorite button does not exist anymore
$hasUnread = $this->view->nb_favorites['unread'] > 0;
break;
case 'c':
@@ -102,8 +109,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$hasUnread = true;
break;
}
if (!$hasUnread) {
$this->view->state = $state = 'all';
if (!$hasUnread && ($state_param === null)) {
$this->view->state = $state = FreshRSS_Entry::STATE_ALL;
}
}
@@ -116,14 +123,14 @@ 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
if ($state === 'not_read' && empty($entries)) {
if ($state === FreshRSS_Entry::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, $keepHistoryDefault);
$this->view->state = FreshRSS_Entry::STATE_ALL;
$entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault);
}
if (count($entries) <= $nb) {
@@ -342,6 +349,37 @@ class FreshRSS_index_Controller extends Minz_ActionController {
}
$this->view->_useLayout(false);
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
} elseif (Minz_Configuration::unsafeAutologinEnabled() && isset($_GET['u']) && isset($_GET['p'])) {
Minz_Session::_param('currentUser');
Minz_Session::_param('mail');
Minz_Session::_param('passwordHash');
$username = ctype_alnum($_GET['u']) ? $_GET['u'] : '';
$passwordPlain = $_GET['p'];
Minz_Request::_param('p'); //Discard plain-text password ASAP
$_GET['p'] = '';
if (!function_exists('password_verify')) {
include_once(LIB_PATH . '/password_compat.php');
}
try {
$conf = new FreshRSS_Configuration($username);
$s = $conf->passwordHash;
$ok = password_verify($passwordPlain, $s);
unset($passwordPlain);
if ($ok) {
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $s);
} else {
Minz_Log::record('Unsafe password mismatch for user ' . $username, Minz_Log::WARNING);
}
} catch (Minz_Exception $me) {
Minz_Log::record('Unsafe login failure: ' . $me->getMessage(), Minz_Log::WARNING);
}
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
} elseif (!Minz_Configuration::canLogIn()) {
Minz_Error::error (
403,
array ('error' => array (Minz_Translate::t ('access_denied')))
);
}
invalidateHttpCache();
}

View File

@@ -8,7 +8,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
public function actualizeAction () {
header('Content-Type: text/javascript; charset=UTF-8');
$feedDAO = new FreshRSS_FeedDAO ();
$this->view->feeds = $feedDAO->listFeeds ();
$this->view->feeds = $feedDAO->listFeedsOrderUpdate();
}
public function nbUnreadsPerFeedAction() {

View File

@@ -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));
}
@@ -54,11 +66,22 @@ class FreshRSS_users_Controller extends Minz_ActionController {
$anon = Minz_Request::param('anon_access', false);
$anon = ((bool)$anon) && ($anon !== 'no');
$anon_refresh = Minz_Request::param('anon_refresh', false);
$anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no');
$auth_type = Minz_Request::param('auth_type', 'none');
$unsafe_autologin = Minz_Request::param('unsafe_autologin', false);
$api_enabled = Minz_Request::param('api_enabled', false);
if ($anon != Minz_Configuration::allowAnonymous() ||
$auth_type != Minz_Configuration::authType()) {
$auth_type != Minz_Configuration::authType() ||
$anon_refresh != Minz_Configuration::allowAnonymousRefresh() ||
$unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() ||
$api_enabled != Minz_Configuration::apiEnabled()) {
Minz_Configuration::_authType($auth_type);
Minz_Configuration::_allowAnonymous($anon);
Minz_Configuration::_allowAnonymousRefresh($anon_refresh);
Minz_Configuration::_enableAutologin($unsafe_autologin);
Minz_Configuration::_enableApi($api_enabled);
$ok &= Minz_Configuration::writeFile();
}
}

View File

@@ -1,6 +0,0 @@
<?php
class FreshRSS_Opml_Exception extends FreshRSS_Feed_Exception {
public function __construct ($name_file) {
parent::__construct ('OPML file is invalid');
}
}

View File

@@ -94,10 +94,6 @@ class FreshRSS extends Minz_FrontController {
$loginOk = false;
break;
}
if ((!$loginOk) && (PHP_SAPI === 'cli') && (Minz_Request::actionName() === 'actualize')) { //Command line
Minz_Configuration::_authType('none');
$loginOk = true;
}
}
Minz_View::_param ('loginOk', $loginOk);
return $loginOk;

View File

@@ -3,14 +3,12 @@
class FreshRSS_Category extends Minz_Model {
private $id = 0;
private $name;
private $color;
private $nbFeed = -1;
private $nbNotRead = -1;
private $feeds = null;
public function __construct ($name = '', $color = '#0062BE', $feeds = null) {
public function __construct ($name = '', $feeds = null) {
$this->_name ($name);
$this->_color ($color);
if (isset ($feeds)) {
$this->_feeds ($feeds);
$this->nbFeed = 0;
@@ -28,9 +26,6 @@ class FreshRSS_Category extends Minz_Model {
public function name () {
return $this->name;
}
public function color () {
return $this->color;
}
public function nbFeed () {
if ($this->nbFeed < 0) {
$catDAO = new FreshRSS_CategoryDAO ();
@@ -68,13 +63,6 @@ class FreshRSS_Category extends Minz_Model {
public function _name ($value) {
$this->name = $value;
}
public function _color ($value) {
if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
$this->color = $value;
} else {
$this->color = '#0062BE';
}
}
public function _feeds ($values) {
if (!is_array ($values)) {
$values = array ($values);

View File

@@ -2,12 +2,11 @@
class FreshRSS_CategoryDAO extends Minz_ModelPdo {
public function addCategory ($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'category` (name, color) VALUES(?, ?)';
$sql = 'INSERT INTO `' . $this->prefix . 'category` (name) VALUES(?)';
$stm = $this->bd->prepare ($sql);
$values = array (
substr($valuesTmp['name'], 0, 255),
substr($valuesTmp['color'], 0, 7),
);
if ($stm && $stm->execute ($values)) {
@@ -19,13 +18,25 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
}
}
public function addCategoryObject($category) {
$cat = $this->searchByName($category->name());
if (!$cat) {
// Category does not exist yet in DB so we add it before continue
$values = array(
'name' => $category->name(),
);
return $this->addCategory($values);
}
return $cat->id();
}
public function updateCategory ($id, $valuesTmp) {
$sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?';
$sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?';
$stm = $this->bd->prepare ($sql);
$values = array (
$valuesTmp['name'],
$valuesTmp['color'],
$id
);
@@ -66,7 +77,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if (isset ($cat[0])) {
return $cat[0];
} else {
return false;
return null;
}
}
public function searchByName ($name) {
@@ -82,14 +93,13 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if (isset ($cat[0])) {
return $cat[0];
} else {
return false;
return null;
}
}
public function listCategories ($prePopulateFeeds = true, $details = false) {
if ($prePopulateFeeds) {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
. ($details ? 'c.color AS c_color, ' : '')
. ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
. 'FROM `' . $this->prefix . 'category` c '
. 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id '
@@ -123,14 +133,13 @@ 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);
$values = array (
'id' => $cat->id (),
'name' => $cat->name (),
'color' => $cat->color ()
);
$this->addCategory ($values);
@@ -203,7 +212,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
// End of the current category, we add it to the $list
$cat = new FreshRSS_Category (
$previousLine['c_name'],
isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
);
$cat->_id ($previousLine['c_id']);
@@ -220,7 +228,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
if ($previousLine != null) {
$cat = new FreshRSS_Category (
$previousLine['c_name'],
isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
);
$cat->_id ($previousLine['c_id']);
@@ -239,8 +246,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
foreach ($listDAO as $key => $dao) {
$cat = new FreshRSS_Category (
$dao['name'],
$dao['color']
$dao['name']
);
$cat->_id ($dao['id']);
$list[$key] = $cat;

View File

@@ -10,13 +10,15 @@ class FreshRSS_Configuration {
'mail_login' => '',
'token' => '',
'passwordHash' => '', //CRYPT_BLOWFISH
'apiPasswordHash' => '', //CRYPT_BLOWFISH
'posts_per_page' => 20,
'view_mode' => 'normal',
'default_view' => 'not_read',
'default_view' => FreshRSS_Entry::STATE_NOT_READ,
'auto_load_more' => true,
'display_posts' => false,
'onread_jump_next' => true,
'lazyload' => true,
'sticky_post' => true,
'sort_order' => 'DESC',
'anon_access' => false,
'mark_when' => array(
@@ -26,15 +28,19 @@ class FreshRSS_Configuration {
'reception' => false,
),
'theme' => 'Origine',
'content_width' => 'thin',
'shortcuts' => array(
'mark_read' => 'r',
'mark_favorite' => 'f',
'go_website' => 'space',
'next_entry' => 'j',
'prev_entry' => 'k',
'first_entry' => 'home',
'last_entry' => 'end',
'collapse_entry' => 'c',
'load_more' => 'm',
'auto_share' => 's',
'focus_search' => 'a',
),
'topline_read' => true,
'topline_favorite' => true,
@@ -46,16 +52,7 @@ class FreshRSS_Configuration {
'bottomline_tags' => true,
'bottomline_date' => true,
'bottomline_link' => true,
'sharing' => array(
'shaarli' => '',
'wallabag' => '',
'diaspora' => '',
'twitter' => true,
'g+' => true,
'facebook' => true,
'email' => true,
'print' => true,
),
'sharing' => array(),
);
private $available_languages = array(
@@ -63,8 +60,10 @@ class FreshRSS_Configuration {
'fr' => 'Français',
);
public function __construct ($user) {
$this->filename = DATA_PATH . '/' . $user . '_user.php';
private $shares;
public function __construct($user) {
$this->filename = DATA_PATH . DIRECTORY_SEPARATOR . $user . '_user.php';
$data = @include($this->filename);
if (!is_array($data)) {
@@ -78,10 +77,20 @@ class FreshRSS_Configuration {
}
}
$this->data['user'] = $user;
$this->shares = DATA_PATH . DIRECTORY_SEPARATOR . 'shares.php';
$shares = @include($this->shares);
if (!is_array($shares)) {
throw new Minz_PermissionDeniedException($this->shares);
}
$this->data['shares'] = $shares;
}
public function save() {
@rename($this->filename, $this->filename . '.bak.php');
unset($this->data['shares']); // Remove shares because it is not intended to be stored in user configuration
if (file_put_contents($this->filename, "<?php\n return " . var_export($this->data, true) . ';', LOCK_EX) === false) {
throw new Minz_PermissionDeniedException($this->filename);
}
@@ -102,16 +111,6 @@ class FreshRSS_Configuration {
}
}
public function sharing($key = false) {
if ($key === false) {
return $this->data['sharing'];
}
if (isset($this->data['sharing'][$key])) {
return $this->data['sharing'][$key];
}
return false;
}
public function availableLanguages() {
return $this->available_languages;
}
@@ -134,7 +133,7 @@ class FreshRSS_Configuration {
}
}
public function _default_view ($value) {
$this->data['default_view'] = $value === 'all' ? 'all' : 'not_read';
$this->data['default_view'] = $value === FreshRSS_Entry::STATE_ALL ? FreshRSS_Entry::STATE_ALL : FreshRSS_Entry::STATE_NOT_READ;
}
public function _display_posts ($value) {
$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
@@ -145,6 +144,9 @@ class FreshRSS_Configuration {
public function _lazyload ($value) {
$this->data['lazyload'] = ((bool)$value) && $value !== 'no';
}
public function _sticky_post($value) {
$this->data['sticky_post'] = ((bool)$value) && $value !== 'no';
}
public function _sort_order ($value) {
$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
}
@@ -166,6 +168,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) {
@@ -185,29 +190,47 @@ class FreshRSS_Configuration {
}
}
public function _sharing ($values) {
$are_url = array ('shaarli', 'wallabag', 'diaspora');
foreach ($values as $key => $value) {
if (in_array($key, $are_url)) {
$this->data['sharing'] = array();
foreach ($values as $value) {
if (!is_array($value)) {
continue;
}
// Verify URL and add default value when needed
if (isset($value['url'])) {
$is_url = (
filter_var ($value, FILTER_VALIDATE_URL) ||
filter_var ($value['url'], FILTER_VALIDATE_URL) ||
(version_compare(PHP_VERSION, '5.3.3', '<') &&
(strpos($value, '-') > 0) &&
($value === filter_var($value, FILTER_SANITIZE_URL)))
); //PHP bug #51192
if (!$is_url) {
$value = '';
continue;
}
} elseif (!is_bool($value)) {
$value = true;
} else {
$value['url'] = null;
}
$this->data['sharing'][$key] = $value;
// Add a default name
if (empty($value['name'])) {
$value['name'] = $value['type'];
}
$this->data['sharing'][] = $value;
}
}
public function _theme($value) {
$this->data['theme'] = $value;
}
public function _content_width($value) {
if ($value === 'medium' ||
$value === 'large' ||
$value === 'no_limit') {
$this->data['content_width'] = $value;
} else {
$this->data['content_width'] = 'thin';
}
}
public function _token($value) {
$this->data['token'] = $value;
}

View File

@@ -1,6 +1,11 @@
<?php
class FreshRSS_Entry extends Minz_Model {
const STATE_ALL = 0;
const STATE_READ = 1;
const STATE_NOT_READ = 2;
const STATE_FAVORITE = 4;
const STATE_NOT_FAVORITE = 8;
private $id = 0;
private $guid;

View File

@@ -35,11 +35,45 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
}
public function markFavorite ($id, $is_favorite = true) {
public function addEntryObject($entry, $conf, $feedHistory) {
$existingGuids = array_fill_keys(
$this->listLastGuidsByFeed($entry->feed(), 20), 1
);
$nb_month_old = max($conf->old_entries, 1);
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
$eDate = $entry->date(true);
if ($feedHistory == -2) {
$feedHistory = $conf->keep_history_default;
}
if (!isset($existingGuids[$entry->guid()]) &&
($feedHistory != 0 || $eDate >= $date_min)) {
$values = $entry->toArray();
$useDeclaredDate = empty($existingGuids);
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
min(time(), $eDate) . uSecString() :
uTimeString();
return $this->addEntry($values);
}
// We don't return Entry object to avoid a research in DB
return -1;
}
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,30 +83,79 @@ 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 = ?,'
. '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;
}
}
}
public function markReadEntries ($idMax = 0, $favorites = false) {
if ($idMax === 0) {
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 '
. 'SET e.is_read = 1, f.cache_nbUnreads=0 '
. 'WHERE e.is_read = 0 AND ';
if ($favorites) {
$sql .= 'e.is_favorite = 1';
} else {
$sql .= 'f.priority > 0';
. 'WHERE e.is_read = 0';
if ($onlyFavorites) {
$sql .= ' AND e.is_favorite = 1';
} elseif ($priorityMin >= 0) {
$sql .= ' AND f.priority > ' . intval($priorityMin);
}
$stm = $this->bd->prepare ($sql);
if ($stm && $stm->execute ()) {
@@ -87,11 +170,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
. 'SET e.is_read = 1 '
. 'WHERE e.is_read = 0 AND e.id <= ? AND ';
if ($favorites) {
$sql .= 'e.is_favorite = 1';
} else {
$sql .= 'f.priority > 0';
. 'WHERE e.is_read = 0 AND e.id <= ?';
if ($onlyFavorites) {
$sql .= ' AND e.is_favorite = 1';
} elseif ($priorityMin >= 0) {
$sql .= ' AND f.priority > ' . intval($priorityMin);
}
$values = array ($idMax);
$stm = $this->bd->prepare ($sql);
@@ -126,8 +209,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
}
public function markReadCat ($id, $idMax = 0) {
if ($idMax === 0) {
if ($idMax == 0) {
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
. 'SET e.is_read = 1, f.cache_nbUnreads=0 '
. 'WHERE f.category = ? AND e.is_read = 0';
@@ -181,8 +265,70 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return $affected;
}
}
public function markReadCatName($name, $idMax = 0) {
if ($idMax == 0) {
$sql = 'UPDATE `' . $this->prefix . 'entry` e '
. 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
. 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
. 'SET e.is_read = 1, f.cache_nbUnreads=0 '
. 'WHERE c.name = ?';
$values = array($name);
$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;
}
} else {
$this->bd->beginTransaction();
$sql = 'UPDATE `' . $this->prefix . 'entry` e '
. 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
. 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
. 'SET e.is_read = 1 '
. 'WHERE c.name = ? AND e.id <= ?';
$values = array($name, $idMax);
$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 '
. 'LEFT OUTER JOIN ('
. 'SELECT e.id_feed, '
. 'COUNT(*) AS nbUnreads '
. 'FROM `' . $this->prefix . 'entry` e '
. 'WHERE e.is_read = 0 '
. 'GROUP BY e.id_feed'
. ') x ON x.id_feed=f.id '
. 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category '
. 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
. 'WHERE c.name = ?';
$values = array($name);
$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;
}
}
$this->bd->commit();
return $affected;
}
}
public function markReadFeed ($id, $idMax = 0) {
if ($idMax === 0) {
if ($idMax == 0) {
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
. 'SET e.is_read = 1, f.cache_nbUnreads=0 '
. 'WHERE f.id=? AND e.is_read = 0';
@@ -244,7 +390,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) {
@@ -257,10 +403,13 @@ 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) {
private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) {
if (!$state) {
$state = FreshRSS_Entry::STATE_ALL;
}
$where = '';
$joinFeed = false;
$values = array();
@@ -269,7 +418,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'f.priority > 0 ';
$joinFeed = true;
break;
case 's':
case 's': //Deprecated: use $state instead
$where .= 'e1.is_favorite = 1 ';
break;
case 'c':
@@ -281,24 +430,30 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
$where .= 'e1.id_feed = ? ';
$values[] = intval($id);
break;
case 'A':
$where .= '1 ';
break;
default:
throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!');
}
switch ($state) {
case 'all':
break;
case 'not_read':
if ($state & FreshRSS_Entry::STATE_NOT_READ) {
if (!($state & FreshRSS_Entry::STATE_READ)) {
$where .= 'AND e1.is_read = 0 ';
break;
case 'read':
$where .= 'AND e1.is_read = 1 ';
break;
case 'favorite':
$where .= 'AND e1.is_favorite = 1 ';
break;
default:
throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!');
}
}
elseif ($state & FreshRSS_Entry::STATE_READ) {
$where .= 'AND e1.is_read = 1 ';
}
if ($state & FreshRSS_Entry::STATE_FAVORITE) {
if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) {
$where .= 'AND e1.is_favorite = 1 ';
}
}
elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
$where .= 'AND e1.is_favorite = 0 ';
}
switch ($order) {
case 'DESC':
case 'ASC':
@@ -310,70 +465,84 @@ 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 = '';
if ($filter !== '') {
require_once(LIB_PATH . '/lib_date.php');
$filter = trim($filter);
$filter = addcslashes($filter, '\\%_');
if (stripos($filter, 'intitle:') === 0) {
$filter = substr($filter, strlen('intitle:'));
$intitle = true;
} else {
$intitle = false;
}
if (stripos($filter, 'inurl:') === 0) {
$filter = substr($filter, strlen('inurl:'));
$inurl = true;
} else {
$inurl = false;
}
if (stripos($filter, 'author:') === 0) {
$filter = substr($filter, strlen('author:'));
$author = true;
} else {
$author = false;
}
$terms = array_unique(explode(' ', $filter));
sort($terms); //Put #tags first
//sort($terms); //Put #tags first //TODO: Put the cheapest filters first
foreach ($terms as $word) {
$word = trim($word);
if (strlen($word) > 0) {
if ($intitle) {
$search .= 'AND e1.title LIKE ? ';
$values[] = '%' . $word .'%';
} elseif ($inurl) {
$search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
$values[] = '%' . $word .'%';
} elseif ($author) {
$search .= 'AND e1.author LIKE ? ';
if (stripos($word, 'intitle:') === 0) {
$word = substr($word, strlen('intitle:'));
$search .= 'AND e1.title LIKE ? ';
$values[] = '%' . $word .'%';
} elseif (stripos($word, 'inurl:') === 0) {
$word = substr($word, strlen('inurl:'));
$search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
$values[] = '%' . $word .'%';
} elseif (stripos($word, 'author:') === 0) {
$word = substr($word, strlen('author:'));
$search .= 'AND e1.author LIKE ? ';
$values[] = '%' . $word .'%';
} elseif (stripos($word, 'date:') === 0) {
$word = substr($word, strlen('date:'));
list($minDate, $maxDate) = parseDateInterval($word);
if ($minDate) {
$search .= 'AND e1.id >= ' . $minDate . '000000 ';
}
if ($maxDate) {
$search .= 'AND e1.id <= ' . $maxDate . '000000 ';
}
} elseif (stripos($word, 'pubdate:') === 0) {
$word = substr($word, strlen('pubdate:'));
list($minDate, $maxDate) = parseDateInterval($word);
if ($minDate) {
$search .= 'AND e1.date >= ' . $minDate . ' ';
}
if ($maxDate) {
$search .= 'AND e1.date <= ' . $maxDate . ' ';
}
} else {
if ($word[0] === '#' && isset($word[1])) {
$search .= 'AND e1.tags LIKE ? ';
$values[] = '%' . $word .'%';
} else {
if ($word[0] === '#' && isset($word[1])) {
$search .= 'AND e1.tags LIKE ? ';
$values[] = '%' . $word .'%';
} else {
$search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
$values[] = '%' . $word .'%';
}
$search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
$values[] = '%' . $word .'%';
}
}
}
}
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 = FreshRSS_Entry::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 '
. '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;
@@ -383,6 +552,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
}
public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::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);
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);

View File

@@ -193,10 +193,10 @@ class FreshRSS_Feed extends Minz_Model {
}
$feed = customSimplePie();
$feed->set_feed_url ($url);
$feed->init ();
$mtime = $feed->init();
if ($feed->error ()) {
throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']');
if ((!$mtime) || $feed->error()) {
throw new FreshRSS_Feed_Exception ($feed->error() . ' [' . $url . ']');
}
// si on a utilisé l'auto-discover, notre url va avoir changé
@@ -210,18 +210,27 @@ class FreshRSS_Feed extends Minz_Model {
}
if ($loadDetails) {
$title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8');
$this->_name ($title === null ? $this->url : $title);
$title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '&lt;', '>' => '&gt;', '"' => '&quot;')); //HTML to HTML-PRE //ENT_COMPAT except &
$this->_name ($title == '' ? $this->url : $title);
$this->_website(html_only_entity_decode($feed->get_link()));
$this->_description(html_only_entity_decode($feed->get_description()));
}
// et on charge les articles du flux
$this->loadEntries ($feed);
if (($mtime === true) || ($mtime > $this->lastUpdate)) {
syslog(LOG_DEBUG, 'FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $subscribe_url);
$this->loadEntries($feed); // et on charge les articles du flux
} else {
syslog(LOG_DEBUG, 'FreshRSS use cache for ' . $subscribe_url);
$this->entries = array();
}
$feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
unset($feed);
}
}
}
private function loadEntries ($feed) {
$entries = array ();
@@ -245,11 +254,16 @@ class FreshRSS_Feed extends Minz_Model {
$elinks = array();
foreach ($item->get_enclosures() as $enclosure) {
$elink = $enclosure->get_link();
if (array_key_exists($elink, $elinks)) continue;
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
$content .= '<br /><img src="' . $elink . '" alt="" />';
if (empty($elinks[$elink])) {
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
$content .= '<br /><img src="' . $elink . '" alt="" />';
} elseif (strpos($mime, 'audio/') === 0) {
$content .= '<br /><audio src="' . $elink . '" controls="controls" />';
} elseif (strpos($mime, 'video/') === 0) {
$content .= '<br /><video src="' . $elink . '" controls="controls" />';
}
}
}
@@ -267,8 +281,27 @@ class FreshRSS_Feed extends Minz_Model {
$entry->loadCompleteContent($this->pathEntries());
$entries[] = $entry;
unset($item);
}
$this->entries = $entries;
}
function lock() {
$lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
@unlink($lock);
}
if (($handle = @fopen($lock, 'x')) === false) {
return false;
}
//register_shutdown_function('unlink', $lock);
@fclose($handle);
return true;
}
function unlock() {
$lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
@unlink($lock);
}
}

View File

@@ -24,6 +24,36 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
}
public function addFeedObject($feed) {
// TODO: not sure if we should write this method in DAO since DAO
// should not be aware about feed class
// Add feed only if we don't find it in DB
$feed_search = $this->searchByUrl($feed->url());
if (!$feed_search) {
$values = array(
'id' => $feed->id(),
'url' => $feed->url(),
'category' => $feed->category(),
'name' => $feed->name(),
'website' => $feed->website(),
'description' => $feed->description(),
'lastUpdate' => 0,
'httpAuth' => $feed->httpAuth()
);
$id = $this->addFeed($values);
if ($id) {
$feed->_id($id);
$feed->faviconPrepare();
}
return $id;
}
return $feed_search->id();
}
public function updateFeed ($id, $valuesTmp) {
$set = '';
foreach ($valuesTmp as $key => $v) {
@@ -52,21 +82,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
}
}
public function updateLastUpdate ($id, $inError = 0) {
$sql = 'UPDATE `' . $this->prefix . 'feed` f ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
. 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),'
. 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
. 'lastUpdate=?, error=? '
. 'WHERE f.id=?';
$stm = $this->bd->prepare ($sql);
public function updateLastUpdate ($id, $inError = 0, $updateCache = true) {
if ($updateCache) {
$sql = 'UPDATE `' . $this->prefix . 'feed` f ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
. 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),'
. 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
. 'lastUpdate=?, error=? '
. 'WHERE f.id=?';
} else {
$sql = 'UPDATE `' . $this->prefix . 'feed` f '
. 'SET lastUpdate=?, error=? '
. 'WHERE f.id=?';
}
$values = array (
time (),
time(),
$inError,
$id,
);
$stm = $this->bd->prepare ($sql);
if ($stm && $stm->execute ($values)) {
return $stm->rowCount();
} else {
@@ -164,7 +200,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if (isset ($feed[$id])) {
return $feed[$id];
} else {
return false;
return null;
}
}
public function searchByUrl ($url) {
@@ -180,7 +216,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
if (isset ($feed)) {
return $feed;
} else {
return false;
return null;
}
}
@@ -192,8 +228,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
}
public function listFeedsOrderUpdate () {
$sql = 'SELECT id, name, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate';
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']] = array(
'name' => $line['name'],
'c_name' => $line['c_name'],
);
}
return $feedCategoryNames;
}
public function listFeedsOrderUpdate ($cacheDuration = 1500) {
$sql = 'SELECT id, name, url, lastUpdate, pathEntries, httpAuth, keep_history '
. 'FROM `' . $this->prefix . 'feed` '
. 'WHERE lastUpdate < ' . (time() - intval($cacheDuration))
. ' ORDER BY lastUpdate';
$stm = $this->bd->prepare ($sql);
$stm->execute ();
@@ -220,6 +275,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);
@@ -229,6 +285,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 ('
@@ -241,9 +298,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();

44
app/Models/Share.php Normal file
View File

@@ -0,0 +1,44 @@
<?php
class FreshRSS_Share {
static public function generateUrl($options, $selected, $link, $title) {
$share = $options[$selected['type']];
$matches = array(
'~URL~',
'~TITLE~',
'~LINK~',
);
$replaces = array(
$selected['url'],
self::transformData($title, self::getTransform($share, 'title')),
self::transformData($link, self::getTransform($share, 'link')),
);
$url = str_replace($matches, $replaces, $share['url']);
return $url;
}
static private function transformData($data, $transform) {
if (!is_array($transform)) {
return $data;
}
if (count($transform) === 0) {
return $data;
}
foreach ($transform as $action) {
$data = call_user_func($action, $data);
}
return $data;
}
static private function getTransform($options, $type) {
$transform = $options['transform'];
if (array_key_exists($type, $transform)) {
return $transform[$type];
}
return $transform;
}
}

View File

@@ -77,6 +77,7 @@ class FreshRSS_Themes extends Minz_Model {
'down' => '▽',
'favorite' => '★',
'help' => 'ⓘ',
'key' => '⚿',
'link' => '↗',
'login' => '🔒',
'logout' => '🔓',
@@ -84,6 +85,7 @@ class FreshRSS_Themes extends Minz_Model {
'non-starred' => '☆',
'prev' => '⏪',
'read' => '☑',
'rss' => '☄',
'unread' => '☐',
'refresh' => '🔃', //↻
'search' => '🔍',
@@ -91,6 +93,9 @@ class FreshRSS_Themes extends Minz_Model {
'starred' => '★',
'tag' => '⚐',
'up' => '△',
'view-normal' => '☰',
'view-global' => '☷',
'view-reader' => '☕',
);
if (!isset($alts[$name])) {
return '';

View File

@@ -1,21 +1,5 @@
<?php
require(dirname(__FILE__) . '/../constants.php');
//<Mutex>
$lock = DATA_PATH . '/actualize.lock.txt';
if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
@unlink($lock);
}
if (($handle = @fopen($lock, 'x')) === false) {
syslog(LOG_NOTICE, 'FreshRSS actualize already running?');
fwrite(STDERR, 'FreshRSS actualize already running?' . "\n");
return;
}
register_shutdown_function('unlink', $lock);
//Could use http://php.net/function.pcntl-signal.php to catch interruptions
@fclose($handle);
//</Mutex>
require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
session_cache_limiter('');
@@ -32,7 +16,9 @@ $users = array_unique($users);
foreach ($users as $myUser) {
syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser);
fwrite(STDOUT, 'Actualize ' . $myUser . "...\n"); //Unbuffered
if (defined('STDOUT')) {
fwrite(STDOUT, 'Actualize ' . $myUser . "...\n"); //Unbuffered
}
echo $myUser, ' '; //Buffered
$_GET['c'] = 'feed';
@@ -42,7 +28,8 @@ foreach ($users as $myUser) {
$_SERVER['HTTP_HOST'] = '';
$freshRSS = new FreshRSS();
$freshRSS->_useOb(false);
Minz_Configuration::_authType('none');
Minz_Session::init('FreshRSS');
Minz_Session::_param('currentUser', $myUser);
@@ -50,10 +37,18 @@ foreach ($users as $myUser) {
$freshRSS->init();
$freshRSS->run();
invalidateHttpCache();
if (!invalidateHttpCache()) {
syslog(LOG_NOTICE, 'FreshRSS write access problem in ' . LOG_PATH . '/*.log!');
if (defined('STDERR')) {
fwrite(STDERR, 'Write access problem in ' . LOG_PATH . '/*.log!' . "\n");
}
}
Minz_Session::unset_session(true);
Minz_ModelPdo::clean();
}
syslog(LOG_INFO, 'FreshRSS actualize done.');
if (defined('STDOUT')) {
fwrite(STDOUT, 'Done.' . "\n");
}
echo 'End.', "\n";
ob_end_flush();
fwrite(STDOUT, 'Done.' . "\n");

View File

@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Login',
'login_with_persona' => 'Login with Persona',
'logout' => 'Logout',
'search' => 'Search words or #tags',
'search_short' => 'Search',
@@ -20,12 +21,13 @@ return array (
'your_rss_feeds' => 'Your RSS feeds',
'add_rss_feed' => 'Add a RSS feed',
'no_rss_feed' => 'No RSS feed',
'import_export_opml' => 'Import / export (OPML)',
'import_export' => 'Import / export',
'bookmark' => 'Subscribe (FreshRSS bookmark)',
'subscription_management' => 'Subscriptions management',
'main_stream' => 'Main stream',
'all_feeds' => 'All feeds',
'favorite_feeds' => 'Favourites (%d)',
'favorite_feeds' => 'Favourites (%s)',
'not_read' => '%d unread',
'not_reads' => '%d unread',
@@ -49,7 +51,8 @@ return array (
'show_all_articles' => 'Show all articles',
'show_not_reads' => 'Show only unread',
'show_read' => 'Show only read',
'show_favorite' => 'Show favorites',
'show_favorite' => 'Show only favorites',
'show_not_favorite' => 'Show all but favorites',
'older_first' => 'Oldest first',
'newer_first' => 'Newer first',
@@ -77,14 +80,17 @@ return array (
'sharing_management' => 'Sharing options management',
'bad_opml_file' => 'Your OPML file is invalid',
'shortcuts_updated' => 'Shortcuts have been updated',
'shortcuts_management' => 'Shortcuts management',
'shortcuts_navigation' => 'Navigation',
'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
'shortcuts_article_action' => 'Article actions',
'shortcuts_other_action' => 'Other actions',
'feeds_marked_read' => 'Feeds have been marked as read',
'updated' => 'Modifications have been updated',
'already_subscribed' => 'You have already subscribed to <em>%s</em>',
'feed_added' => 'RSS feed <em>%s</em> has been added',
'feed_not_added' => '<em>%s</em> could not be added',
'internal_problem_feed' => 'The RSS feed could not be added. Check FressRSS logs for details.',
'internal_problem_feed' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.',
'invalid_url' => 'URL <em>%s</em> is invalid',
'feed_actualized' => '<em>%s</em> has been updated',
'n_feeds_actualized' => '%d feeds have been updated',
@@ -121,23 +127,31 @@ return array (
'javascript_for_shortcuts' => 'JavaScript must be enabled in order to use shortcuts',
'javascript_should_be_activated'=> 'JavaScript must be enabled',
'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
'see_on_website' => 'See article on its original website',
'see_on_website' => 'See on original website',
'next_article' => 'Skip to the next article',
'shift_for_last' => '+ <code>shift</code> to skip to the last article of page',
'last_article' => 'Skip to the last article',
'previous_article' => 'Skip to the previous article',
'shift_for_first' => '+ <code>shift</code> to skip to the first article of page',
'first_article' => 'Skip to the first article',
'next_page' => 'Skip to the next page',
'previous_page' => 'Skip to the previous page',
'collapse_article' => 'Collapse current article',
'auto_share' => 'Share current article',
'collapse_article' => 'Collapse',
'auto_share' => 'Share',
'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
'focus_search' => 'Access search box',
'file_to_import' => 'File to import',
'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
'import' => 'Import',
'export' => 'Export',
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
'starred_list' => 'List of favourite articles',
'feed_list' => 'List of %s articles',
'or' => 'or',
'informations' => 'Information',
'damn' => 'Damn!',
'feed_in_error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
'feed_empty' => 'This feed is empty. Please verify that it is still maintained.',
'feed_description' => 'Description',
'website_url' => 'Website URL',
'feed_url' => 'Feed URL',
@@ -158,6 +172,8 @@ return array (
'http_username' => 'HTTP username',
'http_password' => 'HTTP password',
'blank_to_disable' => 'Leave blank to disable',
'share_name' => 'Share name to display',
'share_url' => 'Share URL to use',
'not_yet_implemented' => 'Not yet implemented',
'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds',
'no_selected_feed' => 'No feed selected.',
@@ -166,8 +182,12 @@ return array (
'current_user' => 'Current user',
'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>',
'persona_connection_email' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)',
'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles',
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ',
'api_enabled' => 'Allow <abbr>API</abbr> access <small>(required for mobile apps)</small>',
'auth_token' => 'Authentication token',
'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>',
'login_configuration' => 'Login',
@@ -193,6 +213,7 @@ return array (
'purge_completed' => 'Purge completed (%d articles deleted)',
'archiving_configuration_help' => 'More options are available in the individual stream settings',
'reading_configuration' => 'Reading',
'display_configuration' => 'Display',
'articles_per_page' => 'Number of articles per page',
'default_view' => 'Default view',
'sort_order' => 'Sort order',
@@ -200,10 +221,11 @@ return array (
'display_articles_unfolded' => 'Show articles unfolded by default',
'after_onread' => 'After “mark all as read”,',
'jump_next' => 'jump to next unread sibling (feed or category)',
'reading_icons' => 'Reading icons',
'article_icons' => 'Article icons',
'top_line' => 'Top line',
'bottom_line' => 'Bottom line',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
'sticky_post' => 'Stick the article to the top when opened',
'auto_read_when' => 'Mark article as read…',
'article_selected' => 'when article is selected',
'article_open_on_website' => 'when article is opened on its original website',
@@ -218,9 +240,15 @@ return array (
'optimize_bdd' => 'Optimize database',
'optimize_todo_sometimes' => 'To do occasionally to reduce the size of the database',
'theme' => 'Theme',
'content_width' => 'Content width',
'width_thin' => 'Thin',
'width_medium' => 'Medium',
'width_large' => 'Large',
'width_no_limit' => 'No limit',
'more_information' => 'More information',
'activate_sharing' => 'Activate sharing',
'shaarli' => 'Shaarli',
'blogotext' => 'Blogotext',
'wallabag' => 'wallabag',
'diaspora' => 'Diaspora*',
'twitter' => 'Twitter',
@@ -241,6 +269,7 @@ return array (
'rss_feeds_of' => 'RSS feed of %s',
'refresh' => 'Refresh',
'no_feed_to_refresh' => 'There is no feed to refresh…',
'today' => 'Today',
'yesterday' => 'Yesterday',
@@ -267,7 +296,7 @@ return array (
'logs_empty' => 'Log file is empty',
'clear_logs' => 'Clear the logs',
'forbidden_access' => 'Access forbidden! (%s)',
'forbidden_access' => 'Access is forbidden!',
'login_required' => 'Login required:',
'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!',

View File

@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Connexion',
'login_with_persona' => 'Connexion avec Persona',
'logout' => 'Déconnexion',
'search' => 'Rechercher des mots ou des #tags',
'search_short' => 'Rechercher',
@@ -20,12 +21,13 @@ return array (
'your_rss_feeds' => 'Vos flux RSS',
'add_rss_feed' => 'Ajouter un flux RSS',
'no_rss_feed' => 'Aucun flux RSS',
'import_export_opml' => 'Importer / exporter (OPML)',
'import_export' => 'Importer / exporter',
'bookmark' => 'Sabonner (bookmark FreshRSS)',
'subscription_management' => 'Gestion des abonnements',
'main_stream' => 'Flux principal',
'all_feeds' => 'Tous les flux',
'favorite_feeds' => 'Favoris (%d)',
'favorite_feeds' => 'Favoris (%s)',
'not_read' => '%d non lu',
'not_reads' => '%d non lus',
@@ -50,6 +52,7 @@ return array (
'show_not_reads' => 'Afficher les non lus',
'show_read' => 'Afficher les lus',
'show_favorite' => 'Afficher les favoris',
'show_not_favorite' => 'Afficher tout sauf les favoris',
'older_first' => 'Plus anciens en premier',
'newer_first' => 'Plus récents en premier',
@@ -77,14 +80,17 @@ return array (
'sharing_management' => 'Gestion des options de partage',
'bad_opml_file' => 'Votre fichier OPML nest pas valide',
'shortcuts_updated' => 'Les raccourcis ont été mis à jour',
'shortcuts_management' => 'Gestion des raccourcis',
'shortcuts_navigation' => 'Navigation',
'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation sappliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation sappliquent aux catégories.',
'shortcuts_article_action' => 'Actions associées à larticle courant',
'shortcuts_other_action' => 'Autres actions',
'feeds_marked_read' => 'Les flux ont été marqués comme lus',
'updated' => 'Modifications enregistrées',
'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>',
'feed_added' => 'Le flux <em>%s</em> a bien été ajouté',
'feed_not_added' => '<em>%s</em> na pas pu être ajouté',
'internal_problem_feed' => 'Le flux na pas pu être ajouté. Consulter les logs de FreshRSS pour plus de détails.',
'internal_problem_feed' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.',
'invalid_url' => 'Lurl <em>%s</em> est invalide',
'feed_actualized' => '<em>%s</em> a été mis à jour',
'n_feeds_actualized' => '%d flux ont été mis à jour',
@@ -121,23 +127,31 @@ return array (
'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis',
'javascript_should_be_activated'=> 'Le JavaScript doit être activé',
'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
'see_on_website' => 'Voir larticle sur le site dorigine',
'see_on_website' => 'Voir sur le site dorigine',
'next_article' => 'Passer à larticle suivant',
'shift_for_last' => '+ <code>shift</code> pour passer au dernier article de la page',
'last_article' => 'Passer au dernier article',
'previous_article' => 'Passer à larticle précédent',
'shift_for_first' => '+ <code>shift</code> pour passer au premier article de la page',
'first_article' => 'Passer au premier article',
'next_page' => 'Passer à la page suivante',
'previous_page' => 'Passer à la page précédente',
'collapse_article' => 'Refermer larticle courant',
'auto_share' => 'Partager larticle courant',
'collapse_article' => 'Refermer',
'auto_share' => 'Partager',
'auto_share_help' => 'Si il ny a quun mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
'focus_search' => 'Accéder à la recherche',
'file_to_import' => 'Fichier à importer',
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
'import' => 'Importer',
'export' => 'Exporter',
'export_opml' => 'Exporter la liste des flux (OPML)',
'export_starred' => 'Exporter les favoris',
'starred_list' => 'Liste des articles favoris',
'feed_list' => 'Liste des articles de %s',
'or' => 'ou',
'informations' => 'Informations',
'damn' => 'Arf !',
'feed_in_error' => 'Ce flux a rencontré un problème. Veuillez vérifier quil est toujours accessible puis actualisez-le.',
'feed_empty' => 'Ce flux est vide. Veuillez vérifier quil est toujours maintenu.',
'feed_description' => 'Description',
'website_url' => 'URL du site',
'feed_url' => 'URL du flux',
@@ -158,6 +172,8 @@ return array (
'http_username' => 'Identifiant HTTP',
'http_password' => 'Mot de passe HTTP',
'blank_to_disable' => 'Laissez vide pour désactiver',
'share_name' => 'Nom du partage à afficher',
'share_url' => 'URL du partage à utiliser',
'not_yet_implemented' => 'Pas encore implémenté',
'access_protected_feeds' => 'La connexion permet daccéder aux flux protégés par une authentification HTTP',
'no_selected_feed' => 'Aucun flux sélectionné.',
@@ -165,9 +181,13 @@ return array (
'current_user' => 'Utilisateur actuel',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
'default_user' => 'Nom de lutilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>',
'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 lutilisateur 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 : ',
'api_enabled' => 'Autoriser laccès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>',
'auth_token' => 'Jeton didentification',
'explain_token' => 'Permet daccéder à la sortie RSS de lutilisateur par défaut sans besoin de sauthentifier.<br /><kbd>%s?output=rss&token=%s</kbd>',
'login_configuration' => 'Identification',
@@ -193,6 +213,7 @@ return array (
'purge_completed' => 'Purge effectuée (%d articles supprimés)',
'archiving_configuration_help' => 'Dautres options sont disponibles dans la configuration individuelle des flux',
'reading_configuration' => 'Lecture',
'display_configuration' => 'Affichage',
'articles_per_page' => 'Nombre darticles par page',
'default_view' => 'Vue par défaut',
'sort_order' => 'Ordre de tri',
@@ -200,10 +221,11 @@ return array (
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
'after_onread' => 'Après “marquer tout comme lu”,',
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
'reading_icons' => 'Icônes de lecture',
'article_icons' => 'Icônes darticle',
'top_line' => 'Ligne du haut',
'bottom_line' => 'Ligne du bas',
'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images',
'sticky_post' => 'Aligner larticle en haut quand il est ouvert',
'auto_read_when' => 'Marquer un article comme lu…',
'article_selected' => 'lorsque larticle est sélectionné',
'article_open_on_website' => 'lorsque larticle est ouvert sur le site dorigine',
@@ -218,9 +240,15 @@ return array (
'optimize_bdd' => 'Optimiser la base de données',
'optimize_todo_sometimes' => 'À faire de temps en temps pour réduire la taille de la BDD',
'theme' => 'Thème',
'content_width' => 'Largeur du contenu',
'width_thin' => 'Fine',
'width_medium' => 'Moyenne',
'width_large' => 'Large',
'width_no_limit' => 'Pas de limite',
'more_information' => 'Plus dinformations',
'activate_sharing' => 'Activer le partage',
'shaarli' => 'Shaarli',
'blogotext' => 'Blogotext',
'wallabag' => 'wallabag',
'diaspora' => 'Diaspora*',
'twitter' => 'Twitter',
@@ -241,6 +269,7 @@ return array (
'rss_feeds_of' => 'Flux RSS de %s',
'refresh' => 'Actualisation',
'no_feed_to_refresh' => 'Il ny a aucun flux à actualiser…',
'today' => 'Aujourdhui',
'yesterday' => 'Hier',
@@ -267,7 +296,7 @@ return array (
'logs_empty' => 'Les logs sont vides',
'clear_logs' => 'Effacer les logs',
'forbidden_access' => 'Accès interdit ! (%s)',
'forbidden_access' => 'Laccès vous est interdit !',
'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 !',

View File

@@ -1,7 +1,10 @@
<ul class="nav nav-list aside">
<li class="nav-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
<li class="item<?php echo Minz_Request::actionName () == 'display' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a>
<a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('display_configuration'); ?></a>
</li>
<li class="item<?php echo Minz_Request::actionName () == 'reading' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'reading'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a>
</li>
<li class="item<?php echo Minz_Request::actionName () == 'archiving' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a>

View File

@@ -20,9 +20,14 @@
<?php echo $cat->name (); ?>
</option>
<?php } ?>
<option value="nc"><?php echo Minz_Translate::t ('new_category'); ?></option>
</select>
</li>
<li class="input" style="display:none">
<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
</li>
<li class="separator"></li>
<li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li>
@@ -38,8 +43,14 @@
</div>
</form></li>
<li class="item<?php echo Minz_Request::actionName () == 'importExport' ? ' active' : ''; ?>">
<a href="<?php echo _url ('configure', 'importExport'); ?>"><?php echo Minz_Translate::t ('import_export_opml'); ?></a>
<li class="item">
<a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&amp;url_rss='+encodeURIComponent(url), '_blank');})();">
<?php echo Minz_Translate::t('bookmark'); ?>
</a>
</li>
<li class="item<?php echo Minz_Request::controllerName () == 'importExport' ? ' active' : ''; ?>">
<a href="<?php echo _url ('importExport', 'index'); ?>"><?php echo Minz_Translate::t ('import_export'); ?></a>
</li>
<li class="item<?php echo Minz_Request::actionName () == 'categorize' ? ' active' : ''; ?>">
@@ -51,7 +62,7 @@
<?php if (!empty ($this->feeds)) { ?>
<?php foreach ($this->feeds as $feed) { ?>
<?php $nbEntries = $feed->nbEntries (); ?>
<li class="item<?php echo ($this->flux && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
<li class="item<?php echo (isset($this->flux) && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
<a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>">
<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
<?php echo $feed->name (); ?>

View File

@@ -20,7 +20,7 @@
}
?>
<li>
<div class="category all">
<div class="category all<?php echo $this->get_c == 'a' ? ' active' : ''; ?>">
<a data-unread="<?php echo formatNumber($this->nb_not_read); ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>">
<?php echo FreshRSS_Themes::icon('all'); ?>
<?php echo Minz_Translate::t ('main_stream'); ?>
@@ -29,7 +29,7 @@
</li>
<li>
<div class="category favorites">
<div class="category favorites<?php echo $this->get_c == 's' ? ' active' : ''; ?>">
<a data-unread="<?php echo formatNumber($this->nb_favorites['unread']); ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>">
<?php echo FreshRSS_Themes::icon('bookmark'); ?>
<?php echo Minz_Translate::t('favorite_feeds', formatNumber($this->nb_favorites['all'])); ?>

View File

@@ -67,7 +67,8 @@ if (Minz_Configuration::canLogIn()) {
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close"></a></li>
<li class="dropdown-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a></li>
<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('display_configuration'); ?></a></li>
<li class="item"><a href="<?php echo _url ('configure', 'reading'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a></li>
<li class="item"><a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a></li>
<li class="item"><a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a></li>
<li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a></li>
@@ -75,8 +76,8 @@ if (Minz_Configuration::canLogIn()) {
<li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
<li class="separator"></li>
<li class="item"><a href="<?php echo _url ('index', 'stats'); ?>"><?php echo Minz_Translate::t ('stats'); ?></a></li>
<li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
<li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Minz_Translate::t ('logs'); ?></a></li>
<li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
<?php
if (Minz_Configuration::canLogIn()) {
?><li class="separator"></li><?php

View File

@@ -3,46 +3,59 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1.0" />
<?php echo self::headTitle (); ?>
<?php echo self::headStyle (); ?>
<?php echo self::headScript (); ?>
<?php echo self::headTitle(); ?>
<?php echo self::headStyle(); ?>
<?php echo self::headScript(); ?>
<script>//<![CDATA[
<?php $this->renderHelper ('javascript_vars'); ?>
<?php $this->renderHelper('javascript_vars'); ?>
//]]></script>
<?php
if (!empty($this->nextId)) {
$params = Minz_Request::params ();
$params = Minz_Request::params();
$params['next'] = $this->nextId;
?>
<link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display (array ('c' => Minz_Request::controllerName (), 'a' => Minz_Request::actionName (), 'params' => $params)); ?>" />
<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="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->rss_url)) { ?>
<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display ($this->rss_url); ?>" />
<?php
if (isset($this->url)) {
$rss_url = $this->url;
$rss_url['params']['output'] = 'rss';
?>
<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($rss_url); ?>" />
<?php } ?>
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('starred', true); ?>">
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('non-starred', true); ?>">
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('read', true); ?>">
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('unread', true); ?>">
<link rel="apple-touch-icon" href="<?php echo Minz_Url::display('/themes/icons/apple-touch-icon.png'); ?>">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="<?php echo Minz_Configuration::title(); ?>">
<meta name="msapplication-TileColor" content="#FFF" />
<meta name="robots" content="noindex,nofollow" />
</head>
<body class="<?php echo Minz_Request::param('output', 'normal'); ?>">
<?php $this->partial ('header'); ?>
<?php $this->partial('header'); ?>
<div id="global">
<?php $this->render (); ?>
<?php $this->render(); ?>
</div>
<?php
if (isset ($this->notification)) {
$msg = '';
$status = 'closed';
if (isset($this->notification)) {
$msg = $this->notification['content'];
$status = $this->notification['type'];
invalidateHttpCache();
}
?>
<div class="notification <?php echo $this->notification['type']; ?>">
<?php echo $this->notification['content']; ?>
<div id="notification" class="notification <?php echo $status; ?>">
<span class="msg"><?php echo $msg; ?></span>
<a class="close" href=""><?php echo FreshRSS_Themes::icon('close'); ?></a>
</div>
<?php } ?>
</body>
</html>

View File

@@ -7,8 +7,80 @@
<?php } ?>
<?php if ($this->loginOk) { ?>
<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
<?php $url_state = $this->url;
if ($this->state & FreshRSS_Entry::STATE_READ) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_READ;
$checked = 'true';
$class = 'active';
} else {
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_READ;
$checked = 'false';
$class = '';
}
?>
<div class="stick">
<a id="toggle-read"
class="btn <?php echo $class; ?>"
aria-checked="<?php echo $checked; ?>"
href="<?php echo Minz_Url::display ($url_state); ?>"
title="<?php echo Minz_Translate::t ('show_read'); ?>">
<?php echo FreshRSS_Themes::icon('read'); ?>
</a>
<?php
if ($this->state & FreshRSS_Entry::STATE_NOT_READ) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_READ;
$checked = 'true';
$class = 'active';
} else {
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_READ;
$checked = 'false';
$class = '';
}
?>
<a id="toggle-unread"
class="btn <?php echo $class; ?>"
aria-checked="<?php echo $checked; ?>"
href="<?php echo Minz_Url::display ($url_state); ?>"
title="<?php echo Minz_Translate::t ('show_not_reads'); ?>">
<?php echo FreshRSS_Themes::icon('unread'); ?>
</a>
<?php
if ($this->state & FreshRSS_Entry::STATE_FAVORITE) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_FAVORITE;
$checked = 'true';
$class = 'active';
} else {
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_FAVORITE;
$checked = 'false';
$class = '';
}
?>
<a id="toggle-favorite"
class="btn <?php echo $class; ?>"
aria-checked="<?php echo $checked; ?>"
href="<?php echo Minz_Url::display ($url_state); ?>"
title="<?php echo Minz_Translate::t ('show_favorite'); ?>">
<?php echo FreshRSS_Themes::icon('starred'); ?>
</a>
<?php
if ($this->state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_FAVORITE;
$checked = 'true';
$class = 'active';
} else {
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_FAVORITE;
$checked = 'false';
$class = '';
}
?>
<a id="toggle-not-favorite"
class="btn <?php echo $class; ?>"
aria-checked="<?php echo $checked; ?>"
href="<?php echo Minz_Url::display ($url_state); ?>"
title="<?php echo Minz_Translate::t ('show_not_favorite'); ?>">
<?php echo FreshRSS_Themes::icon('non-starred'); ?>
</a>
</div>
<?php
$get = false;
$string_mark = Minz_Translate::t ('mark_all_read');
@@ -59,8 +131,12 @@
break;
}
}
$p = isset($this->entries[0]) ? $this->entries[0] : null;
$idMax = $p === null ? '0' : $p->id();
if ($this->order === 'ASC') {
$idMax = 0;
} else {
$p = isset($this->entries[0]) ? $this->entries[0] : null;
$idMax = $p === null ? '0' : $p->id();
}
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax));
$output = Minz_Request::param('output', '');
@@ -80,7 +156,7 @@
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close"></a></li>
<li class="item"><a href="<?php echo $markReadUrl; ?>"><?php echo $string_mark; ?></a></li>
<li class="item"><a href="<?php echo $markReadUrl; ?>"><?php echo $string_mark; ?></a></li>
<li class="separator"></li>
<?php
$today = $this->today;
@@ -93,113 +169,30 @@
</div>
<?php } ?>
<?php
$params = Minz_Request::params ();
if (isset ($params['search'])) {
$params['search'] = urlencode ($params['search']);
}
$url = array (
'c' => 'index',
'a' => 'index',
'params' => $params
);
?>
<div class="dropdown" id="nav_menu_views">
<div id="dropdown-views" class="dropdown-target"></div>
<a class="dropdown-toggle btn" href="#dropdown-views"><?php echo Minz_Translate::t ('display'); ?> <?php echo FreshRSS_Themes::icon('down'); ?></a>
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close"></a></li>
<?php $url_output = $this->url; ?>
<div class="stick" id="nav_menu_views">
<?php $url_output['params']['output'] = 'normal'; ?>
<a class="view_normal btn <?php echo $actual_view == 'normal'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('normal_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
<?php echo FreshRSS_Themes::icon("view-normal"); ?>
</a>
<?php
$url_output = $url;
if ($actual_view !== 'normal') { ?>
<li class="item">
<?php $url_output['params']['output'] = 'normal'; ?>
<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
<?php echo Minz_Translate::t ('normal_view'); ?>
</a>
</li>
<?php } if($actual_view !== 'reader') { ?>
<li class="item">
<?php $url_output['params']['output'] = 'reader'; ?>
<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
<?php echo Minz_Translate::t ('reader_view'); ?>
</a>
</li>
<?php } if($actual_view !== 'global') { ?>
<li class="item">
<?php $url_output['params']['output'] = 'global'; ?>
<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
<?php echo Minz_Translate::t ('global_view'); ?>
</a>
</li>
<?php } ?>
<?php $url_output['params']['output'] = 'global'; ?>
<a class="view_global btn <?php echo $actual_view == 'global'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
<?php echo FreshRSS_Themes::icon("view-global"); ?>
</a>
<li class="separator"></li>
<?php $url_output['params']['output'] = 'reader'; ?>
<a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('reader_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
<?php echo FreshRSS_Themes::icon("view-reader"); ?>
</a>
<?php
$url_state = $url;
$url_state['params']['state'] = 'all';
?>
<li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'all') ? 'true' :'false'; ?>">
<a class="print_all" href="<?php echo Minz_Url::display ($url_state); ?>">
<?php echo Minz_Translate::t ('show_all_articles'); ?>
</a>
</li>
<?php
$url_state['params']['state'] = 'not_read';
?>
<li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'not_read') ? 'true' :'false'; ?>">
<a class="print_non_read" href="<?php echo Minz_Url::display ($url_state); ?>">
<?php echo Minz_Translate::t ('show_not_reads'); ?>
</a>
</li>
<?php
$url_state['params']['state'] = 'read';
?>
<li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'read') ? 'true' :'false'; ?>">
<a class="print_read" href="<?php echo Minz_Url::display ($url_state); ?>">
<?php echo Minz_Translate::t ('show_read'); ?>
</a>
</li>
<?php
$url_state['params']['state'] = 'favorite';
?>
<li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'favorite') ? 'true' :'false'; ?>">
<a class="print_favorite" href="<?php echo Minz_Url::display ($url_state); ?>">
<?php echo Minz_Translate::t ('show_favorite'); ?>
</a>
</li>
<li class="separator"></li>
<li class="item">
<?php
$url_order = $url;
if ($this->order === 'DESC') {
$url_order['params']['order'] = 'ASC';
?>
<a href="<?php echo Minz_Url::display ($url_order); ?>">
<?php echo Minz_Translate::t ('older_first'); ?>
</a>
<?php
} else {
$url_order['params']['order'] = 'DESC';
?>
<a href="<?php echo Minz_Url::display ($url_order); ?>">
<?php echo Minz_Translate::t ('newer_first'); ?>
</a>
<?php } ?>
</li>
<li class="separator"></li>
<li class="item">
<a class="view_rss" target="_blank" href="<?php echo Minz_Url::display ($this->rss_url); ?>">
<?php echo Minz_Translate::t ('rss_view'); ?>
</a>
</li>
</ul>
<?php
$url_output['params']['output'] = 'rss';
$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'); ?>
</a>
</div>
<div class="item search">
@@ -223,4 +216,25 @@
<?php } ?>
</form>
</div>
<?php
if ($this->order === 'DESC') {
$order = 'ASC';
$icon = 'up';
$title = 'older_first';
} else {
$order = 'DESC';
$icon = 'down';
$title = 'newer_first';
}
$url_order = $this->url;
$url_order['params']['order'] = $order;
?>
<a class="btn" href="<?php echo Minz_Url::display ($url_order); ?>" title="<?php echo Minz_Translate::t ($title); ?>">
<?php echo FreshRSS_Themes::icon($icon); ?>
</a>
<?php if ($this->loginOk || Minz_Configuration::allowAnonymousRefresh()) { ?>
<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
<?php } ?>
</div>

View File

@@ -3,7 +3,6 @@ define('SQL_CREATE_TABLES', '
CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
`name` varchar(255) NOT NULL,
`color` char(7),
PRIMARY KEY (`id`),
UNIQUE KEY (`name`) -- v0.7
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci

View File

@@ -14,14 +14,16 @@
<?php echo Minz_Translate::t ('category_number', $i); ?>
</label>
<div class="group-controls">
<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
<div class="stick">
<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
<?php if ($cat->nbFeed () > 0) { ?>
<a class="confirm" href="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Minz_Translate::t ('ask_empty'); ?></a>
<?php } ?>
<?php if ($cat->nbFeed () > 0) { ?>
<button type="submit" class="btn btn-attention confirm" formaction="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Minz_Translate::t ('ask_empty'); ?></button>
<?php } ?>
</div>
(<?php echo Minz_Translate::t ('number_feeds', $cat->nbFeed ()); ?>)
<?php if ($cat->id () == $this->defaultCategory->id ()) { ?>
<?php if ($cat->id () === $this->defaultCategory->id ()) { ?>
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('can_not_be_deleted'); ?>
<?php } ?>

View File

@@ -4,7 +4,7 @@
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
<form method="post" action="<?php echo _url ('configure', 'display'); ?>">
<legend><?php echo Minz_Translate::t ('theme'); ?></legend>
<legend><?php echo Minz_Translate::t ('display_configuration'); ?></legend>
<div class="form-group">
<label class="group-name" for="language"><?php echo Minz_Translate::t ('language'); ?></label>
@@ -35,122 +35,29 @@
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
</div>
</div>
<legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend>
<?php $width = $this->conf->content_width; ?>
<div class="form-group">
<label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('articles_per_page'); ?></label>
<label class="group-name" for="content_width"><?php echo Minz_Translate::t('content_width'); ?></label>
<div class="group-controls">
<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="sort_order"><?php echo Minz_Translate::t ('sort_order'); ?></label>
<div class="group-controls">
<select name="sort_order" id="sort_order">
<option value="DESC"<?php echo $this->conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option>
<option value="ASC"<?php echo $this->conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option>
<select name="content_width" id="content_width" required="">
<option value="thin" <?php echo $width === 'thin'? 'selected="selected"' : ''; ?>>
<?php echo Minz_Translate::t('width_thin'); ?>
</option>
<option value="medium" <?php echo $width === 'medium'? 'selected="selected"' : ''; ?>>
<?php echo Minz_Translate::t('width_medium'); ?>
</option>
<option value="large" <?php echo $width === 'large'? 'selected="selected"' : ''; ?>>
<?php echo Minz_Translate::t('width_large'); ?>
</option>
<option value="no_limit" <?php echo $width === 'no_limit'? 'selected="selected"' : ''; ?>>
<?php echo Minz_Translate::t('width_no_limit'); ?>
</option>
</select>
</div>
</div>
<div class="form-group">
<label class="group-name" for="view_mode"><?php echo Minz_Translate::t ('default_view'); ?></label>
<div class="group-controls">
<select name="view_mode" id="view_mode">
<option value="normal"<?php echo $this->conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option>
<option value="reader"<?php echo $this->conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option>
<option value="global"<?php echo $this->conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option>
</select>
<label class="radio" for="radio_all">
<input type="radio" name="default_view" id="radio_all" value="all"<?php echo $this->conf->default_view === 'all' ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('show_all_articles'); ?>
</label>
<label class="radio" for="radio_not_read">
<input type="radio" name="default_view" id="radio_not_read" value="not_read"<?php echo $this->conf->default_view === 'not_read' ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('show_not_reads'); ?>
</label>
</div>
</div>
<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="display_posts">
<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('display_articles_unfolded'); ?>
<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">
<label class="group-name"><?php echo Minz_Translate::t ('auto_read_when'); ?></label>
<div class="group-controls">
<label class="checkbox" for="check_open_article">
<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('article_selected'); ?>
</label>
<label class="checkbox" for="check_open_site">
<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('article_open_on_website'); ?>
</label>
<label class="checkbox" for="check_scroll">
<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo $this->conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('scroll'); ?>
</label>
<label class="checkbox" for="check_reception">
<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo $this->conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('upon_reception'); ?>
</label>
</div>
</div>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('after_onread'); ?></label>
<div class="group-controls">
<label class="checkbox" for="onread_jump_next">
<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo $this->conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('jump_next'); ?>
</label>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
</div>
</div>
<legend><?php echo Minz_Translate::t ('reading_icons'); ?></legend>
<div class="form-group">
<label class="group-name" for="theme"><?php echo Minz_Translate::t ('article_icons'); ?></label>
<table>
<thead>
<tr>

View File

@@ -7,8 +7,12 @@
<h1><?php echo $this->flux->name (); ?></h1>
<?php echo $this->flux->description (); ?>
<?php $nbEntries = $this->flux->nbEntries (); ?>
<?php if ($this->flux->inError ()) { ?>
<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p>
<?php } elseif ($nbEntries === 0) { ?>
<p class="alert alert-warn"><?php echo Minz_Translate::t ('feed_empty'); ?></p>
<?php } ?>
<form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off">
@@ -28,16 +32,21 @@
<div class="form-group">
<label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label>
<div class="group-controls">
<input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" />
<a target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
<div class="stick">
<input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" />
<a class="btn" target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
</div>
</div>
</div>
<div class="form-group">
<label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label>
<div class="group-controls">
<input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" />
<a target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
  <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
<div class="stick">
<input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" />
<a class="btn" target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
</div>
<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
</div>
</div>
<div class="form-group">
@@ -81,7 +90,7 @@
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('number_articles'); ?></label>
<div class="group-controls">
<span class="control"><?php echo $this->flux->nbEntries (); ?></span>
<span class="control"><?php echo $nbEntries; ?></span>
</div>
</div>
<div class="form-group">

View File

@@ -1,40 +0,0 @@
<?php
require_once(LIB_PATH . '/lib_opml.php');
if ($this->req == 'export') {
echo '<?xml version="1.0" encoding="UTF-8" ?>';
?>
<!-- Generated by <?php echo Minz_Configuration::title (); ?> -->
<opml version="2.0">
<head>
<title><?php echo Minz_Configuration::title (); ?> OPML Feed</title>
<dateCreated><?php echo date('D, d M Y H:i:s'); ?></dateCreated>
</head>
<body>
<?php echo opml_export ($this->categories); ?>
</body>
</opml>
<?php } else { ?>
<?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>
<form method="post" action="<?php echo Minz_Url::display (array ('c' => 'configure', 'a' => 'importExport', 'params' => array ('q' => 'import'))); ?>" enctype="multipart/form-data">
<legend><?php echo Minz_Translate::t ('import_export_opml'); ?></legend>
<div class="form-group">
<label class="group-name" for="file"><?php echo Minz_Translate::t ('file_to_import'); ?></label>
<div class="group-controls">
<input type="file" name="file" id="file" />
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('import'); ?></button>
<?php echo Minz_Translate::t ('or'); ?>
<a target="_blank" class="btn btn-important" href="<?php echo _url ('configure', 'importExport', 'q', 'export'); ?>"><?php echo Minz_Translate::t ('export'); ?></a>
</div>
</div>
</form>
</div>
<?php } ?>

View File

@@ -0,0 +1,125 @@
<?php $this->partial ('aside_configure'); ?>
<div class="post">
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
<form method="post" action="<?php echo _url ('configure', 'reading'); ?>">
<legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend>
<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; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="sort_order"><?php echo Minz_Translate::t ('sort_order'); ?></label>
<div class="group-controls">
<select name="sort_order" id="sort_order">
<option value="DESC"<?php echo $this->conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option>
<option value="ASC"<?php echo $this->conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option>
</select>
</div>
</div>
<div class="form-group">
<label class="group-name" for="view_mode"><?php echo Minz_Translate::t ('default_view'); ?></label>
<div class="group-controls">
<select name="view_mode" id="view_mode">
<option value="normal"<?php echo $this->conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option>
<option value="reader"<?php echo $this->conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option>
<option value="global"<?php echo $this->conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option>
</select>
<label class="radio" for="radio_all">
<input type="radio" name="default_view" id="radio_all" value="<?php echo FreshRSS_Entry::STATE_ALL; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_ALL ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('show_all_articles'); ?>
</label>
<label class="radio" for="radio_not_read">
<input type="radio" name="default_view" id="radio_not_read" value="<?php echo FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_NOT_READ ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('show_not_reads'); ?>
</label>
</div>
</div>
<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="display_posts">
<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('display_articles_unfolded'); ?>
<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="sticky_post">
<input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo $this->conf->sticky_post ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('sticky_post'); ?>
<noscript><strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
</label>
</div>
</div>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('auto_read_when'); ?></label>
<div class="group-controls">
<label class="checkbox" for="check_open_article">
<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('article_selected'); ?>
</label>
<label class="checkbox" for="check_open_site">
<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('article_open_on_website'); ?>
</label>
<label class="checkbox" for="check_scroll">
<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo $this->conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('scroll'); ?>
</label>
<label class="checkbox" for="check_reception">
<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo $this->conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('upon_reception'); ?>
</label>
</div>
</div>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('after_onread'); ?></label>
<div class="group-controls">
<label class="checkbox" for="onread_jump_next">
<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo $this->conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('jump_next'); ?>
</label>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
</div>
</div>
</form>
</div>

View File

@@ -3,54 +3,49 @@
<div class="post">
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
<form method="post" action="<?php echo _url ('configure', 'sharing'); ?>">
<form method="post" action="<?php echo _url ('configure', 'sharing'); ?>"
data-simple='<div class="form-group"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="share remove btn btn-attention"><?php echo FreshRSS_Themes::icon('close'); ?></a>
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>'
data-advanced='<div class="form-group"><label class="group-name">##label##</label><div class="group-controls">
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
<div class="stick">
<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
<input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
<a href="#" class="share remove btn btn-attention"><?php echo FreshRSS_Themes::icon('close'); ?></a></div>
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="##help##"><?php echo FreshRSS_Themes::icon('help'); ?></a>
</div></div>'>
<legend><?php echo Minz_Translate::t ('sharing'); ?></legend>
<div class="form-group">
<label class="group-name" for="shaarli">
<?php echo Minz_Translate::t ('your_shaarli'); ?>
</label>
<div class="group-controls">
<input type="url" id="shaarli" name="shaarli" class="extend" value="<?php echo $this->conf->sharing ('shaarli'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli"><?php echo Minz_Translate::t ('more_information'); ?></a>
</div>
</div>
<div class="form-group">
<label class="group-name" for="wallabag">
<?php echo Minz_Translate::t ('your_wallabag'); ?>
</label>
<div class="group-controls">
<input type="url" id="wallabag" name="wallabag" class="extend" value="<?php echo $this->conf->sharing ('wallabag'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://www.wallabag.org"><?php echo Minz_Translate::t ('more_information'); ?></a>
</div>
</div>
<div class="form-group">
<label class="group-name" for="diaspora">
<?php echo Minz_Translate::t ('your_diaspora_pod'); ?>
</label>
<div class="group-controls">
<input type="url" id="diaspora" name="diaspora" class="extend" value="<?php echo $this->conf->sharing ('diaspora'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="https://diasporafoundation.org/"><?php echo Minz_Translate::t ('more_information'); ?></a>
</div>
</div>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t ('activate_sharing'); ?></label>
<div class="group-controls">
<?php
$services = array ('twitter', 'g+', 'facebook', 'email', 'print');
foreach ($services as $service) {
?>
<label class="checkbox" for="<?php echo $service; ?>">
<input type="checkbox" name="<?php echo $service; ?>" id="<?php echo $service; ?>" value="1"<?php echo $this->conf->sharing($service) ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ($service); ?>
<?php foreach ($this->conf->sharing as $key => $sharing): ?>
<?php $share = $this->conf->shares[$sharing['type']]; ?>
<div class="form-group">
<label class="group-name">
<?php echo Minz_Translate::t ($sharing['type']); ?>
</label>
<?php } ?>
<div class="group-controls">
<input type='hidden' id='share_<?php echo $key;?>_type' name="share[<?php echo $key;?>][type]" value='<?php echo $sharing['type']?>' />
<?php if ($share['form'] === 'advanced'){ ?>
<div class="stick">
<input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
<input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
<a href='#' class='share remove btn btn-attention'><?php echo FreshRSS_Themes::icon('close'); ?></a>
</div>
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="<?php echo $share['help']?>"><?php echo FreshRSS_Themes::icon('help'); ?></a>
<?php } else { ?>
<a href='#' class='share remove btn btn-attention'><?php echo FreshRSS_Themes::icon('close'); ?></a>
<?php } ?>
</div>
</div>
<?php endforeach;?>
<div class="form-group">
<div class="group-controls">
<select>
<?php foreach($this->conf->shares as $key => $params):?>
<option value='<?php echo $key?>' data-form='<?php echo $params['form']?>' data-help='<?php if (!empty($params['help'])) {echo $params['help'];}?>'><?php echo Minz_Translate::t($key) ?></option>
<?php endforeach; ?>
</select>
<a href='#' class='share add btn'><?php echo FreshRSS_Themes::icon('add'); ?></a>
</div>
</div>

View File

@@ -12,10 +12,44 @@
<?php $s = $this->conf->shortcuts; ?>
<form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>">
<legend><?php echo Minz_Translate::t ('shortcuts_management'); ?></legend>
<legend><?php echo Minz_Translate::t ('shortcuts'); ?></legend>
<noscript><p class="alert alert-error"><?php echo Minz_Translate::t ('javascript_for_shortcuts'); ?></p></noscript>
<legend><?php echo Minz_Translate::t ('shortcuts_navigation'); ?></legend>
<div class="form-group">
<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label>
<div class="group-controls">
<input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
<div class="group-controls">
<input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="first_entry"><?php echo Minz_Translate::t ('first_article'); ?></label>
<div class="group-controls">
<input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="last_entry"><?php echo Minz_Translate::t ('last_article'); ?></label>
<div class="group-controls">
<input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" />
</div>
</div>
<div><?php echo Minz_Translate::t ('shortcuts_navigation_help');?></div>
<legend><?php echo Minz_Translate::t ('shortcuts_article_action');?></legend>
<div class="form-group">
<label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></label>
<div class="group-controls">
@@ -39,18 +73,10 @@
</div>
<div class="form-group">
<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label>
<label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label>
<div class="group-controls">
<input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
<?php echo Minz_Translate::t ('shift_for_last'); ?>
</div>
</div>
<div class="form-group">
<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
<div class="group-controls">
<input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
<?php echo Minz_Translate::t ('shift_for_first'); ?>
<input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
<?php echo Minz_Translate::t ('auto_share_help'); ?>
</div>
</div>
@@ -61,6 +87,8 @@
</div>
</div>
<legend><?php echo Minz_Translate::t ('shortcuts_other_action');?></legend>
<div class="form-group">
<label class="group-name" for="load_more_shortcut"><?php echo Minz_Translate::t ('load_more'); ?></label>
<div class="group-controls">
@@ -69,9 +97,9 @@
</div>
<div class="form-group">
<label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label>
<label class="group-name" for="focus_search_shortcut"><?php echo Minz_Translate::t ('focus_search'); ?></label>
<div class="group-controls">
<input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
<input type="text" id="focus_search_shortcut" name="shortcuts[focus_search]" list="keys" value="<?php echo $s['focus_search']; ?>" />
</div>
</div>

View File

@@ -20,16 +20,31 @@
<div class="form-group">
<label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
<div class="group-controls">
<input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" />
<div class="stick">
<input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
</div>
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
</div>
</div>
<?php if (Minz_Configuration::apiEnabled()) { ?>
<div class="form-group">
<label class="group-name" for="apiPasswordPlain"><?php echo Minz_Translate::t('password_api'); ?></label>
<div class="group-controls">
<div class="stick">
<input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
</div>
</div>
</div>
<?php } ?>
<div class="form-group">
<label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
<?php $mail = $this->conf->mail_login; ?>
<div class="group-controls">
<input type="email" id="mail_login" name="mail_login" class="extend" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
<input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
</div>
</div>
@@ -52,7 +67,7 @@
<?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?>
<option selected="selected"></option>
<?php } ?>
<option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', version_compare(PHP_VERSION, '5.3', '<') ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_form'); ?></option>
<option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo Minz_Translate::t('auth_form'); ?></option>
<option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_persona'); ?></option>
<option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
<option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t('auth_none'); ?></option>
@@ -70,18 +85,49 @@
</div>
</div>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="anon_refresh">
<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '',
Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
<?php echo Minz_Translate::t('allow_anonymous_refresh'); ?>
</label>
</div>
</div>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="unsafe_autologin">
<input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo Minz_Configuration::unsafeAutologinEnabled() ? ' checked="checked"' : '',
Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
<?php echo Minz_Translate::t('unsafe_autologin'); ?>
<kbd>p/i/?a=formLogin&amp;u=Alice&amp;p=1234</kbd>
</label>
</div>
</div>
<?php if (Minz_Configuration::canLogIn()) { ?>
<div class="form-group">
<label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label>
<?php $token = $this->conf->token; ?>
<div class="group-controls">
<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"<?php
<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"<?php
echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?>
</div>
</div>
<?php } ?>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="api_enabled">
<input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo Minz_Configuration::apiEnabled() ? ' checked="checked"' : '',
Minz_Configuration::needsLogin() ? '' : ' disabled="disabled"'; ?> />
<?php echo Minz_Translate::t('api_enabled'); ?>
</label>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
@@ -129,14 +175,17 @@
<div class="form-group">
<label class="group-name" for="new_user_name"><?php echo Minz_Translate::t('username'); ?></label>
<div class="group-controls">
<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="new_user_passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
<div class="group-controls">
<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" pattern=".{7,}" />
<div class="stick">
<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" />
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
</div>
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
</div>
</div>
@@ -145,7 +194,7 @@
<label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
<?php $mail = $this->conf->mail_login; ?>
<div class="group-controls">
<input type="email" id="new_user_email" name="new_user_email" class="extend" placeholder="alice@example.net" />
<input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" />
</div>
</div>

View File

@@ -3,7 +3,15 @@
<h1 class="alert-head"><?php echo $this->code; ?></h1>
<p>
<?php echo Minz_Translate::t ('page_not_found'); ?><br />
<?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>
</p>
</div>

91
app/views/feed/add.phtml Normal file
View File

@@ -0,0 +1,91 @@
<?php if ($this->feed) { ?>
<div class="post">
<h1><?php echo Minz_Translate::t ('add_rss_feed'); ?></h1>
<?php if (!$this->load_ok) { ?>
<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t('damn'); ?></span> <?php echo Minz_Translate::t('internal_problem_feed', _url('index', 'logs')); ?></p>
<?php } ?>
<form method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off">
<legend><?php echo Minz_Translate::t('informations'); ?></legend>
<?php if ($this->load_ok) { ?>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t('title'); ?></label>
<div class="group-controls">
<label><?php echo $this->feed->name() ; ?></label>
</div>
</div>
<?php $desc = $this->feed->description(); if ($desc != '') { ?>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t('feed_description'); ?></label>
<div class="group-controls">
<label><?php echo htmlspecialchars($desc, ENT_NOQUOTES, 'UTF-8'); ?></label>
</div>
</div>
<?php } ?>
<div class="form-group">
<label class="group-name"><?php echo Minz_Translate::t('website_url'); ?></label>
<div class="group-controls">
<?php echo $this->feed->website(); ?>
<a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
</div>
</div>
<?php } ?>
<div class="form-group">
<label class="group-name" for="url"><?php echo Minz_Translate::t('feed_url'); ?></label>
<div class="group-controls">
<div class="stick">
<input type="text" name="url_rss" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" />
<a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
</div>
<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo Minz_Translate::t('feed_validator'); ?></a>
</div>
</div>
<div class="form-group">
<label class="group-name" for="category"><?php echo Minz_Translate::t('category'); ?></label>
<div class="group-controls">
<select name="category" id="category">
<?php foreach ($this->categories as $cat) { ?>
<option value="<?php echo $cat->id(); ?>"<?php echo $cat->id() == 1 ? ' selected="selected"' : ''; ?>>
<?php echo $cat->name(); ?>
</option>
<?php } ?>
<option value="nc"><?php echo Minz_Translate::t('new_category'); ?></option>
</select>
<span style="display: none;">
<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t('new_category'); ?>" />
</span>
</div>
</div>
<legend><?php echo Minz_Translate::t('http_authentication'); ?></legend>
<?php $auth = $this->feed->httpAuth(false); ?>
<div class="form-group">
<label class="group-name" for="http_user"><?php echo Minz_Translate::t('http_username'); ?></label>
<div class="group-controls">
<input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
</div>
<label class="group-name" for="http_pass"><?php echo Minz_Translate::t('http_password'); ?></label>
<div class="group-controls">
<input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
</div>
<div class="group-controls">
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('access_protected_feeds'); ?>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
</div>
</div>
</form>
</div>
<?php } ?>

View File

@@ -0,0 +1,47 @@
<?php
$username = Minz_Session::param('currentUser', '_');
$articles = array(
'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type,
'title' => $this->list_title,
'author' => $username,
'items' => array()
);
foreach ($this->entries as $entry) {
if (!isset($this->feed)) {
$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feed ());
} else {
$feed = $this->feed;
}
$articles['items'][] = array(
'id' => $entry->guid(),
'categories' => array_values($entry->tags()),
'title' => $entry->title(),
'author' => $entry->author(),
'published' => $entry->date(true),
'updated' => $entry->date(true),
'alternate' => array(array(
'href' => $entry->link(),
'type' => 'text/html'
)),
'content' => array(
'content' => $entry->content()
),
'origin' => array(
'streamId' => $feed->id(),
'title' => $feed->name(),
'htmlUrl' => $feed->website(),
'feedUrl' => $feed->url()
)
);
}
$options = 0;
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
$options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
}
echo json_encode($articles, $options);
?>

View File

@@ -0,0 +1,28 @@
<?php
$opml_array = array(
'head' => array(
'title' => Minz_Configuration::title(),
'dateCreated' => date('D, d M Y H:i:s')
),
'body' => array()
);
foreach ($this->categories as $key => $cat) {
$opml_array['body'][$key] = array(
'text' => $cat['name'],
'@outlines' => array()
);
foreach ($cat['feeds'] as $feed) {
$opml_array['body'][$key]['@outlines'][] = array(
'text' => htmlspecialchars_decode($feed->name()),
'type' => 'rss',
'xmlUrl' => $feed->url(),
'htmlUrl' => $feed->website(),
'description' => $feed->description()
);
}
}
echo libopml_render($opml_array);

View File

@@ -5,12 +5,14 @@ echo '"use strict";', "\n";
$mark = $this->conf->mark_when;
echo 'var ',
'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true',
',display_order="', Minz_Request::param('order', $this->conf->sort_order), '"',
',auto_mark_article=', $mark['article'] ? 'true' : 'false',
',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';
',does_lazyload=', $this->conf->lazyload ? 'true' : 'false',
',sticky_post=', $this->conf->sticky_post ? 'true' : 'false';
$s = $this->conf->shortcuts;
echo ',shortcuts={',
@@ -19,9 +21,12 @@ echo ',shortcuts={',
'go_website:"', $s['go_website'], '",',
'prev_entry:"', $s['prev_entry'], '",',
'next_entry:"', $s['next_entry'], '",',
'first_entry:"', $s['first_entry'], '",',
'last_entry:"', $s['last_entry'], '",',
'collapse_entry:"', $s['collapse_entry'], '",',
'load_more:"', $s['load_more'], '",',
'auto_share:"', $s['auto_share'], '"',
'auto_share:"', $s['auto_share'], '",',
'focus_search:"', $s['focus_search'], '"',
"},\n";
if (Minz_Request::param ('output') === 'global') {
@@ -30,7 +35,13 @@ if (Minz_Request::param ('output') === 'global') {
$authType = Minz_Configuration::authType();
if ($authType === 'persona') {
echo 'current_user_mail="' . Minz_Session::param ('mail', '') . '",';
// If user is disconnected, current_user_mail MUST be null
$mail = Minz_Session::param ('mail', false);
if ($mail) {
echo 'current_user_mail="' . $mail . '",';
} else {
echo 'current_user_mail=null,';
}
}
echo 'authType="', $authType, '",',

View File

@@ -8,19 +8,10 @@ if (!empty($this->entries)) {
$display_yesterday = true;
$display_others = true;
if ($this->loginOk) {
$shaarli = $this->conf->sharing ('shaarli');
$wallabag = $this->conf->sharing ('wallabag');
$diaspora = $this->conf->sharing ('diaspora');
$sharing = $this->conf->sharing;
} else {
$shaarli = '';
$wallabag = '';
$diaspora = '';
$sharing = array();
}
$twitter = $this->conf->sharing ('twitter');
$google_plus = $this->conf->sharing ('g+');
$facebook = $this->conf->sharing ('facebook');
$email = $this->conf->sharing ('email');
$print = $this->conf->sharing ('print');
$hidePosts = !$this->conf->display_posts;
$lazyload = $this->conf->lazyload;
$topline_read = $this->conf->topline_read;
@@ -29,17 +20,17 @@ if (!empty($this->entries)) {
$topline_link = $this->conf->topline_link;
$bottomline_read = $this->conf->bottomline_read;
$bottomline_favorite = $this->conf->bottomline_favorite;
$bottomline_sharing = $this->conf->bottomline_sharing && (
$shaarli || $wallabag || $diaspora || $twitter ||
$google_plus || $facebook || $email || $print);
$bottomline_sharing = $this->conf->bottomline_sharing && (count($sharing));
$bottomline_tags = $this->conf->bottomline_tags;
$bottomline_date = $this->conf->bottomline_date;
$bottomline_link = $this->conf->bottomline_link;
$content_width = $this->conf->content_width;
?>
<div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"><?php
?><div id="new-article">
<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t ('new_article'); ?></a>
<a href="<?php echo Minz_Url::display ($this->url); ?>"><?php echo Minz_Translate::t ('new_article'); ?></a>
</div><?php
foreach ($this->entries as $item) {
if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) {
@@ -98,8 +89,8 @@ if (!empty($this->entries)) {
</ul>
<div class="flux_content">
<div class="content">
<h1 class="title"><?php echo $item->title (); ?></h1>
<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>' : '';
@@ -146,55 +137,13 @@ if (!empty($this->entries)) {
<ul class="dropdown-menu">
<li class="dropdown-close"><a href="#close"></a></li>
<?php if ($shaarli) { ?>
<li class="item">
<a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&amp;title=' . $title . '&amp;source=FreshRSS'; ?>">
<?php echo Minz_Translate::t ('shaarli'); ?>
</a>
</li>
<?php } if ($wallabag) { ?>
<li class="item">
<a target="_blank" href="<?php echo $wallabag . '?action=add&amp;url=' . base64_encode (urldecode($link)); ?>">
<?php echo Minz_Translate::t ('wallabag'); ?>
</a>
</li>
<?php } if ($diaspora) { ?>
<li class="item">
<a target="_blank" href="<?php echo $diaspora . '/bookmarklet?url=' . $link . '&amp;title=' . $title; ?>">
<?php echo Minz_Translate::t ('diaspora'); ?>
</a>
</li>
<?php } if ($twitter) { ?>
<li class="item">
<a target="_blank" href="https://twitter.com/share?url=<?php echo $link; ?>&amp;text=<?php echo $title; ?>">
<?php echo Minz_Translate::t ('twitter'); ?>
</a>
</li>
<?php } if ($google_plus) { ?>
<li class="item">
<a target="_blank" href="https://plus.google.com/share?url=<?php echo $link; ?>">
<?php echo Minz_Translate::t ('g+'); ?>
</a>
</li>
<?php } if ($facebook) { ?>
<li class="item">
<a target="_blank" href="https://www.facebook.com/sharer.php?u=<?php echo $link; ?>&amp;t=<?php echo $title; ?>">
<?php echo Minz_Translate::t ('facebook'); ?>
</a>
</li>
<?php } if ($email) { ?>
<li class="item">
<a href="mailto:?subject=<?php echo urldecode($title); ?>&amp;body=<?php echo $link; ?>">
<?php echo Minz_Translate::t ('by_email'); ?>
</a>
</li>
<?php } if ($print) { ?>
<li class="item">
<a href="#" class="print-article">
<?php echo Minz_Translate::t ('print'); ?>
</a>
</li>
<?php } ?>
<?php foreach ($sharing as $share) :?>
<li class="item share">
<a target="_blank" href="<?php echo FreshRSS_Share::generateUrl($this->conf->shares, $share, $item->link(), $item->title() . ' . ' . $feed->name())?>">
<?php echo Minz_Translate::t ($share['name']);?>
</a>
</li>
<?php endforeach;?>
</ul>
</div>
<?php } ?>

View File

@@ -3,6 +3,7 @@ $this->partial ('nav_menu');
if (!empty($this->entries)) {
$lazyload = $this->conf->lazyload;
$content_width = $this->conf->content_width;
?>
<div id="stream" class="reader">
@@ -10,7 +11,7 @@ if (!empty($this->entries)) {
<div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
<div class="flux_content">
<div class="content">
<div class="content <?php echo $content_width; ?>">
<?php
$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);

View File

@@ -6,7 +6,7 @@
<description><?php echo Minz_Translate::t ('rss_feeds_of', $this->rss_title); ?></description>
<pubDate><?php echo date('D, d M Y H:i:s O'); ?></pubDate>
<lastBuildDate><?php echo gmdate('D, d M Y H:i:s'); ?> GMT</lastBuildDate>
<atom:link href="<?php echo Minz_Url::display ($this->rss_url, 'html', true); ?>" rel="self" type="application/rss+xml" />
<atom:link href="<?php echo Minz_Url::display ($this->url, 'html', true); ?>" rel="self" type="application/rss+xml" />
<?php
foreach ($this->entries as $item) {
?>

View File

@@ -0,0 +1,52 @@
<?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>
<form method="post" action="<?php echo _url('importExport', 'import'); ?>" enctype="multipart/form-data">
<legend><?php echo Minz_Translate::t ('import'); ?></legend>
<div class="form-group">
<label class="group-name" for="file"><?php echo Minz_Translate::t ('file_to_import'); ?></label>
<div class="group-controls">
<input type="file" name="file" id="file" />
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::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>
<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'); ?>
</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'); ?>
</label>
<select name="export_feeds[]" size="<?php echo min(10, count($this->feeds)); ?>" multiple="multiple">
<?php foreach ($this->feeds as $feed) { ?>
<option value="<?php echo $feed->id(); ?>"><?php echo $feed->name(); ?></option>
<?php } ?>
</select>
</div>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('export'); ?></button>
</div>
</div>
</form>
<?php } ?>
</div>

View File

@@ -1,34 +1,32 @@
<div class="prompt">
<?php
if (Minz_Configuration::canLogIn()) {
?><h1><?php echo Minz_Translate::t('login'); ?></h1><?php
<h1><?php echo Minz_Translate::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>
<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>
<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>
</div>
<div>
<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
</div>
</form><?php
break;
case 'form':
?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
<p>
<label for="username"><?php echo Minz_Translate::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" />
</p><p>
<label for="passwordPlain"><?php echo Minz_Translate::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>
</p><p>
<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
</p>
</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>
</p><?php
break;
} ?>
case 'persona':
?><p><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t('login'); ?></a></p><?php
break;
}
} else {
?><h1>FreshRSS</h1>
<p><?php echo Minz_Translate::t('forbidden_access', Minz_Configuration::authType()); ?></p><?php
}
?>
<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 Minz_Translate::t('about_freshrss'); ?></a></p>
</div>

View File

@@ -5,26 +5,21 @@ $output = Minz_Request::param ('output', 'normal');
if ($this->loginOk || Minz_Configuration::allowAnonymous()) {
if ($output === 'normal') {
$this->renderHelper ('view/normal_view');
} elseif ($output === 'rss') {
$this->renderHelper ('view/rss_view');
} elseif ($output === 'reader') {
$this->renderHelper ('view/reader_view');
} elseif ($output === 'global') {
$this->renderHelper ('view/global_view');
} elseif ($output === 'rss') {
$this->renderHelper ('view/rss_view');
} else {
Minz_Request::_param ('output', 'normal');
$output = 'normal';
$this->renderHelper ('view/normal_view');
}
} elseif ($output === 'rss') {
$token = $this->conf->token;
$token_param = Minz_Request::param ('token', '');
$token_is_ok = ($token != '' && $token == $token_param);
if ($token_is_ok) {
$this->renderHelper ('view/rss_view');
} else {
Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true);
}
// token has already been checked in the controller so we can show the view
$this->renderHelper ('view/rss_view');
} else {
Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true);
// Normally, it should not happen, but log it anyway
Minz_Log::record ('Something is wrong in ' . __FILE__ . ' line ' . __LINE__, Minz_Log::ERROR);
}

View File

@@ -1,14 +1,17 @@
"use strict";
var feeds = [];
<?php foreach ($this->feeds as $feed) { ?>
feeds.push("<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'actualize', 'params' => array ('id' => $feed->id (), 'ajax' => '1')), 'php'); ?>");
<?php } ?>
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";
}
?>],
feed_processed = 0,
feed_count = feeds.length;
function initProgressBar(init) {
if (init) {
$("body").after("\<div id=\"actualizeProgress\" class=\"actualizeProgress\">\
<?php echo Minz_Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feeds.length + "</span><br />\
<progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feeds.length + "\"></progress>\
$("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\
<?php echo Minz_Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
<progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feed_count + "\"></progress>\
</div>");
} else {
window.location.reload();
@@ -16,27 +19,37 @@ function initProgressBar(init) {
}
function updateProgressBar(i) {
$("#actualizeProgressBar").val(i);
$("#actualizeProgress .progress").html(i + " / " + feeds.length);
$("#actualizeProgress .progress").html(i + " / " + feed_count);
}
function updateFeeds() {
if (feeds.length === 0) {
if (feed_count === 0) {
openNotification("<?php echo Minz_Translate::t ('no_feed_to_refresh'); ?>", "good");
return;
}
initProgressBar(true);
var i = 0;
for (var f in feeds) {
$.ajax({
type: 'POST',
url: feeds[f],
}).done(function (data) {
i++;
updateProgressBar(i);
if (i === feeds.length) {
initProgressBar(false);
}
});
for (var i = 0; i < 10; i++) {
updateFeed();
}
}
function updateFeed() {
var feed = feeds.pop();
if (feed == undefined) {
return;
}
$.ajax({
type: 'POST',
url: feed,
}).complete(function (data) {
feed_processed++;
updateProgressBar(feed_processed);
if (feed_processed === feed_count) {
initProgressBar(false);
} else {
updateFeed();
}
});
}

View File

@@ -1,7 +1,10 @@
<?php
define('FRESHRSS_VERSION', '0.7');
define('FRESHRSS_VERSION', '0.8-dev');
define('FRESHRSS_WEBSITE', 'http://freshrss.org');
// PHP text output compression http://php.net/ob_gzhandler (better to do it at Web server level)
define('PHP_COMPRESSION', false);
// Constantes de chemins
define('FRESHRSS_PATH', dirname(__FILE__));
@@ -15,3 +18,5 @@ define('FRESHRSS_PATH', dirname(__FILE__));
define('LIB_PATH', FRESHRSS_PATH . '/lib');
define('APP_PATH', FRESHRSS_PATH . '/app');
define('TMP_PATH', sys_get_temp_dir());

75
data/shares.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
/*
* This is a configuration file. You shouldn't modify it unless you know what
* you are doing. If you want to add a share type, this is where you need to do
* it.
*
* For each share there is different configuration options. Here is the description
* of those options:
* - url is a mandatory option. It is a string representing the share URL. It
* supports 3 different placeholders for custom data. The ~URL~ placeholder
* represents the URL of the system used to share, it is configured by the
* user. The ~LINK~ placeholder represents the link of the shared article.
* The ~TITLE~ placeholder represents the title of the shared article.
* - transform is an array of transformation to apply on links and titles
* - help is a URL to a help page
*/
return array(
'shaarli' => array(
'url' => '~URL~?post=~LINK~&amp;title=~TITLE~&amp;source=FreshRSS',
'transform' => array('urlencode'),
'help' => 'http://sebsauvage.net/wiki/doku.php?id=php:shaarli',
'form' => 'advanced',
),
'blogotext' => array(
'url' => '~URL~/admin/links.php?url=~LINK~',
'transform' => array(),
'help' => 'http://lehollandaisvolant.net/blogotext/fr/',
'form' => 'advanced',
),
'wallabag' => array(
'url' => '~URL~?action=add&amp;url=~LINK~',
'transform' => array(
'link' => array('base64_encode'),
'title' => array(),
),
'help' => 'http://www.wallabag.org/',
'form' => 'advanced',
),
'diaspora' => array(
'url' => '~URL~/bookmarklet?url=~LINK~&amp;title=~TITLE~',
'transform' => array('urlencode'),
'help' => 'https://diasporafoundation.org/',
'form' => 'advanced',
),
'twitter' => array(
'url' => 'https://twitter.com/share?url=~LINK~&amp;text=~TITLE~',
'transform' => array('urlencode'),
'form' => 'simple',
),
'g+' => array(
'url' => 'https://plus.google.com/share?url=~LINK~',
'transform' => array('urlencode'),
'form' => 'simple',
),
'facebook' => array(
'url' => 'https://www.facebook.com/sharer.php?u=~LINK~&amp;t=~TITLE~',
'transform' => array('urlencode'),
'form' => 'simple',
),
'email' => array(
'url' => 'mailto:?subject=~TITLE~&amp;body=~LINK~',
'transform' => array(
'link' => array('urlencode'),
'title' => array(),
),
'form' => 'simple',
),
'print' => array(
'url' => '#',
'transform' => array(),
'form' => 'simple',
),
);

View File

@@ -8,16 +8,12 @@
* La classe ActionController représente le contrôleur de l'application
*/
class Minz_ActionController {
protected $router;
protected $view;
/**
* Constructeur
* @param $controller nom du controller
* @param $action nom de l'action à lancer
*/
public function __construct ($router) {
$this->router = $router;
public function __construct () {
$this->view = new Minz_View ();
$this->view->attributeParams ();
}

View File

@@ -1,116 +0,0 @@
<?php
/**
* MINZ - Copyright 2011 Marien Fressinaud
* Sous licence AGPL3 <http://www.gnu.org/licenses/>
*/
/**
* La classe Cache permet de gérer facilement les pages en cache
*/
class Minz_Cache {
/**
* $expire timestamp auquel expire le cache de $url
*/
private $expire = 0;
/**
* $file est le nom du fichier de cache
*/
private $file = '';
/**
* $enabled permet de déterminer si le cache est activé
*/
private static $enabled = true;
/**
* Constructeur
*/
public function __construct () {
$this->_fileName ();
$this->_expire ();
}
/**
* Setteurs
*/
public function _fileName () {
$file = md5 (Minz_Request::getURI ());
$this->file = CACHE_PATH . '/'.$file;
}
public function _expire () {
if ($this->exist ()) {
$this->expire = filemtime ($this->file)
+ Minz_Configuration::delayCache ();
}
}
/**
* Permet de savoir si le cache est activé
* @return true si activé, false sinon
*/
public static function isEnabled () {
return Minz_Configuration::cacheEnabled () && self::$enabled;
}
/**
* Active / désactive le cache
*/
public static function switchOn () {
self::$enabled = true;
}
public static function switchOff () {
self::$enabled = false;
}
/**
* Détermine si le cache de $url a expiré ou non
* @return true si il a expiré, false sinon
*/
public function expired () {
return time () > $this->expire;
}
/**
* Affiche le contenu du cache
* @print le code html du cache
*/
public function render () {
if ($this->exist ()) {
include ($this->file);
}
}
/**
* Enregistre $html en cache
* @param $html le html à mettre en cache
*/
public function cache ($html) {
file_put_contents ($this->file, $html);
}
/**
* Permet de savoir si le cache existe
* @return true si il existe, false sinon
*/
public function exist () {
return file_exists ($this->file);
}
/**
* Nettoie le cache en supprimant tous les fichiers
*/
public static function clean () {
$files = opendir (CACHE_PATH);
while ($fic = readdir ($files)) {
if ($fic != '.' && $fic != '..') {
unlink (CACHE_PATH.'/'.$fic);
}
}
closedir ($files);
}
}

View File

@@ -30,12 +30,9 @@ class Minz_Configuration {
* définition des variables de configuration
* $salt une chaîne de caractères aléatoires (obligatoire)
* $environment gère le niveau d'affichage pour log et erreurs
* $use_url_rewriting indique si on utilise l'url_rewriting
* $base_url le chemin de base pour accéder à l'application
* $title le nom de l'application
* $language la langue par défaut de l'application
* $cacheEnabled permet de savoir si le cache doit être activé
* $delayCache la limite de cache
* $db paramètres pour la base de données (tableau)
* - host le serveur de la base
* - user nom d'utilisateur
@@ -45,14 +42,14 @@ class Minz_Configuration {
private static $salt = '';
private static $environment = Minz_Configuration::PRODUCTION;
private static $base_url = '';
private static $use_url_rewriting = false;
private static $title = '';
private static $language = 'en';
private static $cache_enabled = false;
private static $delay_cache = 3600;
private static $default_user = '';
private static $allow_anonymous = false;
private static $allow_anonymous_refresh = false;
private static $auth_type = 'none';
private static $api_enabled = false;
private static $unsafe_autologin_enabled = false;
private static $db = array (
'type' => 'mysql',
@@ -91,21 +88,12 @@ class Minz_Configuration {
public static function baseUrl () {
return self::$base_url;
}
public static function useUrlRewriting () {
return self::$use_url_rewriting;
}
public static function title () {
return self::$title;
}
public static function language () {
return self::$language;
}
public static function cacheEnabled () {
return self::$cache_enabled;
}
public static function delayCache () {
return self::$delay_cache;
}
public static function dataBase () {
return self::$db;
}
@@ -118,6 +106,9 @@ class Minz_Configuration {
public static function allowAnonymous() {
return self::$allow_anonymous;
}
public static function allowAnonymousRefresh() {
return self::$allow_anonymous_refresh;
}
public static function authType() {
return self::$auth_type;
}
@@ -127,10 +118,19 @@ class Minz_Configuration {
public static function canLogIn() {
return self::$auth_type === 'form' || self::$auth_type === 'persona';
}
public static function apiEnabled() {
return self::$api_enabled;
}
public static function unsafeAutologinEnabled() {
return self::$unsafe_autologin_enabled;
}
public static function _allowAnonymous($allow = false) {
self::$allow_anonymous = ((bool)$allow) && self::canLogIn();
}
public static function _allowAnonymousRefresh($allow = false) {
self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous();
}
public static function _authType($value) {
$value = strtolower($value);
switch ($value) {
@@ -144,6 +144,13 @@ class Minz_Configuration {
self::_allowAnonymous(self::$allow_anonymous);
}
public static function _enableApi($value = false) {
self::$api_enabled = (bool)$value;
}
public static function _enableAutologin($value = false) {
self::$unsafe_autologin_enabled = (bool)$value;
}
/**
* Initialise les variables de configuration
* @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas
@@ -164,13 +171,15 @@ class Minz_Configuration {
$ini_array = array(
'general' => array(
'environment' => self::environment(true),
'use_url_rewriting' => self::$use_url_rewriting,
'salt' => self::$salt,
'base_url' => self::$base_url,
'title' => self::$title,
'default_user' => self::$default_user,
'allow_anonymous' => self::$allow_anonymous,
'allow_anonymous_refresh' => self::$allow_anonymous_refresh,
'auth_type' => self::$auth_type,
'api_enabled' => self::$api_enabled,
'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled,
),
'db' => self::$db,
);
@@ -247,9 +256,6 @@ class Minz_Configuration {
if (isset ($general['base_url'])) {
self::$base_url = $general['base_url'];
}
if (isset ($general['use_url_rewriting'])) {
self::$use_url_rewriting = $general['use_url_rewriting'];
}
if (isset ($general['title'])) {
self::$title = $general['title'];
@@ -257,18 +263,6 @@ class Minz_Configuration {
if (isset ($general['language'])) {
self::$language = $general['language'];
}
if (isset ($general['cache_enabled'])) {
self::$cache_enabled = $general['cache_enabled'];
if (CACHE_PATH === false && self::$cache_enabled) {
throw new FileNotExistException (
'CACHE_PATH',
Minz_Exception::ERROR
);
}
}
if (isset ($general['delay_cache'])) {
self::$delay_cache = inval($general['delay_cache']);
}
if (isset ($general['default_user'])) {
self::$default_user = $general['default_user'];
}
@@ -276,7 +270,28 @@ class Minz_Configuration {
self::_authType($general['auth_type']);
}
if (isset ($general['allow_anonymous'])) {
self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no');
self::$allow_anonymous = (
((bool)($general['allow_anonymous'])) &&
($general['allow_anonymous'] !== 'no')
);
}
if (isset ($general['allow_anonymous_refresh'])) {
self::$allow_anonymous_refresh = (
((bool)($general['allow_anonymous_refresh'])) &&
($general['allow_anonymous_refresh'] !== 'no')
);
}
if (isset ($general['api_enabled'])) {
self::$api_enabled = (
((bool)($general['api_enabled'])) &&
($general['api_enabled'] !== 'no')
);
}
if (isset ($general['unsafe_autologin_enabled'])) {
self::$unsafe_autologin_enabled = (
((bool)($general['unsafe_autologin_enabled'])) &&
($general['unsafe_autologin_enabled'] !== 'no')
);
}
// Base de données

View File

@@ -14,85 +14,55 @@ class Minz_Dispatcher {
/* singleton */
private static $instance = null;
private static $needsReset;
private $router;
private $controller;
/**
* Récupère l'instance du Dispatcher
*/
public static function getInstance ($router) {
public static function getInstance () {
if (self::$instance === null) {
self::$instance = new Minz_Dispatcher ($router);
self::$instance = new Minz_Dispatcher ();
}
return self::$instance;
}
/**
* Constructeur
*/
private function __construct ($router) {
$this->router = $router;
}
/**
* Lance le controller indiqué dans Request
* Remplit le body de Response à partir de la Vue
* @exception Minz_Exception
*/
public function run ($ob = true) {
$cache = new Minz_Cache();
// Le ob_start est dupliqué : sans ça il y a un bug sous Firefox
// ici on l'appelle avec 'ob_gzhandler', après sans.
// Vraisemblablement la compression fonctionne mais c'est sale
// J'ignore les effets de bord :(
if ($ob) {
ob_start ('ob_gzhandler');
}
public function run () {
do {
self::$needsReset = false;
if (Minz_Cache::isEnabled () && !$cache->expired ()) {
if ($ob) {
ob_start ();
}
$cache->render ();
if ($ob) {
$text = ob_get_clean();
}
} else {
$text = ''; //TODO: Clean this code
while (Minz_Request::$reseted) {
Minz_Request::$reseted = false;
try {
$this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller');
$this->controller->init ();
$this->controller->firstAction ();
try {
$this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller');
$this->controller->init ();
$this->controller->firstAction ();
if (!self::$needsReset) {
$this->launchAction (
Minz_Request::actionName ()
. 'Action'
);
$this->controller->lastAction ();
if (!Minz_Request::$reseted) {
if ($ob) {
ob_start ();
}
$this->controller->view ()->build ();
if ($ob) {
$text = ob_get_clean();
}
}
} catch (Minz_Exception $e) {
throw $e;
}
}
$this->controller->lastAction ();
if (Minz_Cache::isEnabled ()) {
$cache->cache ($text);
if (!self::$needsReset) {
$this->controller->view ()->build ();
}
} catch (Minz_Exception $e) {
throw $e;
}
}
} while (self::$needsReset);
}
Minz_Response::setBody ($text);
/**
* Informe le contrôleur qu'il doit recommancer car la requête a été modifiée
*/
public static function reset() {
self::$needsReset = true;
}
/**
@@ -112,7 +82,7 @@ class Minz_Dispatcher {
Minz_Exception::ERROR
);
}
$this->controller = new $controller_name ($this->router);
$this->controller = new $controller_name ();
if (! ($this->controller instanceof Minz_ActionController)) {
throw new Minz_ControllerNotActionControllerException (
@@ -129,21 +99,19 @@ class Minz_Dispatcher {
* le controller
*/
private function launchAction ($action_name) {
if (!Minz_Request::$reseted) {
if (!is_callable (array (
$this->controller,
$action_name
))) {
throw new Minz_ActionException (
get_class ($this->controller),
$action_name,
Minz_Exception::ERROR
);
}
call_user_func (array (
$this->controller,
$action_name
));
if (!is_callable (array (
$this->controller,
$action_name
))) {
throw new Minz_ActionException (
get_class ($this->controller),
$action_name,
Minz_Exception::ERROR
);
}
call_user_func (array (
$this->controller,
$action_name
));
}
}

View File

@@ -23,13 +23,32 @@ class Minz_Error {
$logs = self::processLogs ($logs);
$error_filename = APP_PATH . '/Controllers/errorController.php';
switch ($code) {
case 200 :
header('HTTP/1.1 200 OK');
break;
case 403 :
header('HTTP/1.1 403 Forbidden');
break;
case 404 :
header('HTTP/1.1 404 Not Found');
break;
case 500 :
header('HTTP/1.1 500 Internal Server Error');
break;
case 503 :
header('HTTP/1.1 503 Service Unavailable');
break;
default :
header('HTTP/1.1 500 Internal Server Error');
}
if (file_exists ($error_filename)) {
$params = array (
'code' => $code,
'logs' => $logs
);
Minz_Response::setHeader ($code);
if ($redirect) {
Minz_Request::forward (array (
'c' => 'error'
@@ -41,19 +60,16 @@ class Minz_Error {
), false);
}
} else {
$text = '<h1>An error occured</h1>'."\n";
echo '<h1>An error occured</h1>' . "\n";
if (!empty ($logs)) {
$text .= '<ul>'."\n";
echo '<ul>' . "\n";
foreach ($logs as $log) {
$text .= '<li>' . $log . '</li>'."\n";
echo '<li>' . $log . '</li>' . "\n";
}
$text .= '</ul>'."\n";
echo '</ul>' . "\n";
}
Minz_Response::setHeader ($code);
Minz_Response::setBody ($text);
Minz_Response::send ();
exit ();
}
}

View File

@@ -24,13 +24,10 @@
*/
class Minz_FrontController {
protected $dispatcher;
protected $router;
private $useOb = true;
/**
* Constructeur
* Initialise le router et le dispatcher
* Initialise le dispatcher, met à jour la Request
*/
public function __construct () {
if (LOG_PATH === false) {
@@ -42,29 +39,50 @@ class Minz_FrontController {
Minz_Request::init ();
$this->router = new Minz_Router ();
$this->router->init ();
} catch (Minz_RouteNotFoundException $e) {
Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
Minz_Error::error (
404,
array ('error' => array ($e->getMessage ()))
$url = $this->buildUrl();
$url['params'] = array_merge (
$url['params'],
Minz_Request::fetchPOST ()
);
Minz_Request::forward ($url);
} catch (Minz_Exception $e) {
Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
$this->killApp ($e->getMessage ());
}
$this->dispatcher = Minz_Dispatcher::getInstance ($this->router);
$this->dispatcher = Minz_Dispatcher::getInstance();
}
/**
* Démarre l'application (lance le dispatcher et renvoie la réponse
* Retourne un tableau représentant l'url passée par la barre d'adresses
* @return tableau représentant l'url
*/
private function buildUrl() {
$url = array ();
$url['c'] = Minz_Request::fetchGET (
'c',
Minz_Request::defaultControllerName ()
);
$url['a'] = Minz_Request::fetchGET (
'a',
Minz_Request::defaultActionName ()
);
$url['params'] = Minz_Request::fetchGET ();
// post-traitement
unset ($url['params']['c']);
unset ($url['params']['a']);
return $url;
}
/**
* Démarre l'application (lance le dispatcher et renvoie la réponse)
*/
public function run () {
try {
$this->dispatcher->run ($this->useOb);
Minz_Response::send ();
$this->dispatcher->run();
} catch (Minz_Exception $e) {
try {
Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
@@ -96,15 +114,4 @@ class Minz_FrontController {
}
exit ('### Application problem ###<br />'."\n".$txt);
}
public function useOb() {
return $this->useOb;
}
/**
* Use ob_start('ob_gzhandler') or not.
*/
public function _useOb($ob) {
return $this->useOb = (bool)$ob;
}
}

View File

@@ -80,4 +80,21 @@ class Minz_Log {
self::record($msg_get, Minz_Log::DEBUG, $file_name);
self::record($msg_post, Minz_Log::DEBUG, $file_name);
}
/**
* Some helpers to Minz_Log::record() method
* Parameters are the same of those of the record() method.
*/
public static function debug($msg, $file_name = null) {
self::record($msg, Minz_Log::DEBUG, $file_name);
}
public static function notice($msg, $file_name = null) {
self::record($msg, Minz_Log::NOTICE, $file_name);
}
public static function warning($msg, $file_name = null) {
self::record($msg, Minz_Log::WARNING, $file_name);
}
public static function error($msg, $file_name = null) {
self::record($msg, Minz_Log::ERROR, $file_name);
}
}

View File

@@ -15,8 +15,6 @@ class Minz_Request {
private static $default_controller_name = 'index';
private static $default_action_name = 'index';
public static $reseted = true;
/**
* Getteurs
*/
@@ -137,14 +135,13 @@ class Minz_Request {
header ('Location: ' . Minz_Url::display ($url, 'php'));
exit ();
} else {
self::$reseted = true;
self::_controllerName ($url['c']);
self::_actionName ($url['a']);
self::_params (array_merge (
self::$params,
$url['params']
));
Minz_Dispatcher::reset();
}
}
@@ -199,6 +196,6 @@ class Minz_Request {
}
public static function isPost () {
return !empty ($_POST) || !empty ($_FILES);
return $_SERVER['REQUEST_METHOD'] === 'POST';
}
}

View File

@@ -1,60 +0,0 @@
<?php
/**
* MINZ - Copyright 2011 Marien Fressinaud
* Sous licence AGPL3 <http://www.gnu.org/licenses/>
*/
/**
* Response représente la requête http renvoyée à l'utilisateur
*/
class Minz_Response {
private static $header = 'HTTP/1.0 200 OK';
private static $body = '';
/**
* Mets à jour le body de la Response
* @param $text le texte à incorporer dans le body
*/
public static function setBody ($text) {
self::$body = $text;
}
/**
* Mets à jour le header de la Response
* @param $code le code HTTP, valeurs possibles
* - 200 (OK)
* - 403 (Forbidden)
* - 404 (Forbidden)
* - 500 (Forbidden) -> par défaut si $code erroné
* - 503 (Forbidden)
*/
public static function setHeader ($code) {
switch ($code) {
case 200 :
self::$header = 'HTTP/1.0 200 OK';
break;
case 403 :
self::$header = 'HTTP/1.0 403 Forbidden';
break;
case 404 :
self::$header = 'HTTP/1.0 404 Not Found';
break;
case 500 :
self::$header = 'HTTP/1.0 500 Internal Server Error';
break;
case 503 :
self::$header = 'HTTP/1.0 503 Service Unavailable';
break;
default :
self::$header = 'HTTP/1.0 500 Internal Server Error';
}
}
/**
* Envoie la Response à l'utilisateur
*/
public static function send () {
header (self::$header);
echo self::$body;
}
}

View File

@@ -1,16 +0,0 @@
<?php
class Minz_RouteNotFoundException extends Minz_Exception {
private $route;
public function __construct ($route, $code = self::ERROR) {
$this->route = $route;
$message = 'Route `' . $route . '` not found';
parent::__construct ($message, $code);
}
public function route () {
return $this->route;
}
}

View File

@@ -1,209 +0,0 @@
<?php
/**
* MINZ - Copyright 2011 Marien Fressinaud
* Sous licence AGPL3 <http://www.gnu.org/licenses/>
*/
/**
* La classe Router gère le routage de l'application
* Les routes sont définies dans APP_PATH.'/configuration/routes.php'
*/
class Minz_Router {
const ROUTES_PATH_NAME = '/configuration/routes.php';
private $routes = array ();
/**
* Constructeur
* @exception FileNotExistException si ROUTES_PATH_NAME n'existe pas
* et que l'on utilise l'url rewriting
*/
public function __construct () {
if (Minz_Configuration::useUrlRewriting ()) {
if (file_exists (APP_PATH . self::ROUTES_PATH_NAME)) {
$routes = include (
APP_PATH . self::ROUTES_PATH_NAME
);
if (!is_array ($routes)) {
$routes = array ();
}
$this->routes = array_map (
array ('Url', 'checkUrl'),
$routes
);
} else {
throw new Minz_FileNotExistException (
self::ROUTES_PATH_NAME,
Minz_Exception::ERROR
);
}
}
}
/**
* Initialise le Router en déterminant le couple Controller / Action
* Mets à jour la Request
* @exception RouteNotFoundException si l'uri n'est pas présente dans
* > la table de routage
*/
public function init () {
$url = array ();
if (Minz_Configuration::useUrlRewriting ()) {
try {
$url = $this->buildWithRewriting ();
} catch (Minz_RouteNotFoundException $e) {
throw $e;
}
} else {
$url = $this->buildWithoutRewriting ();
}
$url['params'] = array_merge (
$url['params'],
Minz_Request::fetchPOST ()
);
Minz_Request::forward ($url);
}
/**
* Retourne un tableau représentant l'url passée par la barre d'adresses
* Ne se base PAS sur la table de routage
* @return tableau représentant l'url
*/
public function buildWithoutRewriting () {
$url = array ();
$url['c'] = Minz_Request::fetchGET (
'c',
Minz_Request::defaultControllerName ()
);
$url['a'] = Minz_Request::fetchGET (
'a',
Minz_Request::defaultActionName ()
);
$url['params'] = Minz_Request::fetchGET ();
// post-traitement
unset ($url['params']['c']);
unset ($url['params']['a']);
return $url;
}
/**
* Retourne un tableau représentant l'url passée par la barre d'adresses
* Se base sur la table de routage
* @return tableau représentant l'url
* @exception RouteNotFoundException si l'uri n'est pas présente dans
* > la table de routage
*/
public function buildWithRewriting () {
$url = array ();
$uri = Minz_Request::getURI ();
$find = false;
foreach ($this->routes as $route) {
$regex = '*^' . $route['route'] . '$*';
if (preg_match ($regex, $uri, $matches)) {
$url['c'] = $route['controller'];
$url['a'] = $route['action'];
$url['params'] = $this->getParams (
$route['params'],
$matches
);
$find = true;
break;
}
}
if (!$find && $uri != '/') {
throw new Minz_RouteNotFoundException (
$uri,
Minz_Exception::ERROR
);
}
// post-traitement
$url = Minz_Url::checkUrl ($url);
return $url;
}
/**
* Retourne l'uri d'une url en se basant sur la table de routage
* @param l'url sous forme de tableau
* @return l'uri formatée (string) selon une route trouvée
*/
public function printUriRewrited ($url) {
$route = $this->searchRoute ($url);
if ($route !== false) {
return $this->replaceParams ($route, $url['params']);
}
return '';
}
/**
* Recherche la route correspondante à une url
* @param l'url sous forme de tableau
* @return la route telle que spécifiée dans la table de routage,
* false si pas trouvée
*/
public function searchRoute ($url) {
foreach ($this->routes as $route) {
if ($route['controller'] == $url['c']
&& $route['action'] == $url['a']) {
// calcule la différence des tableaux de params
$params = array_flip ($route['params']);
$difference_params = array_diff_key (
$params,
$url['params']
);
// vérifie que pas de différence
// et le cas où $params est vide et pas $url['params']
if (empty ($difference_params)
&& (!empty ($params) || empty ($url['params']))) {
return $route;
}
}
}
return false;
}
/**
* Récupère un tableau dont
* - les clés sont définies dans $params_route
* - les valeurs sont situées dans $matches
* Le tableau $matches est décalé de +1 par rapport à $params_route
*/
private function getParams($params_route, $matches) {
$params = array ();
for ($i = 0; $i < count ($params_route); $i++) {
$param = $params_route[$i];
$params[$param] = $matches[$i + 1];
}
return $params;
}
/**
* Remplace les éléments de la route par les valeurs contenues dans $params
*/
private function replaceParams ($route, $params_replace) {
$uri = $route['route'];
$params = array();
foreach($route['params'] as $param) {
$uri = preg_replace('#\((.+)\)#U', $params_replace[$param], $uri, 1);
}
return stripslashes($uri);
}
}

View File

@@ -5,8 +5,7 @@
*/
class Minz_Url {
/**
* Affiche une Url formatée selon que l'on utilise l'url_rewriting ou non
* si oui, on cherche dans la table de routage la correspondance pour formater
* Affiche une Url formatée
* @param $url l'url à formater définie comme un tableau :
* $url['c'] = controller
* $url['a'] = action
@@ -39,13 +38,7 @@ class Minz_Url {
}
if ($isArray) {
$router = new Minz_Router ();
if (Minz_Configuration::useUrlRewriting ()) {
$url_string .= $router->printUriRewrited ($url);
} else {
$url_string .= self::printUri ($url, $encodage);
}
$url_string .= self::printUri ($url, $encodage);
} else {
$url_string .= $url;
}
@@ -54,14 +47,14 @@ class Minz_Url {
}
/**
* Construit l'URI d'une URL sans url rewriting
* Construit l'URI d'une URL
* @param l'url sous forme de tableau
* @param $encodage pour indiquer comment encoder les & (& ou &amp; pour html)
* @return l'uri sous la forme ?key=value&key2=value2
*/
private static function printUri ($url, $encodage) {
$uri = '';
$separator = '/?';
$separator = '?';
if($encodage == 'html') {
$and = '&amp;';

View File

@@ -102,6 +102,16 @@ class Minz_View {
}
}
/**
* Retourne renderHelper() dans une chaîne
* @param $helper l'élément à traîter
*/
public function helperToString($helper) {
ob_start();
$this->renderHelper($helper);
return ob_get_clean();
}
/**
* Permet de choisir si on souhaite utiliser le layout
* @param $use true si on souhaite utiliser le layout, false sinon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev-FreshRSS
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -50,7 +50,7 @@ define('SIMPLEPIE_NAME', 'SimplePie');
/**
* SimplePie Version
*/
define('SIMPLEPIE_VERSION', '1.3.1');
define('SIMPLEPIE_VERSION', '1.4-dev-FreshRSS');
/**
* SimplePie Build
@@ -602,7 +602,7 @@ class SimplePie
public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
/**
* @var array Stores the default attributes to add to differet tags by add_attributes().
* @var array Stores the default attributes to add to different tags by add_attributes().
* @see SimplePie::add_attributes()
* @access private
*/
@@ -644,7 +644,7 @@ class SimplePie
if (func_num_args() > 0)
{
$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level);
trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', $level);
$args = func_get_args();
switch (count($args)) {
@@ -1212,6 +1212,20 @@ class SimplePie
$this->item_limit = (int) $limit;
}
/**
* Enable throwing exceptions
*
* @param boolean $enable Should we throw exceptions, or use the old-style error property?
*/
public function enable_exceptions($enable = true)
{
$this->enable_exceptions = $enable;
}
function cleanMd5($rss) { //FreshRSS
return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss));
}
/**
* Initialize the feed object
*
@@ -1219,7 +1233,7 @@ class SimplePie
* configuration options get processed, feeds are fetched, cached, and
* parsed, and all of that other good stuff.
*
* @return boolean True if successful, false otherwise
* @return positive integer with modification time if using cache, boolean true if otherwise successful, false otherwise
*/
public function init()
{
@@ -1298,13 +1312,17 @@ class SimplePie
// Fetch the data via SimplePie_File into $this->raw_data
if (($fetched = $this->fetch_data($cache)) === true)
{
return true;
return $this->data['mtime']; //FreshRSS
}
elseif ($fetched === false) {
return false;
}
list($headers, $sniffed) = $fetched;
if (isset($this->data['md5'])) { //FreshRSS
$md5 = $this->data['md5'];
}
}
// Set up array of possible encodings
@@ -1386,6 +1404,8 @@ class SimplePie
$this->data['headers'] = $headers;
}
$this->data['build'] = SIMPLEPIE_BUILD;
$this->data['mtime'] = time(); //FreshRSS
$this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5; //FreshRSS
// Cache the file if caching is enabled
if ($cache && !$cache->save($this))
@@ -1461,7 +1481,7 @@ class SimplePie
elseif ($cache->mtime() + $this->cache_duration < time())
{
// If we have last-modified and/or etag set
if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))
//if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) //FreshRSS removed
{
$headers = array(
'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
@@ -1475,7 +1495,7 @@ class SimplePie
$headers['if-none-match'] = $this->data['headers']['etag'];
}
$file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen));
$file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen)); //FreshRSS
if ($file->success)
{
@@ -1487,7 +1507,20 @@ class SimplePie
}
else
{
unset($file);
$this->error = $file->error; //FreshRSS
return !empty($this->data); //FreshRSS
//unset($file); //FreshRSS removed
}
}
{ //FreshRSS
$md5 = $this->cleanMd5($file->body);
if ($this->data['md5'] === $md5) {
syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . $this->feed_url);
$cache->touch();
return true; //Content unchanged even though server did not send a 304
} else {
syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . $this->feed_url);
$this->data['md5'] = $md5;
}
}
}
@@ -1555,6 +1588,8 @@ class SimplePie
if ($cache)
{
$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
$this->data['mtime'] = time(); //FreshRSS
$this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5; //FreshRSS
if (!$cache->save($this))
{
trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
@@ -1987,7 +2022,21 @@ class SimplePie
*/
public function sanitize($data, $type, $base = '')
{
return $this->sanitize->sanitize($data, $type, $base);
try
{
return $this->sanitize->sanitize($data, $type, $base);
}
catch (SimplePie_Exception $e)
{
if (!$this->enable_exceptions)
{
$this->error = $e->getMessage();
$this->registry->call('Misc', 'error', array($this->error, E_USER_WARNING, $e->getFile(), $e->getLine()));
return '';
}
throw $e;
}
}
/**

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -95,10 +95,8 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
'prefix' => 'simplepie_',
),
);
$parsed = SimplePie_Cache::parse_URL($location);
$this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host'];
$this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port'];
$this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']);
$this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
$this->name = $this->options['extras']['prefix'] . md5("$name:$type");
$this->cache = new Memcache();
@@ -147,7 +145,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
if ($data !== false)
{
// essentially ignore the mtime because Memcache expires on it's own
// essentially ignore the mtime because Memcache expires on its own
return time();
}
@@ -165,7 +163,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
if ($data !== false)
{
return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration);
return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']);
}
return false;

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -96,7 +96,8 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
'prefix' => '',
),
);
$this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
$this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
// Path is prefixed with a "/"
$this->options['dbname'] = substr($this->options['path'], 1);
@@ -136,7 +137,7 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
if (!in_array($this->options['extras']['prefix'] . 'items', $db))
{
$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
if ($query === false)
{
$this->mysql = null;

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -942,7 +942,7 @@ class SimplePie_Enclosure
* - `height` (integer): The height of the embedded media. Accepts any
* numeric pixel value (such as `360`) or `auto`. Defaults to `auto`,
* and it is recommended that you use this default.
* - `loop` (boolean): Do you want the media to loop when its done?
* - `loop` (boolean): Do you want the media to loop when it's done?
* Defaults to `false`.
* - `mediaplayer` (string): The location of the included
* `mediaplayer.swf` file. This allows for the playback of Flash Video

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -108,7 +108,7 @@ class SimplePie_File
curl_setopt($fp, CURLOPT_REFERER, $url);
curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); //FreshRSS
if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
{
curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
@@ -284,7 +284,7 @@ class SimplePie_File
else
{
$this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS;
if (!$this->body = file_get_contents($url))
if (empty($url) || !($this->body = file_get_contents($url)))
{
$this->error = 'file_get_contents could not read the file';
$this->success = false;

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -821,7 +821,7 @@ class SimplePie_Item
if (!empty($this->data['updated']['raw']))
{
$parser = $this->registry->call('Parse_Date', 'get');
$this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']);
$this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']);
}
else
{
@@ -1080,7 +1080,7 @@ class SimplePie_Item
*
* @since Beta 2
* @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4).
* @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists).
* @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists).
* @return array|null List of SimplePie_Enclosure items
*/
public function get_enclosures()

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -277,7 +277,7 @@ class SimplePie_Locator
$parsed = $this->registry->call('Misc', 'parse_url', array($href));
if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme']))
{
if ($this->base_location < $link->getLineNo())
if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo())
{
$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base));
}

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -128,7 +128,7 @@ class SimplePie_Misc
{
$attribs[$j][2] = $attribs[$j][1];
}
$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); //FreshRSS
}
}
}
@@ -142,7 +142,7 @@ class SimplePie_Misc
foreach ($element['attribs'] as $key => $value)
{
$key = strtolower($key);
$full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"';
$full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; //FreshRSS
}
if ($element['self_closing'])
{
@@ -228,6 +228,23 @@ class SimplePie_Misc
}
}
public static function array_merge_recursive($array1, $array2)
{
foreach ($array2 as $key => $value)
{
if (is_array($value))
{
$array1[$key] = SimplePie_Misc::array_merge_recursive($array1[$key], $value);
}
else
{
$array1[$key] = $value;
}
}
return $array1;
}
public static function parse_url($url)
{
$iri = new SimplePie_IRI($url);
@@ -2161,36 +2178,12 @@ function embed_wmedia(width, height, link) {
/**
* Get the SimplePie build timestamp
*
* Uses the git index if it exists, otherwise uses the modification time
* of the newest file.
* Return SimplePie.php modification time.
*/
public static function get_build()
{
$root = dirname(dirname(__FILE__));
if (file_exists($root . '/.git/index'))
{
return filemtime($root . '/.git/index');
}
elseif (file_exists($root . '/SimplePie'))
{
$time = 0;
foreach (glob($root . '/SimplePie/*.php') as $file)
{
if (($mtime = filemtime($file)) > $time)
{
$time = $mtime;
}
}
return $time;
}
elseif (file_exists(dirname(__FILE__) . '/Core.php'))
{
return filemtime(dirname(__FILE__) . '/Core.php');
}
else
{
return filemtime(__FILE__);
}
$mtime = @filemtime(dirname(dirname(__FILE__)) . '/SimplePie.php'); //FreshRSS
return $mtime ? $mtime : filemtime(__FILE__);
}
/**

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -145,10 +145,15 @@ class SimplePie_Parser
$dom->loadXML($data);
$this->encoding = $encoding = $dom->encoding = 'UTF-8';
$data2 = $dom->saveXML();
if (function_exists('mb_convert_encoding'))
{
$data2 = mb_convert_encoding($data2, 'UTF-8', 'UTF-8');
}
if (strlen($data2) > (strlen($data) / 2.0))
{
$data = $data2;
}
unset($data2);
}
catch (Exception $e)
{

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon
@@ -267,6 +267,10 @@ class SimplePie_Sanitize
if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML))
{
if (!class_exists('DOMDocument'))
{
throw new SimplePie_Exception('DOMDocument not found, unable to use sanitizer');
}
$document = new DOMDocument();
$document->encoding = 'UTF-8';
$data = $this->preprocess($data, $type);
@@ -339,7 +343,7 @@ class SimplePie_Sanitize
}
else
{
$file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen));
$file = $this->registry->create('File', array($img->getAttribute('src'), $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen));
$headers = $file->headers;
if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

View File

@@ -33,7 +33,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*
* @package SimplePie
* @version 1.3.1
* @version 1.4-dev
* @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
* @author Ryan Parman
* @author Geoffrey Sneddon

131
lib/lib_date.php Normal file
View File

@@ -0,0 +1,131 @@
<?php
/**
* Author: Alexandre Alapetite http://alexandre.alapetite.fr
* 2014-06-01
* License: GNU AGPLv3 http://www.gnu.org/licenses/agpl-3.0.html
*
* Parser of ISO 8601 time intervals http://en.wikipedia.org/wiki/ISO_8601#Time_intervals
* Examples: "2014-02/2014-04", "2014-02/04", "2014-06", "P1M"
*/
/*
example('2014-03');
example('201403');
example('2014-03-30');
example('2014-05-30T13');
example('2014-05-30T13:30');
example('2014-02/2014-04');
example('2014-02--2014-04');
example('2014-02/04');
example('2014-02-03/05');
example('2014-02-03T22:00/22:15');
example('2014-02-03T22:00/15');
example('2014-03/');
example('/2014-03');
example('2014-03/P1W');
example('P1W/2014-05-25T23:59:59');
example('P1Y/');
example('P1Y');
example('P2M/');
example('P3W/');
example('P4D/');
example('PT5H/');
example('PT6M/');
example('PT7S/');
example('P1DT1H/');
function example($dateInterval) {
$dateIntervalArray = parseDateInterval($dateInterval);
echo $dateInterval, "\t=>\t",
$dateIntervalArray[0] == null ? 'null' : @date('c', $dateIntervalArray[0]), '/',
$dateIntervalArray[1] == null ? 'null' : @date('c', $dateIntervalArray[1]), "\n";
}
*/
function _dateFloor($isoDate) {
$x = explode('T', $isoDate, 2);
$t = isset($x[1]) ? str_pad($x[1], 6, '0') : '000000';
return str_pad($x[0], 8, '01') . 'T' . $t;
}
function _dateCeiling($isoDate) {
$x = explode('T', $isoDate, 2);
$t = isset($x[1]) && strlen($x[1]) > 1 ? str_pad($x[1], 6, '59') : '235959';
switch (strlen($x[0])) {
case 4:
return $x[0] . '1231T' . $t;
case 6:
$d = @strtotime($x[0] . '01');
return $x[0] . date('t', $d) . 'T' . $t;
default:
return $x[0] . 'T' . $t;
}
}
function _noDelimit($isoDate) {
return $isoDate === null || $isoDate === '' ? null :
str_replace(array('-', ':'), '', $isoDate); //FIXME: Bug with negative time zone
}
function _dateRelative($d1, $d2) {
if ($d2 === null) {
return $d1 !== null && $d1[0] !== 'P' ? $d1 : null;
} elseif ($d2 !== '' && $d2[0] != 'P' && $d1 !== null && $d1[0] !== 'P') {
$y2 = substr($d2, 0, 4);
if (strlen($y2) < 4 || !ctype_digit($y2)) { //Does not start by a year
$d2 = _noDelimit($d2);
return substr($d1, 0, -strlen($d2)) . $d2; //Add prefix from $d1
}
}
return _noDelimit($d2);
}
/**
* Parameter $dateInterval is a string containing an ISO 8601 time interval.
* Returns an array with the minimum and maximum Unix timestamp of this interval,
* or null if open interval, or false if error.
*/
function parseDateInterval($dateInterval) {
$dateInterval = trim($dateInterval);
$dateInterval = str_replace('--', '/', $dateInterval);
$dateInterval = strtoupper($dateInterval);
$min = null;
$max = null;
$x = explode('/', $dateInterval, 2);
$d1 = _noDelimit($x[0]);
$d2 = _dateRelative($d1, count($x) > 1 ? $x[1] : null);
if ($d1 !== null && $d1[0] !== 'P') {
$min = @strtotime(_dateFloor($d1));
}
if ($d2 !== null) {
if ($d2[0] === 'P') {
try {
$di2 = new DateInterval($d2);
$dt1 = @date_create(); //new DateTime() would create an Exception if the default time zone is not defined
if ($min !== null && $min !== false) {
$dt1->setTimestamp($min);
}
$max = $dt1->add($di2)->getTimestamp() - 1;
} catch (Exception $e) {
$max = false;
}
} elseif ($d1 === null || $d1[0] !== 'P') {
$max = @strtotime(_dateCeiling($d2));
} else {
$max = @strtotime($d2);
}
}
if ($d1 !== null && $d1[0] === 'P') {
try {
$di1 = new DateInterval($d1);
$dt2 = @date_create();
if ($max !== null && $max !== false) {
$dt2->setTimestamp($max);
}
$min = $dt2->sub($di1)->getTimestamp() + 1;
} catch (Exception $e) {
$min = false;
}
}
return array($min, $max);
}

View File

@@ -1,23 +1,86 @@
<?php
function opml_export ($cats) {
$txt = '';
foreach ($cats as $cat) {
$txt .= '<outline text="' . $cat['name'] . '">' . "\n";
/* *
* lib_opml is a free library to manage OPML format in PHP.
* It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html).
* Basically it means "text" attribute for outline elements is required.
*
* lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php)
*
* Usages:
* > include('lib_opml.php');
* > $filename = 'my_opml_file.xml';
* > $opml_array = libopml_parse_file($filename);
* > print_r($opml_array);
*
* > $opml_string = [...];
* > $opml_array = libopml_parse_string($opml_string);
* > print_r($opml_array);
*
* > $opml_array = [...];
* > $opml_string = libopml_render($opml_array);
* > $opml_object = libopml_render($opml_array, true);
* > echo $opml_string;
* > print_r($opml_object);
*
* If parsing fails for any reason (e.g. not an XML string, does not match with
* the specifications), a LibOPML_Exception is raised.
*
* Author: Marien Fressinaud <dev@marienfressinaud.fr>
* Url: https://github.com/marienfressinaud/lib_opml
* Version: 0.1
* Date: 2014-03-29
* License: public domain
*
* */
foreach ($cat['feeds'] as $feed) {
$txt .= "\t" . '<outline text="' . $feed->name () . '" type="rss" xmlUrl="' . $feed->url () . '" htmlUrl="' . $feed->website () . '" description="' . htmlspecialchars($feed->description(), ENT_COMPAT, 'UTF-8') . '" />' . "\n";
class LibOPML_Exception extends Exception {}
// These elements are optional
define('HEAD_ELEMENTS', serialize(array(
'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail',
'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop',
'windowLeft', 'windowBottom', 'windowRight'
)));
function libopml_parse_outline($outline_xml) {
$outline = array();
// An outline may contain any kind of attributes but "text" attribute is
// required !
$text_is_present = false;
foreach ($outline_xml->attributes() as $key => $value) {
$outline[$key] = (string)$value;
if ($key === 'text') {
$text_is_present = true;
}
$txt .= '</outline>' . "\n";
}
return $txt;
if (!$text_is_present) {
throw new LibOPML_Exception(
'Outline does not contain any text attribute'
);
}
foreach ($outline_xml->children() as $key => $value) {
// An outline may contain any number of outline children
if ($key === 'outline') {
$outline['@outlines'][] = libopml_parse_outline($value);
} else {
throw new LibOPML_Exception(
'Body can contain only outline elements'
);
}
}
return $outline;
}
function opml_import ($xml) {
$xml = html_only_entity_decode($xml); //!\ Assume UTF-8
function libopml_parse_string($xml) {
$dom = new DOMDocument();
$dom->recover = true;
$dom->strictErrorChecking = false;
@@ -27,95 +90,142 @@ function opml_import ($xml) {
$opml = simplexml_import_dom($dom);
if (!$opml) {
throw new FreshRSS_Opml_Exception ();
throw new LibOPML_Exception();
}
$catDAO = new FreshRSS_CategoryDAO();
$catDAO->checkDefault();
$defCat = $catDAO->getDefault();
$array = array(
'version' => (string)$opml['version'],
'head' => array(),
'body' => array()
);
$categories = array ();
$feeds = array ();
foreach ($opml->body->outline as $outline) {
if (!isset ($outline['xmlUrl'])) {
// Catégorie
$title = '';
if (isset ($outline['text'])) {
$title = (string) $outline['text'];
} elseif (isset ($outline['title'])) {
$title = (string) $outline['title'];
}
if ($title) {
// Permet d'éviter les soucis au niveau des id :
// ceux-ci sont générés en fonction de la date,
// un flux pourrait être dans une catégorie X avec l'id Y
// alors qu'il existe déjà la catégorie X mais avec l'id Z
// Y ne sera pas ajouté et le flux non plus vu que l'id
// de sa catégorie n'exisera pas
$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
$catDAO = new FreshRSS_CategoryDAO ();
$cat = $catDAO->searchByName ($title);
if ($cat === false) {
$cat = new FreshRSS_Category ($title);
$values = array (
'name' => $cat->name (),
'color' => $cat->color ()
);
$cat->_id ($catDAO->addCategory ($values));
}
$feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ()));
}
// First, we get all "head" elements. Head is required but its sub-elements
// are optional.
foreach ($opml->head->children() as $key => $value) {
if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
$array['head'][$key] = (string)$value;
} else {
// Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut
$feeds[] = getFeed ($outline, $defCat->id());
}
}
return array ($categories, $feeds);
}
/**
* import all feeds of a given outline tag
*/
function getFeedsOutline ($outline, $cat_id) {
$feeds = array ();
foreach ($outline->children () as $child) {
if (isset ($child['xmlUrl'])) {
$feeds[] = getFeed ($child, $cat_id);
} else {
$feeds = array_merge(
$feeds,
getFeedsOutline ($child, $cat_id)
throw new LibOPML_Exception(
$key . 'is not part of OPML format'
);
}
}
return $feeds;
// Then, we get body oulines. Body must contain at least one outline
// element.
$at_least_one_outline = false;
foreach ($opml->body->children() as $key => $value) {
if ($key === 'outline') {
$at_least_one_outline = true;
$array['body'][] = libopml_parse_outline($value);
} else {
throw new LibOPML_Exception(
'Body can contain only outline elements'
);
}
}
if (!$at_least_one_outline) {
throw new LibOPML_Exception(
'Body must contain at least one outline element'
);
}
return $array;
}
function getFeed ($outline, $cat_id) {
$url = (string) $outline['xmlUrl'];
$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
$title = '';
if (isset ($outline['text'])) {
$title = (string) $outline['text'];
} elseif (isset ($outline['title'])) {
$title = (string) $outline['title'];
function libopml_parse_file($filename) {
$file_content = file_get_contents($filename);
if ($file_content === false) {
throw new LibOPML_Exception(
$filename . ' cannot be found'
);
}
return libopml_parse_string($file_content);
}
function libopml_render_outline($parent_elt, $outline) {
// Outline MUST be an array!
if (!is_array($outline)) {
throw new LibOPML_Exception(
'Outline element must be defined as array'
);
}
$outline_elt = $parent_elt->addChild('outline');
$text_is_present = false;
foreach ($outline as $key => $value) {
// Only outlines can be an array and so we consider children are also
// outline elements.
if ($key === '@outlines' && is_array($value)) {
foreach ($value as $outline_child) {
libopml_render_outline($outline_elt, $outline_child);
}
} elseif (is_array($value)) {
throw new LibOPML_Exception(
'Type of outline elements cannot be array: ' . $key
);
} else {
// Detect text attribute is present, that's good :)
if ($key === 'text') {
$text_is_present = true;
}
$outline_elt->addAttribute($key, $value);
}
}
if (!$text_is_present) {
throw new LibOPML_Exception(
'You must define at least a text element for all outlines'
);
}
}
function libopml_render($array, $as_xml_object = false) {
$opml = new SimpleXMLElement('<opml version="2.0"></opml>');
// Create head element. $array['head'] is optional but head element will
// exist in the final XML object.
$head = $opml->addChild('head');
if (isset($array['head'])) {
foreach ($array['head'] as $key => $value) {
if (in_array($key, unserialize(HEAD_ELEMENTS), true)) {
$head->addChild($key, $value);
}
}
}
// Check body is set and contains at least one element
if (!isset($array['body'])) {
throw new LibOPML_Exception(
'$array must contain a body element'
);
}
if (count($array['body']) <= 0) {
throw new LibOPML_Exception(
'Body element must contain at least one element (array)'
);
}
// Create outline elements
$body = $opml->addChild('body');
foreach ($array['body'] as $outline) {
libopml_render_outline($body, $outline);
}
// And return the final result
if ($as_xml_object) {
return $opml;
} else {
$dom = dom_import_simplexml($opml)->ownerDocument;
$dom->formatOutput = true;
$dom->encoding = 'UTF-8';
return $dom->saveXML();
}
$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
$feed = new FreshRSS_Feed ($url);
$feed->_category ($cat_id);
$feed->_name ($title);
if (isset($outline['htmlUrl'])) {
$feed->_website(htmlspecialchars((string)$outline['htmlUrl'], ENT_COMPAT, 'UTF-8'));
}
if (isset($outline['description'])) {
$feed->_description(sanitizeHTML((string)$outline['description']));
}
return $feed;
}

View File

@@ -27,7 +27,7 @@ function classAutoloader($class) {
include(APP_PATH . '/Models/' . $components[1] . '.php');
return;
case 3: //Controllers, Exceptions
include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php');
@include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php');
return;
}
} elseif (strpos($class, 'Minz') === 0) {
@@ -214,12 +214,12 @@ function uSecString() {
}
function invalidateHttpCache() {
touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log');
Minz_Session::_param('touch', uTimeString());
return touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log');
}
function usernameFromPath($userPath) {
if (preg_match('%/([a-z0-9]{1,16})_user\.php$%', $userPath, $matches)) {
if (preg_match('%/([A-Za-z0-9]{1,16})_user\.php$%', $userPath, $matches)) {
return $matches[1];
} else {
return '';
@@ -233,3 +233,18 @@ function listUsers() {
function httpAuthUser() {
return isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : '';
}
function cryptAvailable() {
if (version_compare(PHP_VERSION, '5.3.3', '>=')) {
try {
$hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
return $hash === @crypt('password', $hash);
} catch (Exception $e) {
}
}
return false;
}
function html_chars_utf8($str) {
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
}

Some files were not shown because too many files have changed in this diff Show More