1
0
Fork 0
mirror of https://github.com/YunoHost-Apps/freshrss_ynh.git synced 2024-09-03 18:36:33 +02:00

[enh][wip]Installation Refactoring

This commit is contained in:
Clément 2016-09-01 08:31:03 +02:00
parent 472c5cdf68
commit a77ab86a38
432 changed files with 29 additions and 66900 deletions

View file

@ -1,10 +1,19 @@
#!/bin/bash #!/bin/bash
# Exit on command errors and treat unset variables as an error
set -eu
# Retrieve arguments # Retrieve arguments
domain=$1 domain=$1
path=$2 path=$2
admin_user=$3 admin_user=$3
# Load common variables and helpers
. ./_common.sh
# Source app helpers
. /usr/share/yunohost/helpers
# Check user parameter if not empty # Check user parameter if not empty
if [[ $admin_user != '' ]]; then if [[ $admin_user != '' ]]; then
@ -34,21 +43,21 @@ sudo yunohost app initdb $db_user -p $db_pwd
sudo yunohost app setting freshrss mysqlpwd -v $db_pwd sudo yunohost app setting freshrss mysqlpwd -v $db_pwd
# Copy files to the right place # Copy files to the right place
final_path=/var/www/freshrss sudo mkdir -p $FINAL_PATH
sudo mkdir -p $final_path extract_freshrss "$FINAL_PATH"
sudo cp -a ../sources/* $final_path sudo cp -a ../conf/config.php $FINAL_PATH/data
sudo cp -a ../conf/config.php $final_path/data sudo cp ../sources/install_ynh.sql $FINAL_PATH/app/SQL/install_ynh.sql
# Change variables in freshrss configuration # Change variables in freshrss configuration
sudo sed -i "s/yunouser/$db_user/g" $final_path/data/config.php sudo sed -i "s/yunouser/$db_user/g" $FINAL_PATH/data/config.php
sudo sed -i "s/yunopass/$db_pwd/g" $final_path/data/config.php sudo sed -i "s/yunopass/$db_pwd/g" $FINAL_PATH/data/config.php
sudo sed -i "s/yunobase/$db_user/g" $final_path/data/config.php sudo sed -i "s/yunobase/$db_user/g" $FINAL_PATH/data/config.php
sudo sed -i "s/yunosalt/$app_salt/g" $final_path/data/config.php sudo sed -i "s/yunosalt/$app_salt/g" $FINAL_PATH/data/config.php
sudo sed -i "s@yunopath@$path@g" $final_path/data/config.php sudo sed -i "s@yunopath@$path@g" $FINAL_PATH/data/config.php
if [[ $admin_user != '' ]]; then if [[ $admin_user != '' ]]; then
sudo sed -i "s/yunoadminuser/$admin_user/g" $final_path/data/config.php sudo sed -i "s/yunoadminuser/$admin_user/g" $FINAL_PATH/data/config.php
else else
sudo sed -i '/yunoadminuser/d' $final_path/data/config.php sudo sed -i '/yunoadminuser/d' $FINAL_PATH/data/config.php
fi fi
# Add users # Add users
@ -66,7 +75,7 @@ freshrss_users=$(ldapsearch -h localhost -b ou=users,dc=yunohost,dc=org -x objec
for myuser in $freshrss_users for myuser in $freshrss_users
do do
#copy sql #copy sql
sudo cp ../sources/app/SQL/install_ynh.sql /tmp/$myuser-install.sql sudo cp ../sources/install_ynh.sql /tmp/$myuser-install.sql
#change username in sql #change username in sql
sudo sed -i "s/YnoUser/$myuser/g" /tmp/$myuser-install.sql sudo sed -i "s/YnoUser/$myuser/g" /tmp/$myuser-install.sql
#create tables #create tables
@ -74,32 +83,32 @@ do
#remove temp sql #remove temp sql
sudo rm /tmp/$myuser-install.sql sudo rm /tmp/$myuser-install.sql
#copy default conf #copy default conf
sudo cp -r $final_path/data/users/_/ $final_path/data/users/$myuser sudo cp -r $FINAL_PATH/data/users/_/ $FINAL_PATH/data/users/$myuser
sudo mv $final_path/data/users/$myuser/config.default.php $final_path/data/users/$myuser/config.php sudo mv $FINAL_PATH/data/users/$myuser/config.default.php $FINAL_PATH/data/users/$myuser/config.php
if [[ $sharingEnable -eq 1 ]]; then if [[ $sharingEnable -eq 1 ]]; then
sudo sed -i "s@'sharing'\ =>\ array\ (@$sharingWallabag@g" $final_path/data/users/$myuser/config.php sudo sed -i "s@'sharing'\ =>\ array\ (@$sharingWallabag@g" $FINAL_PATH/data/users/$myuser/config.php
fi fi
done done
# Delete install directive # Delete install directive
sudo rm $final_path/data/do-install.txt sudo rm $FINAL_PATH/data/do-install.txt
# Modify Nginx configuration file and copy it to Nginx conf directory # Modify Nginx configuration file and copy it to Nginx conf directory
sed -i "s@PATHTOCHANGE@$path@g" ../conf/nginx.conf sed -i "s@PATHTOCHANGE@$path@g" ../conf/nginx.conf
sed -i "s@ALIASTOCHANGE@$final_path/@g" ../conf/nginx.conf sed -i "s@ALIASTOCHANGE@$FINAL_PATH/@g" ../conf/nginx.conf
sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/freshrss.conf sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/freshrss.conf
#install php5-cli #install php5-cli
sudo apt-get update sudo apt-get update
sudo apt-get install -y php5-cli php5-gmp sudo apt-get install -y php5-cli php5-gmp
#install update cron #install update cron
echo "*/10 * * * * www-data /usr/bin/php $final_path/app/actualize_script.php >/tmp/FreshRSS.log 2>&1" > /tmp/cronfreshrss echo "*/10 * * * * www-data /usr/bin/php $FINAL_PATH/app/actualize_script.php >/tmp/FreshRSS.log 2>&1" > /tmp/cronfreshrss
sudo mv /tmp/cronfreshrss /etc/cron.d/freshrss sudo mv /tmp/cronfreshrss /etc/cron.d/freshrss
sudo chown root /etc/cron.d/freshrss sudo chown root /etc/cron.d/freshrss
# Set permissions to freshrss directory # Set permissions to freshrss directory
sudo chown -R www-data: $final_path/data/ sudo chown -R www-data: $FINAL_PATH/data/
sudo chown -R www-data: $final_path/extensions/ sudo chown -R www-data: $FINAL_PATH/extensions/
#skip api directory #skip api directory
sudo yunohost app setting freshrss skipped_uris -v /api/greader.php sudo yunohost app setting freshrss skipped_uris -v /api/greader.php

View file

@ -1,558 +0,0 @@
# Changelog
## 2016-07-23 FreshRSS 1.4.0
## 2016-06-12 FreshRSS 1.3.2-beta
* Compatibility
* Require at least PHP 5.3+ (drop PHP 5.2) [#1133](https://github.com/FreshRSS/FreshRSS/pull/1133)
* Features
* Support for MySQL 5.7+ (e.g. Ubuntu 16.04 LTS) [#1132](https://github.com/FreshRSS/FreshRSS/pull/1132)
* Speed optimization for HTTP/2 [#1133](https://github.com/FreshRSS/FreshRSS/pull/1133)
* API support for REDIRECT_* HTTP headers (fcgi) [#1128](https://github.com/FreshRSS/FreshRSS/issues/1128)
* SimplePie
* Support for feeds with invalid whitespace [#1142](https://github.com/FreshRSS/FreshRSS/issues/1142)
* Bug fixing
* Fix bug when adding feeds with passwords [#1137](https://github.com/FreshRSS/FreshRSS/pull/1137)
* Fix validator link [#1147](https://github.com/FreshRSS/FreshRSS/pull/1147)
* Fix Favicon small bugs [#1135](https://github.com/FreshRSS/FreshRSS/pull/1135)
* Security
* CSP compatibility for homepage [#1120](https://github.com/FreshRSS/FreshRSS/pull/1120)
* I18n
* Draft of Russian [#1085](https://github.com/FreshRSS/FreshRSS/pull/1085)
* Misc.
* Change default feed timeout to 15 seconds [#1146](https://github.com/FreshRSS/FreshRSS/pull/1146)
* Updated Wallabag v2 [#1150](https://github.com/FreshRSS/FreshRSS/pull/1150)
## 2016-03-11 FreshRSS 1.3.1-beta
* Security
* Added CSP `Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *` [#1075](https://github.com/FreshRSS/FreshRSS/issues/1075), [#1114](https://github.com/FreshRSS/FreshRSS/issues/1114)
* Added `X-Content-Type-Options: nosniff` [#1116](https://github.com/FreshRSS/FreshRSS/pull/1116)
* Cookie with `Secure` tag when used over HTTPS [#1117](https://github.com/FreshRSS/FreshRSS/pull/1117)
* Limit API post input to 1MB [#1118](https://github.com/FreshRSS/FreshRSS/pull/1118)
* Features
* New list of domains for which to force HTTPS (for images, videos, iframes…) defined in `./data/force-https.default.txt` and `./data/force-https.txt` [#1083](https://github.com/FreshRSS/FreshRSS/issues/1083)
* In particular useful for privacy and to avoid mixed content errors, e.g. to see YouTube videos when FreshRSS is in HTTPS
* Add sharing with “Journal du Hacker” [#1056](https://github.com/FreshRSS/FreshRSS/pull/1056)
* UI
* Updated to jQuery 2.2.1 and changed code for auto-load on scroll [#1050](https://github.com/FreshRSS/FreshRSS/pull/1050), [#1091](https://github.com/FreshRSS/FreshRSS/pull/1091)
* I18n
* Turkish [#1073](https://github.com/FreshRSS/FreshRSS/issues/1073)
* Bug fixing
* Fixed OPML import title bug [#1048](https://github.com/FreshRSS/FreshRSS/issues/1048)
* Fixed upgrade bug with SQLite when articles were marked as unread [#1049](https://github.com/FreshRSS/FreshRSS/issues/1049)
* Fixed error when deleting feeds from statistics page [#1047](https://github.com/FreshRSS/FreshRSS/issues/1047)
* Fixed several small bugs in global and reader view [#1050](https://github.com/FreshRSS/FreshRSS/pull/1050)
* Fixed sharing bug with PHP7 [#1072](https://github.com/FreshRSS/FreshRSS/issues/1072)
* Fixed fall-back when php-json is not installed [#1092](https://github.com/FreshRSS/FreshRSS/issues/1092)
* API
* Possibility to show only read items [#1035](https://github.com/FreshRSS/FreshRSS/pull/1035)
* Misc.
* Filters `<img />` attributes `srcset` and `sizes` [#1077](https://github.com/FreshRSS/FreshRSS/issues/1077), [#1086](https://github.com/FreshRSS/FreshRSS/pull/1086)
* Implement PubSubHubbub unsubscribe responses [#1058](https://github.com/FreshRSS/FreshRSS/issues/1058)
* Restored some compatibility with PHP 5.2 [#1055](https://github.com/FreshRSS/FreshRSS/issues/1055)
* Check for extension php-xml during install [#1094](https://github.com/FreshRSS/FreshRSS/issues/1094)
* Updated the sharing with Movim [#1030](https://github.com/FreshRSS/FreshRSS/pull/1030)
## 2015-11-03 FreshRSS 1.2.0 / 1.3.0-beta
* Features
* Share with Movim [#992](https://github.com/FreshRSS/FreshRSS/issues/992)
* New option to allow robots / search engines [#938](https://github.com/FreshRSS/FreshRSS/issues/938)
* Security
* Invalid logins now return HTTP 403, to be easier to catch (e.g. fail2ban) [#1015](https://github.com/FreshRSS/FreshRSS/issues/1015)
* UI
* Remove "title" field during installation [#858](https://github.com/FreshRSS/FreshRSS/issues/858)
* Visual alert on categories containing feeds in error [#984](https://github.com/FreshRSS/FreshRSS/pull/984)
* I18n
* Italian [#1003](https://github.com/FreshRSS/FreshRSS/issues/1003)
* Misc.
* Support reverse proxy [#975](https://github.com/FreshRSS/FreshRSS/issues/975)
* Make auto-update server URL alterable [#1019](https://github.com/FreshRSS/FreshRSS/issues/1019)
## 2015-09-12 FreshRSS 1.1.3-beta
* UI
* Configuration page for global settings such as limits [#958](https://github.com/FreshRSS/FreshRSS/pull/958)
* Add feed ID in articles to ease styling [#953](https://github.com/FreshRSS/FreshRSS/issues/953)
* I18n
* Dutch [#949](https://github.com/FreshRSS/FreshRSS/issues/949)
* Bug fixing
* Session cookie bug [#924](https://github.com/FreshRSS/FreshRSS/issues/924)
* Better error handling for PubSubHubbub [#939](https://github.com/FreshRSS/FreshRSS/issues/939)
* Fix tag search link from articles [#970](https://github.com/FreshRSS/FreshRSS/issues/970)
* Fix all quieries deleted when deleting a feed or category [#982](https://github.com/FreshRSS/FreshRSS/pull/982)
## 2015-07-30 FreshRSS 1.1.2-beta
* Features
* Support for PubSubHubbub for instant notifications from compatible Web sites. [#312](https://github.com/FreshRSS/FreshRSS/issues/312)
* cURL options to use a proxy for retrieving feeds. [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#675](https://github.com/FreshRSS/FreshRSS/issues/675)
* Allow anonymous users to create an account. [#679](https://github.com/FreshRSS/FreshRSS/issues/679)
* Security
* cURL options to verify or not SSL/TLS certificates (now enabled by default). [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#502](https://github.com/FreshRSS/FreshRSS/issues/502)
* Support for SSL connection to MySQL. [#868](https://github.com/FreshRSS/FreshRSS/issues/868)
* Workaround for browsers that have disabled support for `<form autocomplete="off">`. [#880](https://github.com/FreshRSS/FreshRSS/issues/880)
* UI
* Force UTF-8 for responses. [#870](https://github.com/FreshRSS/FreshRSS/issues/870)
* Increased pagination limit to 500 articles. [#872](https://github.com/FreshRSS/FreshRSS/issues/872)
* Improved UI for installation. [#855](https://github.com/FreshRSS/FreshRSS/issues/855)
* Misc.
* PHP 7 officially supported (~70% speed improvements on early tests). [#889](https://github.com/FreshRSS/FreshRSS/issues/889)
* Restore support for PHP 5.2.1+. [#214a5cc](https://github.com/Alkarex/FreshRSS/commit/214a5cc9a4c2b821961bc21f22b4b08e34b5be68) [#894](https://github.com/FreshRSS/FreshRSS/issues/894)
* Support for data-src for images of articles retrieved via the full-content module. [#877](https://github.com/FreshRSS/FreshRSS/issues/877)
* Add a couple of default feeds for fresh installations. [#886](https://github.com/FreshRSS/FreshRSS/issues/886)
* Changed some log visibilities. [#885](https://github.com/FreshRSS/FreshRSS/issues/885)
* Fix broken links for extension script / style files. [#862](https://github.com/FreshRSS/FreshRSS/issues/862)
* Load default configuration during installation to avoid hard-coded values. [#890](https://github.com/FreshRSS/FreshRSS/issues/890)
* Fix non-consistent behaviour in Minz_Request::getBaseUrl() and introduce Minz_Request::guessBaseUrl(). [#906](https://github.com/FreshRSS/FreshRSS/issues/906)
* Generate `base_url` during the installation and add a `pubsubhubbub_enabled` configuration key. [#865](https://github.com/FreshRSS/FreshRSS/issues/865)
* Load configuration by recursion to overwrite array values. [#923](https://github.com/FreshRSS/FreshRSS/issues/923)
* Cast `$limits` configuration values in integer. [#925](https://github.com/FreshRSS/FreshRSS/issues/925)
* Don't hide errors in configuration. [#920](https://github.com/FreshRSS/FreshRSS/issues/920)
## 2015-05-31 FreshRSS 1.1.1 (beta)
* Features
* New option to detect and mark updated articles as unread.
* Support for internationalized domain name (IDN).
* Improved logic for automatic deletion of old articles.
* API
* Work-around for News+ bug when there is no unread article on the server.
* UI
* New confirmation message when leaving a configuration page without saving the changes.
* Bug fixing
* Corrected bug introduced in previous beta about handling of HTTP 301 (feeds that have changed address)
* Corrected bug in FreshRSS RSS feeds.
* Security
* Sanitize HTTP request header `Host`.
* Misc.
* Attempt to better handle encoded article titles.
## 2015-01-31 FreshRSS 1.0.0 / 1.1.0 (beta)
* UI
* Slider math with Dark theme
* Add a message if request failed for mark as read / favourite
* I18n
* Fix some sentences
* Add German as a supported language
* Add some indications on password format
* Bug fixing
* Some shortcuts was never saved
* Global view didn't work if set by default
* Minz_Error was badly raised
* Feed update failed if nothing had changed (MySQL only)
* CRON task failed with multiple users
* Tricky bug caused by cookie path
* Email sharing was badly supported (no urlencode())
* Misc.
* Add a CREDIT file with contributor names
* Update lib_opml
* Default favicon is now served by HTTP code 200
* Change calls to syslog by Minz_Log::notice
* HTTP credentials are no longer logged
## 2015-01-15 FreshRSS 0.9.4 (beta)
* Feature
* Extension system (!!): some extensions are available at https://github.com/FreshRSS/Extensions
* Refactoring
* Front controller (FreshRSS class)
* Configuration system
* Sharing system
* New data files organization
* Updates
* Remove restriction of 1h for updates
* Show the current version of FreshRSS and the next one
* UI
* Remove the "sticky position" of the feed aside (moved into an extension)
* "Show password" shows the password only while the user is pressing the mouse.
## 2014-12-12 FreshRSS 0.9.3 (beta)
* SimplePie
* Support for content-type application/x-rss+xml
* New force_feed option (for feeds sent with the wrong content-type / MIME) by adding #force_feed at the end of the feed URL
* Improved error messages
* Statistics
* Add information on feed repartition pages
* Add percent repartition for the bigger feeds
* UI
* New theme selector
* Update Screwdriver theme
* Add BlueLagoon theme by Mister aiR
* Misc.
* Add option to remove articles after reading them
* Add comments
* Refactor i18n system to avoid loading unnecessary strings
* Fix security issue in Minz_Error::error() method
* Fix redirection after refreshing a given feed
## 2014-10-31 FreshRSS 0.9.2 (beta)
* UI
* New subscription page (introduce .box items)
* Change feed category by drag and drop
* New feed aside on the main page
* New configuration / administration organization
* Configuration
* New options in config.php for cache duration, timeout, max inactivity, max number of feeds and categories per user.
* Refactoring
* Refactor authentication system (introduce FreshRSS_Auth model)
* Refactor indexController (introduce FreshRSS_Context model)
* Use ```_t()```, ```_i()```, ```_url()```, ```Minz_Request::good()``` and ```Minz_Request::bad()``` as much as possible
* Refactor javascript_vars.phtml
* Better coding style
* I18n
* Introduce a new system for i18n keys (not finished yet)
* Misc.
* Fix global view (did not work anymore)
* Add do_post_update for update system
* Introduce ```checkInstallAction``` to test if FreshRSS installation is ok
## 2014-10-09 FreshRSS 0.8.1 / 0.9.1 (beta)
* UI
* Add a space after tag icon
* Statistics
* Add an average per day on the 30-day period graph
* Add percent of total on top 10 feed
* Bug fixes
* Fix "mark as read" in global view
* Fix "read all" shortcut
* Fix categories not appearing when adding a new feed (GET action)
* Fix enclosure problem
* Fix getExtension() on PHP < 5.3.7
## 2014-09-26 FreshRSS 0.8.0 / 0.9.0 (beta)
* UI
* New interface for statistics
* Fix filter buttons
* Number of articles divided by 2 in reading view
* Redesign of bigMarkAsRead
* Features
* New automatic update system
* New reset auth system
* Security
* "Mark as read" requires POST requests for several articles
* Test HTTP REFERER in install.php
* Configuration
* New "Show all articles" / "Show only unread" / "Adjust viewing" option
* New notification timeout option
* Misc.
* Improve coding style + comments
* Fix SQLite bug "ON DELETE CASCADE"
* Improve performance when importing articles
## 2014-08-24 FreshRSS 0.7.4
* UI
* Hide categories/feeds with unread articles when showing only unread articles
* Dynamic favicon showing the number of unread articles
* New theme: Screwdriver by Mister aiR
* Statistics
* New page with article repartition
* Improvements
* Security
* Basic protection against XSRF (Cross-Site Request Forgery) based on HTTP Referer (POST requests only)
* API
* Compatible with lighttpd
* Misc.
* Changed lazyload implementation
* Support of HTML5 notifications for new upcoming articles
* Add option to stay logged in
* Bug fixes in export function, add/remove users, keyboard shortcuts, etc.
## 2014-07-21 FreshRSS 0.7.3
* New options
* Add system of user queries which are shortcuts to filter the view
* New TTL option to limit the frequency at which feeds are refreshed (by cron or manual refresh button).
It is still possible to manually refresh an individual feed at a higher frequency.
* SQL
* Add support for SQLite (beta) in addition to MySQL
* SimplePie
* Complies with HTTP "301 Moved Permanently" responses by automatically updating the URL of feeds that have changed address.
* Themes
* Flat and Dark designs are based on same template file as Origine
* Statistics
* Refactor code
* Add an idle feed page
* Misc
* Several bug fixes
* Add confirmation option when marking all articles as read
* Fix some typo
## 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
* Lutilisateur par défaut (administrateur) peut créer et supprimer dautres utilisateurs
* Nécessite un contrôle daccès, soit :
* par le nouveau mode de connexion par formulaire (nom dutilisateur + mot de passe)
* relativement sûr même sans HTTPS (le mot de passe nest pas transmis en clair)
* requiert JavaScript et PHP 5.3+
* par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd)
* le nom dutilisateur HTTP doit correspondre au nom dutilisateur FreshRSS
* par Mozilla Persona, en renseignant ladresse courriel des utilisateurs
* Installateur supportant les mises à jour :
* Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/”
(voir réorganisation ci-dessous)
* Pour les versions suivantes, juste garder le répertoire “./data/”
* Rafraîchissement automatique du nombre darticles non lus toutes les deux minutes (utilise le cache HTTP à bon escient)
* Permet aussi de conserver la session valide, surtout dans le cas de Persona
* Nouvelle page de statistiques (nombres darticles par jour / catégorie)
* Importation OPML instantanée et plus tolérante
* Nouvelle gestion des favicons avec téléchargement en parallèle
* Nouvelles options
* Réorganisation des options
* Gestion des utilisateurs
* Améliorations partage vers Shaarli, Poche, Diaspora*, Facebook, Twitter, Google+, courriel
* Raccourci s par défaut
* Permet la suppression de tous les articles dun flux
* Option pour marquer les articles comme lus dès la réception
* Permet de configurer plus finement le nombre darticles minimum à conserver par flux
* Permet de modifier la description et ladresse dun flux RSS ainsi que le site Web associé
* Nouveau raccourci pour ouvrir/fermer un article (c par défaut)
* Boutons pour effacer les logs et pour purger les vieux articles
* Nouveaux filtres daffichage : seulement les articles favoris, et seulement les articles lus
* SQL :
* Nouveau moteur de recherche, aussi accessible depuis la vue mobile
* Mots clefs de recherche “intitle:”, “inurl:”, “author:”
* Les articles sont triés selon la date de leur ajout dans FreshRSS plutôt que la date déclarée (souvent erronée)
* Permet de marquer tout comme lu sans affecter les nouveaux articles arrivés en cours de lecture
* Permet une pagination efficace
* Refactorisation
* Les tables sont préfixées avec le nom dutilisateur afin de permettre le mode multi-utilisateurs
* Amélioration des performances
* Tolère un beaucoup plus grand nombre darticles
* Compression des données côté MySQL plutôt que côté PHP
* Incompatible avec la version 0.6 (nécessite une mise à jour grâce à linstallateur)
* Affichage de la taille de la base de données dans FreshRSS
* Correction problème de marquage de tous les favoris comme lus
* HTML5 :
* Support des balises HTML5 audio, video, et éléments associés
* Utilisation de preload="none", et réécriture correcte des adresses, aussi en HTTPS
* Protection HTML5 des iframe (sandbox="allow-scripts allow-same-origin")
* Filtrage des object et embed
* Chargement différé HTML5 (postpone="") pour iframe et video
* Chargement différé JavaScript pour iframe
* CSS :
* Nouveau thème sombre
* Chargement plus robuste des thèmes
* Meilleur support des longs titres darticles sur des écrans étroits
* Meilleure accessibilité
* FreshRSS fonctionne aussi en mode dégradé sans images (alternatives Unicode) et/ou sans CSS
* Diverses améliorations
* PHP :
* Encore plus tolérant pour les flux comportant des erreurs
* Mise à jour automatique de lURL du flux (en base de données) lorsque SimplePie découvre quelle a changé
* Meilleure gestion des caractères spéciaux dans différents cas
* Compatibilité PHP 5.5+ avec OPcache
* Amélioration des performances
* Chargement automatique des classes
* Alternative dans le cas dabsence de librairie JSON
* Pour le développement, le cache HTTP peut être désactivé en créant un fichier “./data/no-cache.txt”
* Réorganisation des fichiers et répertoires, en particulier :
* Tous les fichiers utilisateur sont dans “./data/” (y compris “cache”, “favicons”, et “log”)
* Déplacement de “./app/configuration/application.ini” vers “./data/config.php”
* Meilleure sécurité et compatibilité
* Déplacement de “./public/data/Configuration.array.php” vers “./data/*_user.php”
* Déplacement de “./public/” vers “./p/”
* Déplacement de “./public/index.php” vers “./p/i/index.php” (voir cookie ci-dessous)
* Déplacement de “./actualize_script.php” vers “./app/actualize_script.php” (pour une meilleure sécurité)
* Pensez à mettre à jour votre Cron !
* Divers :
* Nouvelle politique de cookie de session (témoin de connexion)
* Utilise un nom poli “FreshRSS” (évite des problèmes avec certains filtres)
* Se limite au répertoire “./FreshRSS/p/i/” pour de meilleures performances HTTP
* Les images, CSS, scripts sont servis sans cookie
* Utilise “HttpOnly” pour plus de sécurité
* Nouvel “agent utilisateur” exposé lors du téléchargement des flux, par exemple :
* “FreshRSS/0.7 (Linux; http://freshrss.org) SimplePie/1.3.1”
* Script dactualisation avec plus de messages
* Sur la sortie standard, ainsi que dans le log système (syslog)
* Affichage du numéro de version dans "À propos"
## 2013-11-21 FreshRSS 0.6.1
* Corrige bug chargement du JavaScript
* Affiche un message derreur plus explicite si fichier de configuration inaccessible
## 2013-11-17 FreshRSS 0.6
* Nettoyage du code JavaScript + optimisations
* Utilisation dadresses relatives
* Amélioration des performances coté client
* Mise à jour automatique du nombre darticles non lus
* Corrections traductions
* Mise en cache de FreshRSS
* Amélioration des retours utilisateur lorsque la configuration nest pas bonne
* Actualisation des flux après une importation OPML
* Meilleure prise en charge des flux RSS invalides
* Amélioration de la vue globale
* Possibilité de personnaliser les icônes de lecture
* Suppression de champs lors de linstallation (base_url et sel)
* Correction bugs divers
## 2013-10-15 FreshRSS 0.5.1
* Correction bug des catégories disparues
* Correction traduction i18n/fr et i18n/en
* Suppression de certains appels à la feuille de style fallback.css
## 2013-10-12 FreshRSS 0.5.0
* Possibilité dinterdire la lecture anonyme
* Option pour garder lhistorique dun flux
* Lors dun clic sur “Marquer tous les articles comme lus”, FreshRSS peut désormais sauter à la prochaine catégorie / prochain flux avec des articles non lus.
* Ajout dun token pour accéder aux flux RSS générés par FreshRSS sans nécessiter de connexion
* Possibilité de partager vers Facebook, Twitter et Google+
* Possibilité de changer de thème
* Le menu de navigation (article précédent / suivant / haut de page) a été ajouté à la vue non mobile
* La police OpenSans est désormais appliquée
* Amélioration de la page de configuration
* Une meilleure sortie pour limprimante
* Quelques retouches du design par défaut
* Les vidéos ne dépassent plus du cadre de lécran
* Nouveau logo
* Possibilité dajouter un préfixe aux tables lors de linstallation
* Ajout dun champ en base de données keep_history à la table feed
* Si possible, création automatique de la base de données si elle nexiste pas lors de linstallation
* Lutilisation dUTF-8 est forcée
* Le marquage automatique au défilement de la page a été amélioré
* La vue globale a été énormément améliorée et est beaucoup plus utile
* Amélioration des requêtes SQL
* Amélioration du JavaScript
* Correction bugs divers
## 2013-07-02 FreshRSS 0.4.0
* Correction bug et ajout notification lors de la phase dinstallation
* Affichage derreur si fichier OPML invalide
* Les tags sont maintenant cliquables pour filtrer dessus
* Amélioration vue mobile (boutons plus gros et ajout dune barre de navigation)
* Possibilité dajouter directement un flux dans une catégorie dès son ajout
* Affichage des flux en erreur (injoignable par exemple) en rouge pour les différencier
* Possibilité de changer les noms des flux
* Ajout dune option (désactivable donc) pour charger les images en lazyload permettant de ne pas charger toutes les images dun coup
* Le framework Minz est maintenant directement inclus dans larchive (plus besoin de passer par ./build.sh)
* Amélioration des performances pour la récupération des flux tronqués
* Possibilité dimporter des flux sans catégorie lors de limport OPML
* Suppression de “lAPI” (qui était de toute façon très basique) et de la fonctionnalité de “notes”
* Amélioration de la recherche (garde en mémoire si lon a sélectionné une catégorie) par exemple
* Modification apparence des balises hr et pre
* Meilleure vérification des champs de formulaire
* Remise en place du mode “endless” (permettant de simplement charger les articles qui suivent plutôt que de charger une nouvelle page)
* Ajout dune page de visualisation des logs
* Ajout dune option pour optimiser la BDD (diminue sa taille)
* Ajout des vues lecture et globale (assez basique)
* Les vidéos YouTube ne débordent plus du cadre sur les petits écrans
* Ajout dune option pour marquer les articles comme lus lors du défilement (et suppression de celle au chargement de la page)
## 2013-05-05 FreshRSS 0.3.0
* Fallback pour les icônes SVG (utilisation de PNG à la place)
* Fallback pour les propriétés CSS3 (utilisation de préfixes)
* Affichage des tags associés aux articles
* Internationalisation de lapplication (gestion des langues anglaise et française)
* Gestion des flux protégés par authentification HTTP
* Mise en cache des favicons
* Création dun logo *temporaire*
* Affichage des vidéos dans les articles
* Gestion de la recherche et filtre par tags pleinement fonctionnels
* Création dun vrai script CRON permettant de mettre tous les flux à jour
* Correction bugs divers
## 2013-04-17 FreshRSS 0.2.0
* Création dun installateur
* Actualisation des flux en Ajax
* Partage par mail et Shaarli ajouté
* Export par flux RSS
* Possibilité de vider une catégorie
* Possibilité de sélectionner les catégories en vue mobile
* Les flux peuvent être sortis du flux principal (système de priorité)
* Amélioration ajout / import / export des flux
* Amélioration actualisation (meilleure gestion des erreurs)
* Améliorations CSS
* Changements dans la base de données
* Màj de la librairie SimplePie
* Flux sans auteurs gérés normalement
* Correction bugs divers
## 2013-04-08 FreshRSS 0.1.0
* “Première” version

View file

@ -1,57 +0,0 @@
# How to contribute to FreshRSS?
## Join us on the mailing lists
Do you want to ask us some questions? Do you want to discuss with us? Don't hesitate to subscribe to our mailing lists!
- The first mailing is destined to generic information, it should be adapted to users. [Join mailing@freshrss.org](https://freshrss.org/mailman/listinfo/mailing).
- The second mailing is mainly for developers. [Join dev@freshrss.org](https://freshrss.org/mailman/listinfo/dev)
## Report a bug
You found a bug? Don't panic, here are some steps to report it easily:
1. Search for it on [the bug tracker](https://github.com/FreshRSS/FreshRSS/issues) (don't forget to use the search bar).
2. If you find a similar bug, don't hesitate to post a comment to add more importance to the related ticket.
3. If you didn't find it, [open a new ticket](https://github.com/FreshRSS/FreshRSS/issues/new).
If you have to create a new ticket, try to apply the following advices:
- Give an explicit title to the ticket so it will be easier to find it later.
- Be as exhaustive as possible in the description: what did you do? What is the bug? What are the steps to reproduce the bug?
- We also need some information:
+ Your FreshRSS version (on about page or `constants.php` file)
+ Your server configuration: type of hosting, PHP version
+ Your storage system (MySQL / MariaDB or SQLite)
+ If possible, the related logs (PHP logs and FreshRSS logs under `data/users/your_user/log.txt`)
## Fix a bug
Did you want to fix a bug? To keep a great coordination between collaborators, you will have to follow these indications:
1. Be sure the bug is associated to a ticket and say you work on it.
2. [Fork this project repository](https://help.github.com/articles/fork-a-repo/).
3. [Create a new branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/). The name of the branch must be explicit and being prefixed by the related ticket id. For instance, `783-contributing-file` to fix [ticket #783](https://github.com/FreshRSS/FreshRSS/issues/783).
4. Make your changes to your fork and [send a pull request](https://help.github.com/articles/using-pull-requests/) on the **dev branch**.
If you have to write code, please follow [our coding style recommendations](http://doc2.freshrss.org/en/Developer_documentation/First_steps/Coding_style).
**Tip:** if you are searching for bugs easy to fix, have a look at the « [New comers](https://github.com/FreshRSS/FreshRSS/labels/New%20comers) » ticket label.
## Submit an idea
You have great ideas, yes! Don't be shy and open [a new ticket](https://github.com/FreshRSS/FreshRSS/issues/new) on our bug tracker to ask if we can implement it. The greatest ideas often come from the shyest suggestions!
If your idea is nice, we'll have a look at it.
## Contribute to internationalization (i18n)
If you want to improve internationalization, please open a new ticket first and follow indications from « Fix a bug » section.
Translations are present in the subdirectories of `./app/i18n/`.
We are working on a better way to handle internationalization but don't hesitate to suggest any idea!
## Contribute to documentation
The documentation needs a lot of improvements in order to be more useful to new contributors and we are working on it. If you want to give some help, meet us on [the dedicated repository](https://github.com/FreshRSS/documentation)!

View file

@ -1,25 +0,0 @@
This is a credit file of people who have [contributed to FreshRSS](https://github.com/FreshRSS/FreshRSS/graphs/contributors) with, at least,
one commit on the FreshRSS repository (at https://github.com/FreshRSS/FreshRSS).
Please note a commit on THIS specific file is not considered as a contribution
(too easy!). Its purpose is to show that even the smallest contribution is important.
People are sorted by name so please keep this order.
---
* [Alexandre Alapetite](https://github.com/Alkarex): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Alkarex), [Web](http://alexandre.alapetite.fr/)
* [Alexis Degrugillier](https://github.com/aledeg): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=aledeg)
* [Alwaysin](https://github.com/Alwaysin): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Alwaysin)
* [Amaury Carrade](https://github.com/AmauryCarrade): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=AmauryCarrade)
* [ealdraed](https://github.com/ealdraed): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ealdraed)
* [Luc Didry](https://github.com/ldidry): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ldidry)
* [Marcus Rohrmoser](https://github.com/mro):
[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=mro)
* [Marien Fressinaud](https://github.com/marienfressinaud): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marienfressinaud), [Web](http://marienfressinaud.fr/)
* [Melvyn Laïly](https://github.com/yaurthek): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=yaurthek)
* [Nicolas Elie](https://github.com/nicolaselie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicolaselie)
* [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop)
* [Tets42](https://github.com/Tets42):
[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Tets42)
* [thomasE1993](https://github.com/thomasE1993):
[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=thomasE1993)
* [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue)

View file

@ -1,662 +0,0 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<http://www.gnu.org/licenses/>.

View file

@ -1,138 +0,0 @@
* [English version](README.md)
# FreshRSS
FreshRSS est un agrégateur de flux RSS à auto-héberger à limage de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/).
Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.
Il permet de gérer plusieurs utilisateurs, et dispose dun mode de lecture anonyme.
Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles.
* Site officiel : http://freshrss.org
* Démo : http://demo.freshrss.org/
* Licence : [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
# Téléchargement
Voir la [liste des versions](../../releases).
## Note sur les branches
**Ce logiciel est en développement permanent !** Veuillez vous assurer d'utiliser la branche qui vous correspond :
* Utilisez [la branche master](https://github.com/FreshRSS/FreshRSS/tree/master/) si vous visez la stabilité.
* [La branche beta](https://github.com/FreshRSS/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 veulent aider à tester les toutes dernières fonctionnalités, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras !
# Avertissements
Cette application a été développée pour sadapter principalement à des besoins personnels, et aucune garantie n'est fournie.
Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues).
Nous sommes une communauté amicale.
# Prérequis
* Serveur modeste, par exemple sous Linux ou Windows
* Fonctionne même sur un Raspberry Pi 1 avec des temps de réponse < 1s (testé sur 150 flux, 22k articles)
* Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres)
* PHP 5.3+ (PHP 5.3.7+ recommandé, et PHP 5.5+ pour les performances, et PHP 7+ pour dencore meilleures performances)
* Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés)
* Recommandés : [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib)
* Inclus par défaut : [DOM](http://php.net/dom), [XML](http://php.net/xml)…
* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+
* Un navigateur Web récent tel Firefox, Chrome, Opera, Safari. [Internet Explorer ne fonctionne plus, mais ce sera corrigé](https://github.com/FreshRSS/FreshRSS/issues/772).
* Fonctionne aussi sur mobile
* Lentête HTTP `Referer` ne doit pas être désactivé pour pouvoir utiliser le formulaire de connexion
![Capture décran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
# Installation
1. Récupérez lapplication FreshRSS via la commande git ou [en téléchargeant larchive](../releases)
2. Placez lapplication sur votre serveur (la partie à exposer au Web est le répertoire `./p/`)
3. Le serveur Web doit avoir les droits décriture dans le répertoire `./data/`
4. Accédez à FreshRSS à travers votre navigateur Web et suivez les instructions dinstallation
5. Tout devrait fonctionner :) En cas de problème, nhésitez pas à me contacter.
6. Des paramètres de configuration avancée peuvent être accédés depuis [config.php](./data/config.default.php).
## Installation automatisée
[![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/gh-pages/img/deploy.png)](https://dfabric.github.io/DPlatform-ShellCore)
## Exemple dinstallation complète sur Linux Debian/Ubuntu
```sh
# Si vous utilisez le serveur Web Apache (sinon il faut un autre serveur Web)
sudo apt-get install apache2
sudo a2enmod headers expires rewrite ssl
# (optionnel) Si vous voulez un serveur de base de données MySQL
sudo apt-get install mysql-server mysql-client php5-mysql
# Composants principaux (git est optionnel si vous déployez manuellement les fichiers dinstallation)
sudo apt-get install git php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite
# Redémarrage du serveur Web
sudo service apache2 restart
# Pour FreshRSS lui-même
cd /usr/share/
sudo git clone https://github.com/FreshRSS/FreshRSS.git
# Mettre les droits daccès pour le serveur Web
cd FreshRSS
sudo chown -R :www-data .
sudo chmod -R g+w ./data/
# Publier FreshRSS dans votre répertoire HTML public
sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
# Naviguez vers http://example.net/FreshRSS pour terminer linstallation.
# (Si vous le faite depuis localhost, vous pourrez avoir à ajuster le réglage de votre adresse publique)
# Mettre à jour FreshRSS vers une nouvelle version
cd /usr/share/FreshRSS
sudo git reset --hard
sudo git pull
sudo chown -R :www-data .
sudo chmod -R g+w ./data/
```
# Contrôle daccès
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)
* Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant.
# Rafraîchissement automatique des flux
* Vous pouvez ajouter une tâche Cron lançant régulièrement le script dactualisation automatique des flux.
Consultez la documentation de Cron de votre système dexploitation ([Debian/Ubuntu](http://doc.ubuntu-fr.org/cron), [Red Hat/Fedora](http://doc.fedora-fr.org/wiki/CRON_:_Configuration_de_t%C3%A2ches_automatis%C3%A9es), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](http://wiki.gentoo.org/wiki/Cron/fr), [Arch Linux](http://wiki.archlinux.fr/Cron)…).
Cest une bonne idée dutiliser le même utilisateur que votre serveur Web (souvent “www-data”).
Par exemple, pour exécuter le script toutes les heures :
```
7 * * * * php /votre-chemin/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
```
# Conseils
* Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`.
* En particulier, les données personnelles se trouvent dans le répertoire `./data/`.
* Le fichier `./constants.php` définit les chemins daccès aux répertoires clés de lapplication. Si vous les bougez, tout se passe ici.
* En cas de problème, les logs peuvent être utile à lire, soit depuis linterface de FreshRSS, soit manuellement depuis `./data/log/*.log`.
# Sauvegarde
* Il faut conserver vos fichiers `./data/config.php` ainsi que `./data/*_user.php` et éventuellement `./data/persona/`
* Vous pouvez exporter votre liste de flux depuis FreshRSS au format OPML
* Pour sauvegarder les articles eux-mêmes, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL :
```bash
mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
```
# Bibliothèques incluses
* [SimplePie](http://simplepie.org/)
* [MINZ](https://github.com/marienfressinaud/MINZ)
* [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)
* [phpQuery](http://code.google.com/p/phpquery/)
## Si les fonctions natives ne sont pas disponibles
* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198)
* [password_compat](https://github.com/ircmaxell/password_compat)

View file

@ -1,138 +0,0 @@
* [Version française](README.fr.md)
# FreshRSS
FreshRSS is a self-hosted RSS feed aggregator such as [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/).
It is at the same time lightweight, easy to work with, powerful and customizable.
It is a multi-user application with an anonymous reading mode.
It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites.
* Official website: http://freshrss.org
* Demo: http://demo.freshrss.org/
* License: [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
![FreshRSS logo](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
# Releases
See the [list of releases](../../releases).
## Note on branches
**This application is under continuous development!** Please use the branch that suits your needs:
* Use [the master branch](https://github.com/FreshRSS/FreshRSS/tree/master/) if you need a stable version.
* [The beta branch](https://github.com/FreshRSS/FreshRSS/tree/beta) is the default branch: new features are added on a monthly basis.
* For developers and tech savvy persons willing to help testing the latest features, [the dev branch](https://github.com/FreshRSS/FreshRSS/tree/dev) is waiting for you!
# Disclaimer
This application was developed to fulfil personal needs primarily, and comes with absolutely no warranty.
Feature requests, bug reports, and other contributions are welcome. The best way is to [open issues on GitHub](https://github.com/FreshRSS/FreshRSS/issues).
We are a friendly community.
# Requirements
* Light server running Linux or Windows
* It even works on Raspberry Pi 1 with response time under a second (tested with 150 feeds, 22k articles)
* A web server: Apache2 (recommended), nginx, lighttpd (not tested on others)
* PHP 5.3+ (PHP 5.3.7+ recommended, and PHP 5.5+ for performance, and PHP 7 for even higher performance)
* Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names)
* Recommended extensions: [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib)
* Enabled by default: [DOM](http://php.net/dom), [XML](http://php.net/xml)…
* MySQL 5.0.3+ (recommended) or SQLite 3.7.4+
* A recent browser like Firefox, Chrome, Opera, Safari. [Internet Explorer currently not supported, but support will come back](https://github.com/FreshRSS/FreshRSS/issues/772).
* Works on mobile
* The browser HTTP `Referer` header must not be disabled when using the form login method
![FreshRSS screenshot](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
# Installation
1. Get FreshRSS with git or [by downloading the archive](https://github.com/FreshRSS/FreshRSS/archive/master.zip)
2. Dump the application on your server (expose only the `./p/` folder)
3. Add write access on `./data/` folder to the webserver user
4. Access FreshRSS with your browser and follow the installation process
5. Everything should be working :) If you encounter any problem, feel free to contact me.
6. Advanced configuration settings can be seen in [config.php](./data/config.default.php).
## Automated install
[![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/gh-pages/img/deploy.png)](https://dfabric.github.io/DPlatform-ShellCore)
## Example of full installation on Linux Debian/Ubuntu
```sh
# If you use an Apache Web server (otherwise you need another Web server)
sudo apt-get install apache2
sudo a2enmod headers expires rewrite ssl
# (Optional) If you want a MySQL database server
sudo apt-get install mysql-server mysql-client php5-mysql
# Main components (git is optional if you manually download the installation files)
sudo apt-get install git php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite
# Restart Web server
sudo service apache2 restart
# For FreshRSS itself
cd /usr/share/
sudo git clone https://github.com/FreshRSS/FreshRSS.git
# Set the rights so that your Web browser can access the files
cd FreshRSS
sudo chown -R :www-data .
sudo chmod -R g+w ./data/
# Publish FreshRSS in your public HTML directory
sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
# Navigate to http://example.net/FreshRSS to complete the installation.
# (If you do it from localhost, you may have to adjust the setting of your public address later)
# Update to a newer version of FreshRSS
cd /usr/share/FreshRSS
sudo git reset --hard
sudo git pull
sudo chown -R :www-data .
sudo chmod -R g+w ./data/
```
# Access control
It is needed for the multi-user mode to limit access to FreshRSS. You can:
* use form authentication (need JavaScript and PHP 5.3.7+, works with some PHP 5.3.3+)
* use [Mozilla Persona](https://login.persona.org/about) authentication included in FreshRSS
* use HTTP authentication supported by your web server
* See [Apache documentation](http://httpd.apache.org/docs/trunk/howto/auth.html)
* In that case, create a `./p/i/.htaccess` file with a matching `.htpasswd` file.
# Automatic feed update
* You can add a Cron job to launch the update script.
Check the Cron documentation related to your distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron)…).
Its a good idea to use the Web server user.
For example, if you want to run the script every hour:
```
7 * * * * php /your-path/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
```
# Advices
* For a better security, expose only the `./p/` folder on the web.
* Be aware that the `./data/` folder contains all personal data, so it is a bad idea to expose it.
* The `./constants.php` file defines access to application folder. If you want to customize your installation, every thing happens here.
* If you encounter any problem, logs are accessible from the interface or manually in `./data/log/*.log` files.
# Backup
* You need to keep `./data/config.php`, `./data/*_user.php` and `./data/persona/` files
* You can export your feed list in OPML format from FreshRSS
* To save articles, you can use [phpMyAdmin](http://www.phpmyadmin.net) or MySQL tools:
```bash
mysqldump -u user -p --databases freshrss > freshrss.sql
```
# Included libraries
* [SimplePie](http://simplepie.org/)
* [MINZ](https://github.com/marienfressinaud/MINZ)
* [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)
## Only for some options
* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js)
* [phpQuery](http://code.google.com/p/phpquery/)
## If native functions are not available
* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198)
* [password_compat](https://github.com/ircmaxell/password_compat)

View file

@ -1,3 +0,0 @@
Order Allow,Deny
Deny from all
Satisfy all

View file

@ -1,358 +0,0 @@
<?php
/**
* This controller handles action about authentication.
*/
class FreshRSS_auth_Controller extends Minz_ActionController {
/**
* This action handles authentication management page.
*
* Parameters are:
* - token (default: current token)
* - anon_access (default: false)
* - anon_refresh (default: false)
* - auth_type (default: none)
* - unsafe_autologin (default: false)
* - api_enabled (default: false)
*
* @todo move unsafe_autologin in an extension.
*/
public function indexAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
Minz_View::prependTitle(_t('admin.auth.title') . ' · ');
if (Minz_Request::isPost()) {
$ok = true;
$current_token = FreshRSS_Context::$user_conf->token;
$token = Minz_Request::param('token', $current_token);
FreshRSS_Context::$user_conf->token = $token;
$ok &= FreshRSS_Context::$user_conf->save();
$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 != FreshRSS_Context::$system_conf->allow_anonymous ||
$auth_type != FreshRSS_Context::$system_conf->auth_type ||
$anon_refresh != FreshRSS_Context::$system_conf->allow_anonymous_refresh ||
$unsafe_autologin != FreshRSS_Context::$system_conf->unsafe_autologin_enabled ||
$api_enabled != FreshRSS_Context::$system_conf->api_enabled) {
// TODO: test values from form
FreshRSS_Context::$system_conf->auth_type = $auth_type;
FreshRSS_Context::$system_conf->allow_anonymous = $anon;
FreshRSS_Context::$system_conf->allow_anonymous_refresh = $anon_refresh;
FreshRSS_Context::$system_conf->unsafe_autologin_enabled = $unsafe_autologin;
FreshRSS_Context::$system_conf->api_enabled = $api_enabled;
$ok &= FreshRSS_Context::$system_conf->save();
}
invalidateHttpCache();
if ($ok) {
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'auth', 'a' => 'index'));
} else {
Minz_Request::bad(_t('feedback.conf.error'),
array('c' => 'auth', 'a' => 'index'));
}
}
}
/**
* This action handles the login page.
*
* It forwards to the correct login page (form or Persona) or main page if
* the user is already connected.
*/
public function loginAction() {
if (FreshRSS_Auth::hasAccess()) {
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
}
$auth_type = FreshRSS_Context::$system_conf->auth_type;
switch ($auth_type) {
case 'form':
Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin'));
break;
case 'persona':
Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin'));
break;
case 'http_auth':
case 'none':
// It should not happened!
Minz_Error::error(404);
default:
// TODO load plugin instead
Minz_Error::error(404);
}
}
/**
* This action handles form login page.
*
* If this action is reached through a POST request, username and password
* are compared to login the current user.
*
* Parameters are:
* - nonce (default: false)
* - username (default: '')
* - challenge (default: '')
* - keep_logged_in (default: false)
*
* @todo move unsafe autologin in an extension.
*/
public function formLoginAction() {
invalidateHttpCache();
$file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
if (Minz_Request::isPost()) {
$nonce = Minz_Session::param('nonce');
$username = Minz_Request::param('username', '');
$challenge = Minz_Request::param('challenge', '');
$conf = get_user_configuration($username);
if (is_null($conf)) {
Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false);
return;
}
$ok = FreshRSS_FormAuth::checkCredentials(
$username, $conf->passwordHash, $nonce, $challenge
);
if ($ok) {
// Set session parameter to give access to the user.
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $conf->passwordHash);
FreshRSS_Auth::giveAccess();
// Set cookie parameter if nedded.
if (Minz_Request::param('keep_logged_in')) {
FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash);
} else {
FreshRSS_FormAuth::deleteCookie();
}
// All is good, go back to the index.
Minz_Request::good(_t('feedback.auth.login.success'),
array('c' => 'index', 'a' => 'index'));
} else {
Minz_Log::warning('Password mismatch for' .
' user=' . $username .
', nonce=' . $nonce .
', c=' . $challenge);
Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false);
}
} elseif (FreshRSS_Context::$system_conf->unsafe_autologin_enabled) {
$username = Minz_Request::param('u', '');
$password = Minz_Request::param('p', '');
Minz_Request::_param('p');
if (!$username) {
return;
}
$conf = get_user_configuration($username);
if (is_null($conf)) {
return;
}
if (!function_exists('password_verify')) {
include_once(LIB_PATH . '/password_compat.php');
}
$s = $conf->passwordHash;
$ok = password_verify($password, $s);
unset($password);
if ($ok) {
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $s);
FreshRSS_Auth::giveAccess();
Minz_Request::good(_t('feedback.auth.login.success'),
array('c' => 'index', 'a' => 'index'));
} else {
Minz_Log::warning('Unsafe password mismatch for user ' . $username);
Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false);
}
}
}
/**
* This action handles Persona login page.
*
* If this action is reached through a POST request, assertion from Persona
* is verificated and user connected if all is ok.
*
* Parameter is:
* - assertion (default: false)
*
* @todo: Persona system should be moved to a plugin
*/
public function personaLoginAction() {
$this->view->res = false;
if (Minz_Request::isPost()) {
$this->view->_useLayout(false);
$assert = Minz_Request::param('assertion');
$url = 'https://verifier.login.persona.org/verify';
$params = 'assertion=' . $assert . '&audience=' .
urlencode(Minz_Url::display(null, 'php', true));
$ch = curl_init();
$options = array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_POST => 2,
CURLOPT_POSTFIELDS => $params
);
curl_setopt_array($ch, $options);
$result = curl_exec($ch);
curl_close($ch);
$res = json_decode($result, true);
$login_ok = false;
$reason = '';
if ($res['status'] === 'okay') {
$email = filter_var($res['email'], FILTER_VALIDATE_EMAIL);
if ($email != '') {
$persona_file = DATA_PATH . '/persona/' . $email . '.txt';
if (($current_user = @file_get_contents($persona_file)) !== false) {
$current_user = trim($current_user);
$conf = get_user_configuration($current_user);
if (!is_null($conf)) {
$login_ok = strcasecmp($email, $conf->mail_login) === 0;
} else {
$reason = 'Invalid configuration for user ' .
'[' . $current_user . ']';
}
}
} else {
$reason = 'Invalid email format [' . $res['email'] . ']';
}
} else {
$reason = $res['reason'];
}
if ($login_ok) {
Minz_Session::_param('currentUser', $current_user);
Minz_Session::_param('mail', $email);
FreshRSS_Auth::giveAccess();
invalidateHttpCache();
} else {
Minz_Log::warning($reason);
$res = array();
$res['status'] = 'failure';
$res['reason'] = _t('feedback.auth.login.invalid');
}
header('Content-Type: application/json; charset=UTF-8');
$this->view->res = $res;
}
}
/**
* This action removes all accesses of the current user.
*/
public function logoutAction() {
invalidateHttpCache();
FreshRSS_Auth::removeAccess();
Minz_Request::good(_t('feedback.auth.logout.success'),
array('c' => 'index', 'a' => 'index'));
}
/**
* This action resets the authentication system.
*
* After reseting, form auth is set by default.
*/
public function resetAction() {
Minz_View::prependTitle(_t('admin.auth.title_reset') . ' · ');
Minz_View::appendScript(Minz_Url::display(
'/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
));
$this->view->no_form = false;
// Enable changement of auth only if Persona!
if (FreshRSS_Context::$system_conf->auth_type != 'persona') {
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.auth.not_persona')
);
$this->view->no_form = true;
return;
}
$conf = get_user_configuration(FreshRSS_Context::$system_conf->default_user);
if (is_null($conf)) {
return;
}
// Admin user must have set its master password.
if (!$conf->passwordHash) {
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.auth.no_password_set')
);
$this->view->no_form = true;
return;
}
invalidateHttpCache();
if (Minz_Request::isPost()) {
$nonce = Minz_Session::param('nonce');
$username = Minz_Request::param('username', '');
$challenge = Minz_Request::param('challenge', '');
$ok = FreshRSS_FormAuth::checkCredentials(
$username, $conf->passwordHash, $nonce, $challenge
);
if ($ok) {
FreshRSS_Context::$system_conf->auth_type = 'form';
$ok = FreshRSS_Context::$system_conf->save();
if ($ok) {
Minz_Request::good(_t('feedback.auth.form.set'));
} else {
Minz_Request::bad(_t('feedback.auth.form.not_set'),
array('c' => 'auth', 'a' => 'reset'));
}
} else {
Minz_Log::warning('Password mismatch for' .
' user=' . $username .
', nonce=' . $nonce .
', c=' . $challenge);
Minz_Request::bad(_t('feedback.auth.login.invalid'),
array('c' => 'auth', 'a' => 'reset'));
}
}
}
/**
* This action gives possibility to a user to create an account.
*/
public function registerAction() {
if (max_registrations_reached()) {
Minz_Error::error(403);
}
Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · ');
}
}

View file

@ -1,194 +0,0 @@
<?php
/**
* Controller to handle actions relative to categories.
* User needs to be connected.
*/
class FreshRSS_category_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
$catDAO = new FreshRSS_CategoryDAO();
$catDAO->checkDefault();
}
/**
* This action creates a new category.
*
* Request parameter is:
* - new-category
*/
public function createAction() {
$catDAO = new FreshRSS_CategoryDAO();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
$limits = FreshRSS_Context::$system_conf->limits;
$this->view->categories = $catDAO->listCategories(false);
if (count($this->view->categories) >= $limits['max_categories']) {
Minz_Request::bad(_t('feedback.sub.category.over_max', $limits['max_categories']),
$url_redirect);
}
if (Minz_Request::isPost()) {
invalidateHttpCache();
$cat_name = Minz_Request::param('new-category');
if (!$cat_name) {
Minz_Request::bad(_t('feedback.sub.category.no_name'), $url_redirect);
}
$cat = new FreshRSS_Category($cat_name);
if ($catDAO->searchByName($cat->name()) != null) {
Minz_Request::bad(_t('feedback.sub.category.name_exists'), $url_redirect);
}
$values = array(
'id' => $cat->id(),
'name' => $cat->name(),
);
if ($catDAO->addCategory($values)) {
Minz_Request::good(_t('feedback.sub.category.created', $cat->name()), $url_redirect);
} else {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
/**
* This action updates the given category.
*
* Request parameters are:
* - id
* - name
*/
public function updateAction() {
$catDAO = new FreshRSS_CategoryDAO();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
invalidateHttpCache();
$id = Minz_Request::param('id');
$name = Minz_Request::param('name', '');
if (strlen($name) <= 0) {
Minz_Request::bad(_t('feedback.sub.category.no_name'), $url_redirect);
}
if ($catDAO->searchById($id) == null) {
Minz_Request::bad(_t('feedback.sub.category.not_exist'), $url_redirect);
}
$cat = new FreshRSS_Category($name);
$values = array(
'name' => $cat->name(),
);
if ($catDAO->updateCategory($id, $values)) {
Minz_Request::good(_t('feedback.sub.category.updated'), $url_redirect);
} else {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
/**
* This action deletes a category.
* Feeds in the given category are moved in the default category.
* Related user queries are deleted too.
*
* Request parameter is:
* - id (of a category)
*/
public function deleteAction() {
$feedDAO = FreshRSS_Factory::createFeedDao();
$catDAO = new FreshRSS_CategoryDAO();
$default_category = $catDAO->getDefault();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
invalidateHttpCache();
$id = Minz_Request::param('id');
if (!$id) {
Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect);
}
if ($id === $default_category->id()) {
Minz_Request::bad(_t('feedback.sub.category.not_delete_default'), $url_redirect);
}
if ($feedDAO->changeCategory($id, $default_category->id()) === false) {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
if ($catDAO->deleteCategory($id) === false) {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
// Remove related queries.
FreshRSS_Context::$user_conf->queries = remove_query_by_get(
'c_' . $id, FreshRSS_Context::$user_conf->queries);
FreshRSS_Context::$user_conf->save();
Minz_Request::good(_t('feedback.sub.category.deleted'), $url_redirect);
}
Minz_Request::forward($url_redirect, true);
}
/**
* This action deletes all the feeds relative to a given category.
* Feed-related queries are deleted.
*
* Request parameter is:
* - id (of a category)
*/
public function emptyAction() {
$feedDAO = FreshRSS_Factory::createFeedDao();
$url_redirect = array('c' => 'subscription', 'a' => 'index');
if (Minz_Request::isPost()) {
invalidateHttpCache();
$id = Minz_Request::param('id');
if (!$id) {
Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect);
}
// List feeds to remove then related user queries.
$feeds = $feedDAO->listByCategory($id);
if ($feedDAO->deleteFeedByCategory($id)) {
// TODO: Delete old favicons
// Remove related queries
foreach ($feeds as $feed) {
FreshRSS_Context::$user_conf->queries = remove_query_by_get(
'f_' . $feed->id(), FreshRSS_Context::$user_conf->queries);
}
FreshRSS_Context::$user_conf->save();
Minz_Request::good(_t('feedback.sub.category.emptied'), $url_redirect);
} else {
Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
}

View file

@ -1,331 +0,0 @@
<?php
/**
* Controller to handle every configuration options.
*/
class FreshRSS_configure_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
}
/**
* This action handles the display configuration page.
*
* It displays the display configuration page.
* If this action is reached through a POST request, it stores all new
* configuration values then sends a notification to the user.
*
* The options available on the page are:
* - language (default: en)
* - theme (default: Origin)
* - content width (default: thin)
* - display of read action in header
* - display of favorite action in header
* - display of date in header
* - display of open action in header
* - display of read action in footer
* - display of favorite action in footer
* - display of sharing action in footer
* - display of tags in footer
* - display of date in footer
* - display of open action in footer
* - html5 notification timeout (default: 0)
* Default values are false unless specified.
*/
public function displayAction() {
if (Minz_Request::isPost()) {
FreshRSS_Context::$user_conf->language = Minz_Request::param('language', 'en');
FreshRSS_Context::$user_conf->theme = Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme);
FreshRSS_Context::$user_conf->content_width = Minz_Request::param('content_width', 'thin');
FreshRSS_Context::$user_conf->topline_read = Minz_Request::param('topline_read', false);
FreshRSS_Context::$user_conf->topline_favorite = Minz_Request::param('topline_favorite', false);
FreshRSS_Context::$user_conf->topline_date = Minz_Request::param('topline_date', false);
FreshRSS_Context::$user_conf->topline_link = Minz_Request::param('topline_link', false);
FreshRSS_Context::$user_conf->bottomline_read = Minz_Request::param('bottomline_read', false);
FreshRSS_Context::$user_conf->bottomline_favorite = Minz_Request::param('bottomline_favorite', false);
FreshRSS_Context::$user_conf->bottomline_sharing = Minz_Request::param('bottomline_sharing', false);
FreshRSS_Context::$user_conf->bottomline_tags = Minz_Request::param('bottomline_tags', false);
FreshRSS_Context::$user_conf->bottomline_date = Minz_Request::param('bottomline_date', false);
FreshRSS_Context::$user_conf->bottomline_link = Minz_Request::param('bottomline_link', false);
FreshRSS_Context::$user_conf->html5_notif_timeout = Minz_Request::param('html5_notif_timeout', 0);
FreshRSS_Context::$user_conf->save();
Minz_Session::_param('language', FreshRSS_Context::$user_conf->language);
Minz_Translate::reset(FreshRSS_Context::$user_conf->language);
invalidateHttpCache();
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'display'));
}
$this->view->themes = FreshRSS_Themes::get();
Minz_View::prependTitle(_t('conf.display.title') . ' · ');
}
/**
* This action handles the reading configuration page.
*
* It displays the reading configuration page.
* If this action is reached through a POST request, it stores all new
* configuration values then sends a notification to the user.
*
* The options available on the page are:
* - number of posts per page (default: 10)
* - view mode (default: normal)
* - default article view (default: all)
* - load automatically articles
* - display expanded articles
* - display expanded categories
* - hide categories and feeds without unread articles
* - jump on next category or feed when marked as read
* - image lazy loading
* - stick open articles to the top
* - display a confirmation when reading all articles
* - auto remove article after reading
* - article order (default: DESC)
* - mark articles as read when:
* - displayed
* - opened on site
* - scrolled
* - received
* Default values are false unless specified.
*/
public function readingAction() {
if (Minz_Request::isPost()) {
FreshRSS_Context::$user_conf->posts_per_page = Minz_Request::param('posts_per_page', 10);
FreshRSS_Context::$user_conf->view_mode = Minz_Request::param('view_mode', 'normal');
FreshRSS_Context::$user_conf->default_view = Minz_Request::param('default_view', 'adaptive');
FreshRSS_Context::$user_conf->auto_load_more = Minz_Request::param('auto_load_more', false);
FreshRSS_Context::$user_conf->display_posts = Minz_Request::param('display_posts', false);
FreshRSS_Context::$user_conf->display_categories = Minz_Request::param('display_categories', false);
FreshRSS_Context::$user_conf->hide_read_feeds = Minz_Request::param('hide_read_feeds', false);
FreshRSS_Context::$user_conf->onread_jump_next = Minz_Request::param('onread_jump_next', false);
FreshRSS_Context::$user_conf->lazyload = Minz_Request::param('lazyload', false);
FreshRSS_Context::$user_conf->sticky_post = Minz_Request::param('sticky_post', false);
FreshRSS_Context::$user_conf->reading_confirm = Minz_Request::param('reading_confirm', false);
FreshRSS_Context::$user_conf->auto_remove_article = Minz_Request::param('auto_remove_article', false);
FreshRSS_Context::$user_conf->mark_updated_article_unread = Minz_Request::param('mark_updated_article_unread', false);
FreshRSS_Context::$user_conf->sort_order = Minz_Request::param('sort_order', 'DESC');
FreshRSS_Context::$user_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),
);
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'reading'));
}
Minz_View::prependTitle(_t('conf.reading.title') . ' · ');
}
/**
* This action handles the sharing configuration page.
*
* It displays the sharing configuration page.
* If this action is reached through a POST request, it stores all
* configuration values then sends a notification to the user.
*/
public function sharingAction() {
if (Minz_Request::isPost()) {
$params = Minz_Request::params();
FreshRSS_Context::$user_conf->sharing = $params['share'];
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'sharing'));
}
Minz_View::prependTitle(_t('conf.sharing.title') . ' · ');
}
/**
* This action handles the shortcut configuration page.
*
* It displays the shortcut configuration page.
* If this action is reached through a POST request, it stores all new
* configuration values then sends a notification to the user.
*
* The authorized values for shortcuts are letters (a to z), numbers (0
* to 9), function keys (f1 to f12), backspace, delete, down, end, enter,
* escape, home, insert, left, page down, page up, return, right, space,
* tab and up.
*/
public function shortcutAction() {
$list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter',
'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', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
'f10', 'f11', 'f12');
$this->view->list_keys = $list_keys;
if (Minz_Request::isPost()) {
$shortcuts = Minz_Request::param('shortcuts');
$shortcuts_ok = array();
foreach ($shortcuts as $key => $value) {
if (in_array($value, $list_keys)) {
$shortcuts_ok[$key] = $value;
}
}
FreshRSS_Context::$user_conf->shortcuts = $shortcuts_ok;
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
Minz_Request::good(_t('feedback.conf.shortcuts_updated'),
array('c' => 'configure', 'a' => 'shortcut'));
}
Minz_View::prependTitle(_t('conf.shortcut.title') . ' · ');
}
/**
* This action handles the archive configuration page.
*
* It displays the archive configuration page.
* If this action is reached through a POST request, it stores all new
* configuration values then sends a notification to the user.
*
* The options available on that page are:
* - duration to retain old article (default: 3)
* - number of article to retain per feed (default: 0)
* - refresh frequency (default: -2)
*
* @todo explain why the default value is -2 but this value does not
* exist in the drop-down list
*/
public function archivingAction() {
if (Minz_Request::isPost()) {
FreshRSS_Context::$user_conf->old_entries = Minz_Request::param('old_entries', 3);
FreshRSS_Context::$user_conf->keep_history_default = Minz_Request::param('keep_history_default', 0);
FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', -2);
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'archiving'));
}
Minz_View::prependTitle(_t('conf.archiving.title') . ' · ');
$entryDAO = FreshRSS_Factory::createEntryDao();
$this->view->nb_total = $entryDAO->count();
$this->view->size_user = $entryDAO->size();
if (FreshRSS_Auth::hasAccess('admin')) {
$this->view->size_total = $entryDAO->size(true);
}
}
/**
* This action handles the user queries configuration page.
*
* If this action is reached through a POST request, it stores all new
* configuration values then sends a notification to the user then
* redirect to the same page.
* If this action is not reached through a POST request, it displays the
* configuration page and verifies that every user query is runable by
* checking if categories and feeds are still in use.
*/
public function queriesAction() {
$category_dao = new FreshRSS_CategoryDAO();
$feed_dao = FreshRSS_Factory::createFeedDao();
if (Minz_Request::isPost()) {
$params = Minz_Request::param('queries', array());
foreach ($params as $key => $query) {
if (!$query['name']) {
$query['name'] = _t('conf.query.number', $key + 1);
}
$queries[] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
FreshRSS_Context::$user_conf->queries = $queries;
FreshRSS_Context::$user_conf->save();
Minz_Request::good(_t('feedback.conf.updated'),
array('c' => 'configure', 'a' => 'queries'));
} else {
$this->view->queries = array();
foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
$this->view->queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
}
Minz_View::prependTitle(_t('conf.query.title') . ' · ');
}
/**
* This action handles the creation of a user query.
*
* It gets the GET parameters and stores them in the configuration query
* storage. Before it is saved, the unwanted parameters are unset to keep
* lean data.
*/
public function addQueryAction() {
$category_dao = new FreshRSS_CategoryDAO();
$feed_dao = FreshRSS_Factory::createFeedDao();
$queries = array();
foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
$queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao);
}
$params = Minz_Request::params();
$params['url'] = Minz_Url::display(array('params' => $params));
$params['name'] = _t('conf.query.number', count($queries) + 1);
$queries[] = new FreshRSS_UserQuery($params, $feed_dao, $category_dao);
FreshRSS_Context::$user_conf->queries = $queries;
FreshRSS_Context::$user_conf->save();
Minz_Request::good(_t('feedback.conf.query_created', $query['name']),
array('c' => 'configure', 'a' => 'queries'));
}
/**
* This action handles the system configuration page.
*
* It displays the system configuration page.
* If this action is reach through a POST request, it stores all new
* configuration values then sends a notification to the user.
*
* The options available on the page are:
* - user limit (default: 1)
* - user category limit (default: 16384)
* - user feed limit (default: 16384)
*/
public function systemAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
if (Minz_Request::isPost()) {
$limits = FreshRSS_Context::$system_conf->limits;
$limits['max_registrations'] = Minz_Request::param('max-registrations', 1);
$limits['max_feeds'] = Minz_Request::param('max-feeds', 16384);
$limits['max_categories'] = Minz_Request::param('max-categories', 16384);
FreshRSS_Context::$system_conf->limits = $limits;
FreshRSS_Context::$system_conf->title = Minz_Request::param('instance-name', 'FreshRSS');
FreshRSS_Context::$system_conf->auto_update_url = Minz_Request::param('auto-update-url', false);
FreshRSS_Context::$system_conf->save();
invalidateHttpCache();
Minz_Session::_param('notification', array(
'type' => 'good',
'content' => _t('feedback.conf.updated')
));
}
}
}

View file

@ -1,192 +0,0 @@
<?php
/**
* Controller to handle every entry actions.
*/
class FreshRSS_entry_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
// If ajax request, we do not print layout
$this->ajax = Minz_Request::param('ajax');
if ($this->ajax) {
$this->view->_useLayout(false);
Minz_Request::_param('ajax');
}
}
/**
* Mark one or several entries as read (or not!).
*
* If request concerns several entries, it MUST be a POST request.
* If request concerns several entries, only mark them as read is available.
*
* Parameters are:
* - id (default: false)
* - get (default: false) /(c_\d+|f_\d+|s|a)/
* - nextGet (default: $get)
* - idMax (default: 0)
* - is_read (default: true)
*/
public function readAction() {
$id = Minz_Request::param('id');
$get = Minz_Request::param('get');
$next_get = Minz_Request::param('nextGet', $get);
$id_max = Minz_Request::param('idMax', 0);
$params = array();
$entryDAO = FreshRSS_Factory::createEntryDao();
if ($id === false) {
// id is false? It MUST be a POST request!
if (!Minz_Request::isPost()) {
Minz_Request::bad(_t('feedback.access.not_found'), array('c' => 'index', 'a' => 'index'));
return;
}
if (!$get) {
// No get? Mark all entries as read (from $id_max)
$entryDAO->markReadEntries($id_max);
} else {
$type_get = $get[0];
$get = substr($get, 2);
switch($type_get) {
case 'c':
$entryDAO->markReadCat($get, $id_max);
break;
case 'f':
$entryDAO->markReadFeed($get, $id_max);
break;
case 's':
$entryDAO->markReadEntries($id_max, true);
break;
case 'a':
$entryDAO->markReadEntries($id_max);
break;
}
if ($next_get !== 'a') {
// Redirect to the correct page (category, feed or starred)
// Not "a" because it is the default value if nothing is
// given.
$params['get'] = $next_get;
}
}
} else {
$is_read = (bool)(Minz_Request::param('is_read', true));
$entryDAO->markRead($id, $is_read);
}
if (!$this->ajax) {
Minz_Request::good(_t('feedback.sub.feed.marked_read'), array(
'c' => 'index',
'a' => 'index',
'params' => $params,
), true);
}
}
/**
* This action marks an entry as favourite (bookmark) or not.
*
* Parameter is:
* - id (default: false)
* - is_favorite (default: true)
* If id is false, nothing happened.
*/
public function bookmarkAction() {
$id = Minz_Request::param('id');
$is_favourite = (bool)Minz_Request::param('is_favorite', true);
if ($id !== false) {
$entryDAO = FreshRSS_Factory::createEntryDao();
$entryDAO->markFavorite($id, $is_favourite);
}
if (!$this->ajax) {
Minz_Request::forward(array(
'c' => 'index',
'a' => 'index',
), true);
}
}
/**
* This action optimizes database to reduce its size.
*
* This action shouldbe reached by a POST request.
*
* @todo move this action in configure controller.
* @todo call this action through web-cron when available
*/
public function optimizeAction() {
$url_redirect = array(
'c' => 'configure',
'a' => 'archiving',
);
if (!Minz_Request::isPost()) {
Minz_Request::forward($url_redirect, true);
}
@set_time_limit(300);
$entryDAO = FreshRSS_Factory::createEntryDao();
$entryDAO->optimizeTable();
$feedDAO = FreshRSS_Factory::createFeedDao();
$feedDAO->updateCachedValues();
invalidateHttpCache();
Minz_Request::good(_t('feedback.admin.optimization_complete'), $url_redirect);
}
/**
* This action purges old entries from feeds.
*
* @todo should be a POST request
* @todo should be in feedController
*/
public function purgeAction() {
@set_time_limit(300);
$nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
$feedDAO = FreshRSS_Factory::createFeedDao();
$feeds = $feedDAO->listFeeds();
$nb_total = 0;
invalidateHttpCache();
foreach ($feeds as $feed) {
$feed_history = $feed->keepHistory();
if ($feed_history == -2) {
// TODO: -2 must be a constant!
// -2 means we take the default value from configuration
$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
}
if ($feed_history >= 0) {
$nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feed_history);
if ($nb > 0) {
$nb_total += $nb;
Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']');
}
}
}
$feedDAO->updateCachedValues();
invalidateHttpCache();
Minz_Request::good(_t('feedback.sub.purge_completed', $nb_total), array(
'c' => 'configure',
'a' => 'archiving'
));
}
}

View file

@ -1,53 +0,0 @@
<?php
/**
* Controller to handle error page.
*/
class FreshRSS_error_Controller extends Minz_ActionController {
/**
* This action is the default one for the controller.
*
* It is called by Minz_Error::error() method.
*
* Parameters are passed by Minz_Session to have a proper url:
* - error_code (default: 404)
* - error_logs (default: array())
*/
public function indexAction() {
$code_int = Minz_Session::param('error_code', 404);
$error_logs = Minz_Session::param('error_logs', array());
Minz_Session::_param('error_code');
Minz_Session::_param('error_logs');
switch ($code_int) {
case 200 :
header('HTTP/1.1 200 OK');
break;
case 403:
header('HTTP/1.1 403 Forbidden');
$this->view->code = 'Error 403 - Forbidden';
$this->view->errorMessage = _t('feedback.access.denied');
break;
case 500:
header('HTTP/1.1 500 Internal Server Error');
$this->view->code = 'Error 500 - Internal Server Error';
break;
case 503:
header('HTTP/1.1 503 Service Unavailable');
$this->view->code = 'Error 503 - Service Unavailable';
break;
case 404:
default:
header('HTTP/1.1 404 Not Found');
$this->view->code = 'Error 404 - Not found';
$this->view->errorMessage = _t('feedback.access.not_found');
}
$error_message = trim(implode($error_logs));
if ($error_message !== '') {
$this->view->errorMessage = $error_message;
}
Minz_View::prependTitle($this->view->code . ' · ');
}
}

View file

@ -1,215 +0,0 @@
<?php
/**
* The controller to manage extensions.
*/
class FreshRSS_extension_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
}
/**
* This action lists all the extensions available to the current user.
*/
public function indexAction() {
Minz_View::prependTitle(_t('admin.extensions.title') . ' · ');
$this->view->extension_list = array(
'system' => array(),
'user' => array(),
);
$extensions = Minz_ExtensionManager::listExtensions();
foreach ($extensions as $ext) {
$this->view->extension_list[$ext->getType()][] = $ext;
}
}
/**
* This action handles configuration of a given extension.
*
* Only administrator can configure a system extension.
*
* Parameters are:
* - e: the extension name (urlencoded)
* - additional parameters which should be handle by the extension
* handleConfigureAction() method (POST request).
*/
public function configureAction() {
if (Minz_Request::param('ajax')) {
$this->view->_useLayout(false);
} else {
$this->indexAction();
$this->view->change_view('extension', 'index');
}
$ext_name = urldecode(Minz_Request::param('e'));
$ext = Minz_ExtensionManager::findExtension($ext_name);
if (is_null($ext)) {
Minz_Error::error(404);
}
if ($ext->getType() === 'system' && !FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
$this->view->extension = $ext;
$this->view->extension->handleConfigureAction();
}
/**
* This action enables a disabled extension for the current user.
*
* System extensions can only be enabled by an administrator.
* This action must be reached by a POST request.
*
* Parameter is:
* - e: the extension name (urlencoded).
*/
public function enableAction() {
$url_redirect = array('c' => 'extension', 'a' => 'index');
if (Minz_Request::isPost()) {
$ext_name = urldecode(Minz_Request::param('e'));
$ext = Minz_ExtensionManager::findExtension($ext_name);
if (is_null($ext)) {
Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name),
$url_redirect);
}
if ($ext->isEnabled()) {
Minz_Request::bad(_t('feedback.extensions.already_enabled', $ext_name),
$url_redirect);
}
$conf = null;
if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) {
$conf = FreshRSS_Context::$system_conf;
} elseif ($ext->getType() === 'user') {
$conf = FreshRSS_Context::$user_conf;
} else {
Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name),
$url_redirect);
}
$res = $ext->install();
if ($res === true) {
$ext_list = $conf->extensions_enabled;
array_push_unique($ext_list, $ext_name);
$conf->extensions_enabled = $ext_list;
$conf->save();
Minz_Request::good(_t('feedback.extensions.enable.ok', $ext_name),
$url_redirect);
} else {
Minz_Log::warning('Can not enable extension ' . $ext_name . ': ' . $res);
Minz_Request::bad(_t('feedback.extensions.enable.ko', $ext_name, _url('index', 'logs')),
$url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
/**
* This action disables an enabled extension for the current user.
*
* System extensions can only be disabled by an administrator.
* This action must be reached by a POST request.
*
* Parameter is:
* - e: the extension name (urlencoded).
*/
public function disableAction() {
$url_redirect = array('c' => 'extension', 'a' => 'index');
if (Minz_Request::isPost()) {
$ext_name = urldecode(Minz_Request::param('e'));
$ext = Minz_ExtensionManager::findExtension($ext_name);
if (is_null($ext)) {
Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name),
$url_redirect);
}
if (!$ext->isEnabled()) {
Minz_Request::bad(_t('feedback.extensions.not_enabled', $ext_name),
$url_redirect);
}
$conf = null;
if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) {
$conf = FreshRSS_Context::$system_conf;
} elseif ($ext->getType() === 'user') {
$conf = FreshRSS_Context::$user_conf;
} else {
Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name),
$url_redirect);
}
$res = $ext->uninstall();
if ($res === true) {
$ext_list = $conf->extensions_enabled;
array_remove($ext_list, $ext_name);
$conf->extensions_enabled = $ext_list;
$conf->save();
Minz_Request::good(_t('feedback.extensions.disable.ok', $ext_name),
$url_redirect);
} else {
Minz_Log::warning('Can not unable extension ' . $ext_name . ': ' . $res);
Minz_Request::bad(_t('feedback.extensions.disable.ko', $ext_name, _url('index', 'logs')),
$url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
/**
* This action handles deletion of an extension.
*
* Only administrator can remove an extension.
* This action must be reached by a POST request.
*
* Parameter is:
* -e: extension name (urlencoded)
*/
public function removeAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
$url_redirect = array('c' => 'extension', 'a' => 'index');
if (Minz_Request::isPost()) {
$ext_name = urldecode(Minz_Request::param('e'));
$ext = Minz_ExtensionManager::findExtension($ext_name);
if (is_null($ext)) {
Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name),
$url_redirect);
}
$res = recursive_unlink($ext->getPath());
if ($res) {
Minz_Request::good(_t('feedback.extensions.removed', $ext_name),
$url_redirect);
} else {
Minz_Request::bad(_t('feedback.extensions.cannot_delete', $ext_name),
$url_redirect);
}
}
Minz_Request::forward($url_redirect, true);
}
}

View file

@ -1,573 +0,0 @@
<?php
/**
* Controller to handle every feed actions.
*/
class FreshRSS_feed_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
// 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 = FreshRSS_Context::$user_conf->token;
$token_param = Minz_Request::param('token', '');
$token_is_ok = ($token != '' && $token == $token_param);
$action = Minz_Request::actionName();
$allow_anonymous_refresh = FreshRSS_Context::$system_conf->allow_anonymous_refresh;
if ($action !== 'actualize' ||
!($allow_anonymous_refresh || $token_is_ok)) {
Minz_Error::error(403);
}
}
}
/**
* This action subscribes to a feed.
*
* It can be reached by both GET and POST requests.
*
* GET request displays a form to add and configure a feed.
* Request parameter is:
* - url_rss (default: false)
*
* POST request adds a feed in database.
* Parameters are:
* - url_rss (default: false)
* - category (default: false)
* - new_category (required if category == 'nc')
* - http_user (default: false)
* - http_pass (default: false)
* It tries to get website information from RSS feed.
* If no category is given, feed is added to the default one.
*
* If url_rss is false, nothing happened.
*/
public function addAction() {
$url = Minz_Request::param('url_rss');
if ($url === false) {
// No url, do nothing
Minz_Request::forward(array(
'c' => 'subscription',
'a' => 'index'
), true);
}
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->catDAO = new FreshRSS_CategoryDAO();
$url_redirect = array(
'c' => 'subscription',
'a' => 'index',
'params' => array(),
);
$limits = FreshRSS_Context::$system_conf->limits;
$this->view->feeds = $feedDAO->listFeeds();
if (count($this->view->feeds) >= $limits['max_feeds']) {
Minz_Request::bad(_t('feedback.sub.feed.over_max', $limits['max_feeds']),
$url_redirect);
}
if (Minz_Request::isPost()) {
@set_time_limit(300);
$cat = Minz_Request::param('category');
if ($cat === 'nc') {
// User want to create a new category, new_category parameter
// must exist
$new_cat = Minz_Request::param('new_category');
if (empty($new_cat['name'])) {
$cat = false;
} else {
$cat = $this->catDAO->addCategory($new_cat);
}
}
if ($cat === false) {
// If category was not given or if creating new category failed,
// get the default category
$this->catDAO->checkDefault();
$def_cat = $this->catDAO->getDefault();
$cat = $def_cat->id();
}
// HTTP information are useful if feed is protected behind a
// HTTP authentication
$user = trim(Minz_Request::param('http_user', ''));
$pass = Minz_Request::param('http_pass', '');
$http_auth = '';
if ($user != '' && $pass != '') { //TODO: Sanitize
$http_auth = $user . ':' . $pass;
}
$transaction_started = false;
try {
$feed = new FreshRSS_Feed($url);
} catch (FreshRSS_BadUrl_Exception $e) {
// Given url was not a valid url!
Minz_Log::warning($e->getMessage());
Minz_Request::bad(_t('feedback.sub.feed.invalid_url', $url), $url_redirect);
}
$feed->_httpAuth($http_auth);
try {
$feed->load(true);
} catch (FreshRSS_Feed_Exception $e) {
// Something went bad (timeout, server not found, etc.)
Minz_Log::warning($e->getMessage());
Minz_Request::bad(
_t('feedback.sub.feed.internal_problem', _url('index', 'logs')),
$url_redirect
);
} catch (Minz_FileNotExistException $e) {
// Cache directory doesn't exist!
Minz_Log::error($e->getMessage());
Minz_Request::bad(
_t('feedback.sub.feed.internal_problem', _url('index', 'logs')),
$url_redirect
);
}
if ($feedDAO->searchByUrl($feed->url())) {
Minz_Request::bad(
_t('feedback.sub.feed.already_subscribed', $feed->name()),
$url_redirect
);
}
$feed->_category($cat);
// Call the extension hook
$name = $feed->name();
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
if ($feed === null) {
Minz_Request::bad(_t('feedback.sub.feed.not_added', $name), $url_redirect);
}
$values = array(
'url' => $feed->url(),
'category' => $feed->category(),
'name' => $feed->name(),
'website' => $feed->website(),
'description' => $feed->description(),
'lastUpdate' => time(),
'httpAuth' => $feed->httpAuth(),
);
$id = $feedDAO->addFeed($values);
if (!$id) {
// There was an error in database... we cannot say what here.
Minz_Request::bad(_t('feedback.sub.feed.not_added', $feed->name()), $url_redirect);
}
// Ok, feed has been added in database. Now we have to refresh entries.
$feed->_id($id);
$feed->faviconPrepare();
//$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
$entryDAO = FreshRSS_Factory::createEntryDao();
// We want chronological order and SimplePie uses reverse order.
$entries = array_reverse($feed->entries());
// Calculate date of oldest entries we accept in DB.
$nb_month_old = FreshRSS_Context::$user_conf->old_entries;
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
// Use a shared statement and a transaction to improve a LOT the
// performances.
$feedDAO->beginTransaction();
foreach ($entries as $entry) {
// Entries are added without any verification.
$entry->_feed($feed->id());
$entry->_id(min(time(), $entry->date(true)) . uSecString());
$entry->_isRead($is_read);
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
if ($entry === null) {
// An extension has returned a null value, there is nothing to insert.
continue;
}
$values = $entry->toArray();
$entryDAO->addEntry($values);
}
$feedDAO->updateLastUpdate($feed->id());
$feedDAO->commit();
// Entries are in DB, we redirect to feed configuration page.
$url_redirect['params']['id'] = $feed->id();
Minz_Request::good(_t('feedback.sub.feed.added', $feed->name()), $url_redirect);
} else {
// GET request: we must ask confirmation to user before adding feed.
Minz_View::prependTitle(_t('sub.feed.title_add') . ' · ');
$this->view->categories = $this->catDAO->listCategories(false);
$this->view->feed = new FreshRSS_Feed($url);
try {
// We try to get 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.
$url_redirect['params']['id'] = $feed->id();
Minz_Request::good(_t('feedback.sub.feed.already_subscribed', $feed->name()), $url_redirect);
}
}
}
/**
* This action remove entries from a given feed.
*
* It should be reached by a POST action.
*
* Parameter is:
* - id (default: false)
*/
public function truncateAction() {
$id = Minz_Request::param('id');
$url_redirect = array(
'c' => 'subscription',
'a' => 'index',
'params' => array('id' => $id)
);
if (!Minz_Request::isPost()) {
Minz_Request::forward($url_redirect, true);
}
$feedDAO = FreshRSS_Factory::createFeedDao();
$n = $feedDAO->truncate($id);
invalidateHttpCache();
if ($n === false) {
Minz_Request::bad(_t('feedback.sub.feed.error'), $url_redirect);
} else {
Minz_Request::good(_t('feedback.sub.feed.n_entries_deleted', $n), $url_redirect);
}
}
/**
* This action actualizes entries from one or several feeds.
*
* Parameters are:
* - id (default: false): Feed ID
* - url (default: false): Feed URL
* - force (default: false)
* If id and url are not specified, all the feeds are actualized. But if force is
* false, process stops at 10 feeds to avoid time execution problem.
*/
public function actualizeAction($simplePiePush = null) {
@set_time_limit(300);
$feedDAO = FreshRSS_Factory::createFeedDao();
$entryDAO = FreshRSS_Factory::createEntryDao();
Minz_Session::_param('actualize_feeds', false);
$id = Minz_Request::param('id');
$url = Minz_Request::param('url');
$force = Minz_Request::param('force');
// Create a list of feeds to actualize.
// If id is set and valid, corresponding feed is added to the list but
// alone in order to automatize further process.
$feeds = array();
if ($id || $url) {
$feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url);
if ($feed) {
$feeds[] = $feed;
}
} else {
$feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
}
// Calculate date of oldest entries we accept in DB.
$nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
// PubSubHubbub support
$pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled;
$pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration.
$updated_feeds = 0;
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
foreach ($feeds as $feed) {
$url = $feed->url(); //For detection of HTTP 301
$pubSubHubbubEnabled = $pubsubhubbubEnabledGeneral && $feed->pubSubHubbubEnabled();
if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) {
//$text = 'Skip pull of feed using PubSubHubbub: ' . $url;
//Minz_Log::debug($text);
//file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
continue; //When PubSubHubbub is used, do not pull refresh so often
}
if (!$feed->lock()) {
Minz_Log::notice('Feed already being actualized: ' . $feed->url());
continue;
}
try {
if ($simplePiePush) {
$feed->loadEntries($simplePiePush); //Used by PubSubHubbub
} else {
$feed->load(false);
}
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::warning($e->getMessage());
$feedDAO->updateLastUpdate($feed->id(), true);
$feed->unlock();
continue;
}
$feed_history = $feed->keepHistory();
if ($feed_history == -2) {
// TODO: -2 must be a constant!
// -2 means we take the default value from configuration
$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
}
// We want chronological order and SimplePie uses reverse order.
$entries = array_reverse($feed->entries());
if (count($entries) > 0) {
$newGuids = array();
foreach ($entries as $entry) {
$newGuids[] = $entry->guid();
}
// For this feed, check existing GUIDs already in database.
$existingHashForGuids = $entryDAO->listHashForFeedGuids($feed->id(), $newGuids);
unset($newGuids);
$oldGuids = array();
// Add entries in database if possible.
foreach ($entries as $entry) {
$entry_date = $entry->date(true);
if (isset($existingHashForGuids[$entry->guid()])) {
$existingHash = $existingHashForGuids[$entry->guid()];
if (strcasecmp($existingHash, $entry->hash()) === 0 || trim($existingHash, '0') == '') {
//This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3
$oldGuids[] = $entry->guid();
} else { //This entry already exists but has been updated
//Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() .
//', old hash ' . $existingHash . ', new hash ' . $entry->hash());
//TODO: Make an updated/is_read policy by feed, in addition to the global one.
$entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy.
if (!$entryDAO->hasTransaction()) {
$entryDAO->beginTransaction();
}
$entryDAO->updateEntry($entry->toArray());
}
} elseif ($feed_history == 0 && $entry_date < $date_min) {
// This entry should not be added considering configuration and date.
$oldGuids[] = $entry->guid();
} else {
if ($entry_date < $date_min) {
$id = min(time(), $entry_date) . uSecString();
$entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
} else {
$id = uTimeString();
$entry->_isRead($is_read);
}
$entry->_id($id);
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
if ($entry === null) {
// An extension has returned a null value, there is nothing to insert.
continue;
}
if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull!
$text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid();
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
Minz_Log::warning($text);
$pubSubHubbubEnabled = false;
$feed->pubSubHubbubError(true);
}
if (!$entryDAO->hasTransaction()) {
$entryDAO->beginTransaction();
}
$entryDAO->addEntry($entry->toArray());
}
}
$entryDAO->updateLastSeen($feed->id(), $oldGuids);
}
if ($feed_history >= 0 && rand(0, 30) === 1) {
// TODO: move this function in web cron when available (see entry::purge)
// Remove old entries once in 30.
if (!$entryDAO->hasTransaction()) {
$entryDAO->beginTransaction();
}
$nb = $feedDAO->cleanOldEntries($feed->id(),
$date_min,
max($feed_history, count($entries) + 10));
if ($nb > 0) {
Minz_Log::debug($nb . ' old entries cleaned in feed [' .
$feed->url() . ']');
}
}
$feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->hasTransaction());
if ($entryDAO->hasTransaction()) {
$entryDAO->commit();
}
if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub
if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs
$selfUrl = checkUrl($feed->selfUrl());
if ($selfUrl) {
Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url());
if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe
Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url());
}
$feed->_url($selfUrl, false);
Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url());
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
}
}
}
elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently
Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url());
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
}
$feed->faviconPrepare();
if ($pubsubhubbubEnabledGeneral && $feed->pubSubHubbubPrepare()) {
Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url());
if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe
Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url());
}
}
$feed->unlock();
$updated_feeds++;
unset($feed);
// No more than 10 feeds unless $force is true to avoid overloading
// the server.
if ($updated_feeds >= 10 && !$force) {
break;
}
}
if (Minz_Request::param('ajax')) {
// Most of the time, ajax request is for only one feed. But since
// there are several parallel requests, we should return that there
// are several updated feeds.
$notif = array(
'type' => 'good',
'content' => _t('feedback.sub.feed.actualizeds')
);
Minz_Session::_param('notification', $notif);
// No layout in ajax request.
$this->view->_useLayout(false);
} else {
// Redirect to the main page with correct notification.
if ($updated_feeds === 1) {
$feed = reset($feeds);
Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array(
'params' => array('get' => 'f_' . $feed->id())
));
} elseif ($updated_feeds > 1) {
Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array());
} else {
Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array());
}
}
return $updated_feeds;
}
/**
* This action changes the category of a feed.
*
* This page must be reached by a POST request.
*
* Parameters are:
* - f_id (default: false)
* - c_id (default: false)
* If c_id is false, default category is used.
*
* @todo should handle order of the feed inside the category.
*/
public function moveAction() {
if (!Minz_Request::isPost()) {
Minz_Request::forward(array('c' => 'subscription'), true);
}
$feed_id = Minz_Request::param('f_id');
$cat_id = Minz_Request::param('c_id');
if ($cat_id === false) {
// If category was not given get the default one.
$catDAO = new FreshRSS_CategoryDAO();
$catDAO->checkDefault();
$def_cat = $catDAO->getDefault();
$cat_id = $def_cat->id();
}
$feedDAO = FreshRSS_Factory::createFeedDao();
$values = array('category' => $cat_id);
$feed = $feedDAO->searchById($feed_id);
if ($feed && ($feed->category() == $cat_id ||
$feedDAO->updateFeed($feed_id, $values))) {
// TODO: return something useful
} else {
Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' .
'in the category `' . $cat_id . '`');
Minz_Error::error(404);
}
}
/**
* This action deletes a feed.
*
* This page must be reached by a POST request.
* If there are related queries, they are deleted too.
*
* Parameters are:
* - id (default: false)
* - r (default: false)
* r permits to redirect to a given page at the end of this action.
*
* @todo handle "r" redirection in Minz_Request::forward()?
*/
public function deleteAction() {
$redirect_url = Minz_Request::param('r', false, true);
if (!$redirect_url) {
$redirect_url = array('c' => 'subscription', 'a' => 'index');
}
if (!Minz_Request::isPost()) {
Minz_Request::forward($redirect_url, true);
}
$id = Minz_Request::param('id');
$feedDAO = FreshRSS_Factory::createFeedDao();
if ($feedDAO->deleteFeed($id)) {
// TODO: Delete old favicon
// Remove related queries
FreshRSS_Context::$user_conf->queries = remove_query_by_get(
'f_' . $id, FreshRSS_Context::$user_conf->queries);
FreshRSS_Context::$user_conf->save();
Minz_Request::good(_t('feedback.sub.feed.deleted'), $redirect_url);
} else {
Minz_Request::bad(_t('feedback.sub.feed.error'), $redirect_url);
}
}
}

View file

@ -1,611 +0,0 @@
<?php
/**
* Controller to handle every import and export actions.
*/
class FreshRSS_importExport_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
require_once(LIB_PATH . '/lib_opml.php');
$this->catDAO = new FreshRSS_CategoryDAO();
$this->entryDAO = FreshRSS_Factory::createEntryDao();
$this->feedDAO = FreshRSS_Factory::createFeedDao();
}
/**
* This action displays the main page for import / export system.
*/
public function indexAction() {
$this->view->feeds = $this->feedDAO->listFeeds();
Minz_View::prependTitle(_t('sub.import_export.title') . ' · ');
}
/**
* This action handles import action.
*
* It must be reached by a POST request.
*
* Parameter is:
* - file (default: nothing!)
* Available file types are: zip, json or xml.
*/
public function importAction() {
if (!Minz_Request::isPost()) {
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
$file = $_FILES['file'];
$status_file = $file['error'];
if ($status_file !== 0) {
Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file);
Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'),
array('c' => 'importExport', 'a' => 'index'));
}
@set_time_limit(300);
$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
$list = array();
if ($type_file === 'zip' && extension_loaded('zip')) {
$zip = zip_open($file['tmp_name']);
if (!is_resource($zip)) {
// zip_open cannot open file: something is wrong
Minz_Log::warning('Zip archive cannot be imported. Error code: ' . $zip);
Minz_Request::bad(_t('feedback.import_export.zip_error'),
array('c' => 'importExport', 'a' => 'index'));
}
while (($zipfile = zip_read($zip)) !== false) {
if (!is_resource($zipfile)) {
// zip_entry() can also return an error code!
Minz_Log::warning('Zip file cannot be imported. Error code: ' . $zipfile);
} else {
$type_zipfile = $this->guessFileType(zip_entry_name($zipfile));
if ($type_file !== 'unknown') {
$list_files[$type_zipfile][] = zip_entry_read(
$zipfile,
zip_entry_filesize($zipfile)
);
}
}
}
zip_close($zip);
} elseif ($type_file === 'zip') {
// Zip extension is not loaded
Minz_Request::bad(_t('feedback.import_export.no_zip_extension'),
array('c' => 'importExport', 'a' => 'index'));
} elseif ($type_file !== 'unknown') {
$list_files[$type_file][] = file_get_contents($file['tmp_name']);
}
// Import file contents.
// OPML first(so categories and feeds are imported)
// Starred articles then so the "favourite" status is already set
// And finally all other files.
$error = false;
foreach ($list_files['opml'] as $opml_file) {
$error = $this->importOpml($opml_file);
}
foreach ($list_files['json_starred'] as $article_file) {
$error = $this->importJson($article_file, true);
}
foreach ($list_files['json_feed'] as $article_file) {
$error = $this->importJson($article_file);
}
// And finally, we get import status and redirect to the home page
Minz_Session::_param('actualize_feeds', true);
$content_notif = $error === true ? _t('feedback.import_export.feeds_imported_with_errors') :
_t('feedback.import_export.feeds_imported');
Minz_Request::good($content_notif);
}
/**
* This method tries to guess the file type based on its name.
*
* Itis a *very* basic guess file type function. Only based on filename.
* That's could be improved but should be enough for what we have to do.
*/
private function guessFileType($filename) {
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 (substr_compare($filename, '.json', -5) === 0 &&
strpos($filename, 'starred') !== false) {
return 'json_starred';
} elseif (substr_compare($filename, '.json', -5) === 0) {
return 'json_feed';
} else {
return 'unknown';
}
}
/**
* This method parses and imports an OPML file.
*
* @param string $opml_file the OPML file content.
* @return boolean true if an error occured, false else.
*/
private function importOpml($opml_file) {
$opml_array = array();
try {
$opml_array = libopml_parse_string($opml_file, false);
} catch (LibOPML_Exception $e) {
Minz_Log::warning($e->getMessage());
return true;
}
$this->catDAO->checkDefault();
return $this->addOpmlElements($opml_array['body']);
}
/**
* This method imports an OPML file based on its body.
*
* @param array $opml_elements an OPML element (body or outline).
* @param string $parent_cat the name of the parent category.
* @return boolean true if an error occured, false else.
*/
private function addOpmlElements($opml_elements, $parent_cat = null) {
$error = false;
$nb_feeds = count($this->feedDAO->listFeeds());
$nb_cats = count($this->catDAO->listCategories(false));
$limits = FreshRSS_Context::$system_conf->limits;
foreach ($opml_elements as $elt) {
$is_error = false;
if (isset($elt['xmlUrl'])) {
// If xmlUrl exists, it means it is a feed
if ($nb_feeds >= $limits['max_feeds']) {
Minz_Log::warning(_t('feedback.sub.feed.over_max',
$limits['max_feeds']));
$is_error = true;
continue;
}
$is_error = $this->addFeedOpml($elt, $parent_cat);
if (!$is_error) {
$nb_feeds += 1;
}
} else {
// No xmlUrl? It should be a category!
$limit_reached = ($nb_cats >= $limits['max_categories']);
if ($limit_reached) {
Minz_Log::warning(_t('feedback.sub.category.over_max',
$limits['max_categories']));
}
$is_error = $this->addCategoryOpml($elt, $parent_cat, $limit_reached);
if (!$is_error) {
$nb_cats += 1;
}
}
if (!$error && $is_error) {
// oops: there is at least one error!
$error = $is_error;
}
}
return $error;
}
/**
* This method imports an OPML feed element.
*
* @param array $feed_elt an OPML element (must be a feed element).
* @param string $parent_cat the name of the parent category.
* @return boolean true if an error occured, false else.
*/
private function addFeedOpml($feed_elt, $parent_cat) {
$default_cat = $this->catDAO->getDefault();
if (is_null($parent_cat)) {
// This feed has no parent category so we get the default one
$parent_cat = $default_cat->name();
}
$cat = $this->catDAO->searchByName($parent_cat);
if (is_null($cat)) {
// If there is not $cat, it means parent category does not exist in
// database.
// If it happens, take the default category.
$cat = $default_cat;
}
// We get different useful information
$url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']);
$name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']);
$website = '';
if (isset($feed_elt['htmlUrl'])) {
$website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']);
}
$description = '';
if (isset($feed_elt['description'])) {
$description = Minz_Helper::htmlspecialchars_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);
// Call the extension hook
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
if (!is_null($feed)) {
// addFeedObject checks if feed is already in DB so nothing else to
// check here
$id = $this->feedDAO->addFeedObject($feed);
$error = ($id === false);
} else {
$error = true;
}
} catch (FreshRSS_Feed_Exception $e) {
Minz_Log::warning($e->getMessage());
$error = true;
}
return $error;
}
/**
* This method imports an OPML category element.
*
* @param array $cat_elt an OPML element (must be a category element).
* @param string $parent_cat the name of the parent category.
* @param boolean $cat_limit_reached indicates if category limit has been reached.
* if yes, category is not added (but we try for feeds!)
* @return boolean true if an error occured, false else.
*/
private function addCategoryOpml($cat_elt, $parent_cat, $cat_limit_reached) {
// Create a new Category object
$cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text']));
$error = true;
if (!$cat_limit_reached) {
$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;
}
/**
* This method import a JSON-based file (Google Reader format).
*
* @param string $article_file the JSON file content.
* @param boolean $starred true if articles from the file must be starred.
* @return boolean true if an error occured, false else.
*/
private function importJson($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 = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
$google_compliant = strpos($article_object['id'], 'com.google') !== false;
$error = false;
$article_to_feed = array();
$nb_feeds = count($this->feedDAO->listFeeds());
$limits = FreshRSS_Context::$system_conf->limits;
// First, we check feeds of articles are in DB (and add them if needed).
foreach ($article_object['items'] as $item) {
$key = $google_compliant ? 'htmlUrl' : 'feedUrl';
$feed = new FreshRSS_Feed($item['origin'][$key]);
$feed = $this->feedDAO->searchByUrl($feed->url());
if (is_null($feed)) {
// Feed does not exist in DB,we should to try to add it.
if ($nb_feeds >= $limits['max_feeds']) {
// Oops, no more place!
Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds']));
} else {
$feed = $this->addFeedJson($item['origin'], $google_compliant);
}
if (is_null($feed)) {
// Still null? It means something went wrong.
$error = true;
} else {
// Nice! Increase the counter.
$nb_feeds += 1;
}
}
if (!is_null($feed)) {
$article_to_feed[$item['id']] = $feed->id();
}
}
// Then, articles are imported.
$this->entryDAO->beginTransaction();
foreach ($article_object['items'] as $item) {
if (!isset($article_to_feed[$item['id']])) {
// Related feed does not exist for this entry, do nothing.
continue;
}
$feed_id = $article_to_feed[$item['id']];
$author = isset($item['author']) ? $item['author'] : '';
$key_content = ($google_compliant && !isset($item['content'])) ?
'summary' : 'content';
$tags = $item['categories'];
if ($google_compliant) {
// Remove tags containing "/state/com.google" which are useless.
$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->_id(min(time(), $entry->date(true)) . uSecString());
$entry->_tags($tags);
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
if (is_null($entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
}
$values = $entry->toArray();
$id = $this->entryDAO->addEntry($values);
if (!$error && ($id === false)) {
$error = true;
}
}
$this->entryDAO->commit();
return $error;
}
/**
* This method import a JSON-based feed (Google Reader format).
*
* @param array $origin represents a feed.
* @param boolean $google_compliant takes care of some specific values if true.
* @return FreshRSS_Feed if feed is in database at the end of the process,
* else null.
*/
private function addFeedJson($origin, $google_compliant) {
$default_cat = $this->catDAO->getDefault();
$return = null;
$key = $google_compliant ? 'htmlUrl' : 'feedUrl';
$url = $origin[$key];
$name = $origin['title'];
$website = $origin['htmlUrl'];
try {
// Create a Feed object and add it in database.
$feed = new FreshRSS_Feed($url);
$feed->_category($default_cat->id());
$feed->_name($name);
$feed->_website($website);
// Call the extension hook
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
if (!is_null($feed)) {
// 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;
}
/**
* This action handles export action.
*
* This action must be reached by a POST request.
*
* Parameters are:
* - export_opml (default: false)
* - export_starred (default: false)
* - export_feeds (default: array()) a list of feed ids
*/
public function exportAction() {
if (!Minz_Request::isPost()) {
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
$this->view->_useLayout(false);
$export_opml = Minz_Request::param('export_opml', false);
$export_starred = Minz_Request::param('export_starred', false);
$export_feeds = Minz_Request::param('export_feeds', array());
$export_files = array();
if ($export_opml) {
$export_files['feeds.opml'] = $this->generateOpml();
}
if ($export_starred) {
$export_files['starred.json'] = $this->generateEntries('starred');
}
foreach ($export_feeds as $feed_id) {
$feed = $this->feedDAO->searchById($feed_id);
if ($feed) {
$filename = 'feed_' . $feed->category() . '_'
. $feed->id() . '.json';
$export_files[$filename] = $this->generateEntries('feed', $feed);
}
}
$nb_files = count($export_files);
if ($nb_files > 1) {
// If there are more than 1 file to export, we need a zip archive.
try {
$this->exportZip($export_files);
} catch (Exception $e) {
# Oops, there is no Zip extension!
Minz_Request::bad(_t('feedback.import_export.export_no_zip_extension'),
array('c' => 'importExport', 'a' => 'index'));
}
} elseif ($nb_files === 1) {
// Only one file? Guess its type and export it.
$filename = key($export_files);
$type = $this->guessFileType($filename);
$this->exportFile('freshrss_' . $filename, $export_files[$filename], $type);
} else {
// Nothing to do...
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
}
/**
* This method returns the OPML file based on user subscriptions.
*
* @return string the OPML file content.
*/
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');
}
/**
* This method returns a JSON file content.
*
* @param string $type must be "starred" or "feed"
* @param FreshRSS_Feed $feed feed of which we want to get entries.
* @return string the JSON file content.
*/
private function generateEntries($type, $feed = NULL) {
$this->view->categories = $this->catDAO->listCategories();
if ($type == 'starred') {
$this->view->list_title = _t('sub.import_export.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 = _t('sub.import_export.feed_list', $feed->name());
$this->view->type = 'feed/' . $feed->id();
$this->view->entries = $this->entryDAO->listWhere(
'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
FreshRSS_Context::$user_conf->posts_per_page
);
$this->view->feed = $feed;
}
return $this->view->helperToString('export/articles');
}
/**
* This method zips a list of files and returns it by HTTP.
*
* @param array $files list of files where key is filename and value the content.
* @throws Exception if Zip extension is not loaded.
*/
private function exportZip($files) {
if (!extension_loaded('zip')) {
throw new Exception();
}
// From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
$zip_file = tempnam('tmp', 'zip');
$zip = new ZipArchive();
$zip->open($zip_file, ZipArchive::OVERWRITE);
foreach ($files as $filename => $content) {
$zip->addFromString($filename, $content);
}
// Close and send to user
$zip->close();
header('Content-Type: application/zip');
header('Content-Length: ' . filesize($zip_file));
header('Content-Disposition: attachment; filename="freshrss_export.zip"');
readfile($zip_file);
unlink($zip_file);
}
/**
* This method returns a single file (OPML or JSON) by HTTP.
*
* @param string $filename
* @param string $content
* @param string $type the file type (opml, json_feed or json_starred).
* If equals to unknown, nothing happens.
*/
private function exportFile($filename, $content, $type) {
if ($type === 'unknown') {
return;
}
$content_type = '';
if ($type === 'opml') {
$content_type = "text/opml";
} elseif ($type === 'json_feed' || $type === 'json_starred') {
$content_type = "text/json";
}
header('Content-Type: ' . $content_type . '; charset=utf-8');
header('Content-disposition: attachment; filename=' . $filename);
print($content);
}
}

View file

@ -1,240 +0,0 @@
<?php
/**
* This class handles main actions of FreshRSS.
*/
class FreshRSS_index_Controller extends Minz_ActionController {
/**
* This action only redirect on the default view mode (normal or global)
*/
public function indexAction() {
$prefered_output = FreshRSS_Context::$user_conf->view_mode;
Minz_Request::forward(array(
'c' => 'index',
'a' => $prefered_output
));
}
/**
* This action displays the normal view of FreshRSS.
*/
public function normalAction() {
$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) {
Minz_Request::forward(array('c' => 'auth', 'a' => 'login'));
return;
}
try {
$this->updateContext();
} catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404);
}
$this->view->callbackBeforeContent = function() {
try {
$entries = $this->listEntriesByContext();
$nb_entries = count($entries);
if ($nb_entries > FreshRSS_Context::$number) {
// We have more elements for pagination
$last_entry = array_pop($entries);
FreshRSS_Context::$next_id = $last_entry->id();
}
$first_entry = $nb_entries > 0 ? $entries[0] : null;
FreshRSS_Context::$id_max = $first_entry === null ?
(time() - 1) . '000000' :
$first_entry->id();
if (FreshRSS_Context::$order === 'ASC') {
// In this case we do not know but we guess id_max
$id_max = (time() - 1) . '000000';
if (strcmp($id_max, FreshRSS_Context::$id_max) > 0) {
FreshRSS_Context::$id_max = $id_max;
}
}
$this->view->entries = $entries;
} catch (FreshRSS_EntriesGetter_Exception $e) {
Minz_Log::notice($e->getMessage());
Minz_Error::error(404);
}
$this->view->categories = FreshRSS_Context::$categories;
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
$title = FreshRSS_Context::$name;
if (FreshRSS_Context::$get_unread > 0) {
$title = '(' . FreshRSS_Context::$get_unread . ') ' . $title;
}
Minz_View::prependTitle($title . ' · ');
};
}
/**
* This action displays the reader view of FreshRSS.
*
* @todo: change this view into specific CSS rules?
*/
public function readerAction() {
$this->normalAction();
}
/**
* This action displays the global view of FreshRSS.
*/
public function globalAction() {
$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) {
Minz_Request::forward(array('c' => 'auth', 'a' => 'login'));
return;
}
Minz_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
try {
$this->updateContext();
} catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404);
}
$this->view->categories = FreshRSS_Context::$categories;
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
$title = _t('index.feed.title_global');
if (FreshRSS_Context::$get_unread > 0) {
$title = '(' . FreshRSS_Context::$get_unread . ') ' . $title;
}
Minz_View::prependTitle($title . ' · ');
}
/**
* This action displays the RSS feed of FreshRSS.
*/
public function rssAction() {
$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
$token = FreshRSS_Context::$user_conf->token;
$token_param = Minz_Request::param('token', '');
$token_is_ok = ($token != '' && $token === $token_param);
// Check if user has access.
if (!FreshRSS_Auth::hasAccess() &&
!$allow_anonymous &&
!$token_is_ok) {
Minz_Error::error(403);
}
try {
$this->updateContext();
} catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404);
}
try {
$this->view->entries = $this->listEntriesByContext();
} catch (FreshRSS_EntriesGetter_Exception $e) {
Minz_Log::notice($e->getMessage());
Minz_Error::error(404);
}
// No layout for RSS output.
$this->view->url = empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING'];
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
$this->view->_useLayout(false);
header('Content-Type: application/rss+xml; charset=utf-8');
}
/**
* This action updates the Context object by using request parameters.
*
* Parameters are:
* - state (default: conf->default_view)
* - search (default: empty string)
* - order (default: conf->sort_order)
* - nb (default: conf->posts_per_page)
* - next (default: empty string)
*/
private function updateContext() {
// Update number of read / unread variables.
$entryDAO = FreshRSS_Factory::createEntryDao();
FreshRSS_Context::$total_starred = $entryDAO->countUnreadReadFavorites();
FreshRSS_Context::$total_unread = FreshRSS_CategoryDAO::CountUnreads(
FreshRSS_Context::$categories, 1
);
FreshRSS_Context::_get(Minz_Request::param('get', 'a'));
FreshRSS_Context::$state = Minz_Request::param(
'state', FreshRSS_Context::$user_conf->default_state
);
$state_forced_by_user = Minz_Request::param('state', false) !== false;
if (FreshRSS_Context::$user_conf->default_view === 'adaptive' &&
FreshRSS_Context::$get_unread <= 0 &&
!FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ) &&
!$state_forced_by_user) {
FreshRSS_Context::$state |= FreshRSS_Entry::STATE_READ;
}
FreshRSS_Context::$search = new FreshRSS_Search(Minz_Request::param('search', ''));
FreshRSS_Context::$order = Minz_Request::param(
'order', FreshRSS_Context::$user_conf->sort_order
);
FreshRSS_Context::$number = Minz_Request::param(
'nb', FreshRSS_Context::$user_conf->posts_per_page
);
FreshRSS_Context::$first_id = Minz_Request::param('next', '');
}
/**
* This method returns a list of entries based on the Context object.
*/
private function listEntriesByContext() {
$entryDAO = FreshRSS_Factory::createEntryDao();
$get = FreshRSS_Context::currentGet(true);
if (count($get) > 1) {
$type = $get[0];
$id = $get[1];
} else {
$type = $get;
$id = '';
}
return $entryDAO->listWhere(
$type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order,
FreshRSS_Context::$number + 1, FreshRSS_Context::$first_id,
FreshRSS_Context::$search
);
}
/**
* This action displays the about page of FreshRSS.
*/
public function aboutAction() {
Minz_View::prependTitle(_t('index.about.title') . ' · ');
}
/**
* This action displays logs of FreshRSS for the current user.
*/
public function logsAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
Minz_View::prependTitle(_t('index.log.title') . ' · ');
if (Minz_Request::isPost()) {
FreshRSS_LogDAO::truncate();
}
$logs = FreshRSS_LogDAO::lines(); //TODO: ask only the necessary lines
//gestion pagination
$page = Minz_Request::param('page', 1);
$this->view->logsPaginator = new Minz_Paginator($logs);
$this->view->logsPaginator->_nbItemsPerPage(50);
$this->view->logsPaginator->_currentPage($page);
}
}

View file

@ -1,54 +0,0 @@
<?php
class FreshRSS_javascript_Controller extends Minz_ActionController {
public function firstAction() {
$this->view->_useLayout(false);
}
public function actualizeAction() {
header('Content-Type: application/json; charset=UTF-8');
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
}
public function nbUnreadsPerFeedAction() {
header('Content-Type: application/json; charset=UTF-8');
$catDAO = new FreshRSS_CategoryDAO();
$this->view->categories = $catDAO->listCategories(true, false);
}
//For Web-form login
public function nonceAction() {
header('Content-Type: application/json; charset=UTF-8');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T'));
header('Expires: 0');
header('Cache-Control: private, no-cache, no-store, must-revalidate');
header('Pragma: no-cache');
$user = isset($_GET['user']) ? $_GET['user'] : '';
if (ctype_alnum($user)) {
try {
$salt = FreshRSS_Context::$system_conf->salt;
$conf = get_user_configuration($user);
$s = $conf->passwordHash;
if (strlen($s) >= 60) {
$this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z".
$this->view->nonce = sha1($salt . uniqid(mt_rand(), true));
Minz_Session::_param('nonce', $this->view->nonce);
return; //Success
}
} catch (Minz_Exception $me) {
Minz_Log::warning('Nonce failure: ' . $me->getMessage());
}
} else {
Minz_Log::notice('Nonce failure due to invalid username!');
}
//Failure: Return random data.
$this->view->salt1 = sprintf('$2a$%02d$', FreshRSS_user_Controller::BCRYPT_COST);
$alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for ($i = 22; $i > 0; $i--) {
$this->view->salt1 .= $alphabet[rand(0, 63)];
}
$this->view->nonce = sha1(rand());
}
}

View file

@ -1,128 +0,0 @@
<?php
/**
* Controller to handle application statistics.
*/
class FreshRSS_stats_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
Minz_View::prependTitle(_t('admin.stats.title') . ' · ');
}
/**
* This action handles the statistic main page.
*
* It displays the statistic main page.
* The values computed to display the page are:
* - repartition of read/unread/favorite/not favorite
* - number of article per day
* - number of feed by category
* - number of article by category
* - list of most prolific feed
*/
public function indexAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$this->view->repartition = $statsDAO->calculateEntryRepartition();
$this->view->count = $statsDAO->calculateEntryCount();
$this->view->average = $statsDAO->calculateEntryAverage();
$this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
$this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
$this->view->topFeed = $statsDAO->calculateTopFeed();
}
/**
* This action handles the idle feed statistic page.
*
* It displays the list of idle feed for different period. The supported
* periods are:
* - last year
* - last 6 months
* - last 3 months
* - last month
* - last week
*/
public function idleAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
$feeds = $statsDAO->calculateFeedLastDate();
$idleFeeds = array(
'last_year' => array(),
'last_6_month' => array(),
'last_3_month' => array(),
'last_month' => array(),
'last_week' => array(),
);
$now = new \DateTime();
$feedDate = clone $now;
$lastWeek = clone $now;
$lastWeek->modify('-1 week');
$lastMonth = clone $now;
$lastMonth->modify('-1 month');
$last3Month = clone $now;
$last3Month->modify('-3 month');
$last6Month = clone $now;
$last6Month->modify('-6 month');
$lastYear = clone $now;
$lastYear->modify('-1 year');
foreach ($feeds as $feed) {
$feedDate->setTimestamp($feed['last_date']);
if ($feedDate >= $lastWeek) {
continue;
}
if ($feedDate < $lastYear) {
$idleFeeds['last_year'][] = $feed;
} elseif ($feedDate < $last6Month) {
$idleFeeds['last_6_month'][] = $feed;
} elseif ($feedDate < $last3Month) {
$idleFeeds['last_3_month'][] = $feed;
} elseif ($feedDate < $lastMonth) {
$idleFeeds['last_month'][] = $feed;
} elseif ($feedDate < $lastWeek) {
$idleFeeds['last_week'][] = $feed;
}
}
$this->view->idleFeeds = $idleFeeds;
}
/**
* This action handles the article repartition statistic page.
*
* It displays the number of article and the average of article for the
* following periods:
* - hour of the day
* - day of the week
* - month
*
* @todo verify that the metrics used here make some sense. Especially
* for the average.
*/
public function repartitionAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
$categoryDAO = new FreshRSS_CategoryDAO();
$feedDAO = FreshRSS_Factory::createFeedDao();
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$id = Minz_Request::param('id', null);
$this->view->categories = $categoryDAO->listCategories();
$this->view->feed = $feedDAO->searchById($id);
$this->view->days = $statsDAO->getDays();
$this->view->months = $statsDAO->getMonths();
$this->view->repartition = $statsDAO->calculateEntryRepartitionPerFeed($id);
$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
$this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id);
$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
$this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
$this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
}
}

View file

@ -1,116 +0,0 @@
<?php
/**
* Controller to handle subscription actions.
*/
class FreshRSS_subscription_Controller extends Minz_ActionController {
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
$catDAO = new FreshRSS_CategoryDAO();
$catDAO->checkDefault();
$this->view->categories = $catDAO->listCategories(false);
$this->view->default_category = $catDAO->getDefault();
}
/**
* This action handles the main subscription page
*
* It displays categories and associated feeds.
*/
public function indexAction() {
Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' .
@filemtime(PUBLIC_PATH . '/scripts/category.js')));
Minz_View::prependTitle(_t('sub.title') . ' · ');
$id = Minz_Request::param('id');
if ($id !== false) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feed = $feedDAO->searchById($id);
}
}
/**
* This action handles the feed configuration page.
*
* It displays the feed configuration page.
* If this action is reached through a POST request, it stores all new
* configuraiton values then sends a notification to the user.
*
* The options available on the page are:
* - name
* - description
* - website URL
* - feed URL
* - category id (default: default category id)
* - CSS path to article on website
* - display in main stream (default: 0)
* - HTTP authentication
* - number of article to retain (default: -2)
* - refresh frequency (default: -2)
* Default values are empty strings unless specified.
*/
public function feedAction() {
if (Minz_Request::param('ajax')) {
$this->view->_useLayout(false);
}
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feeds = $feedDAO->listFeeds();
$id = Minz_Request::param('id');
if ($id === false || !isset($this->view->feeds[$id])) {
Minz_Error::error(404);
return;
}
$this->view->feed = $this->view->feeds[$id];
Minz_View::prependTitle(_t('sub.title.feed_management') . ' · ' . $this->view->feed->name() . ' · ');
if (Minz_Request::isPost()) {
$user = trim(Minz_Request::param('http_user_feed' . $id, ''));
$pass = Minz_Request::param('http_pass_feed' . $id, '');
$httpAuth = '';
if ($user != '' && $pass != '') { //TODO: Sanitize
$httpAuth = $user . ':' . $pass;
}
$cat = intval(Minz_Request::param('category', 0));
$values = array(
'name' => Minz_Request::param('name', ''),
'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
'website' => Minz_Request::param('website', ''),
'url' => Minz_Request::param('url', ''),
'category' => $cat,
'pathEntries' => Minz_Request::param('path_entries', ''),
'priority' => intval(Minz_Request::param('priority', 0)),
'httpAuth' => $httpAuth,
'keep_history' => intval(Minz_Request::param('keep_history', -2)),
'ttl' => intval(Minz_Request::param('ttl', -2)),
);
invalidateHttpCache();
$url_redirect = array('c' => 'subscription', 'params' => array('id' => $id));
if ($feedDAO->updateFeed($id, $values) !== false) {
$this->view->feed->_category($cat);
$this->view->feed->faviconPrepare();
Minz_Request::good(_t('feedback.sub.feed.updated'), $url_redirect);
} else {
Minz_Request::bad(_t('feedback.sub.feed.error'), $url_redirect);
}
}
}
}

View file

@ -1,161 +0,0 @@
<?php
class FreshRSS_update_Controller extends Minz_ActionController {
public function firstAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
invalidateHttpCache();
$this->view->update_to_apply = false;
$this->view->last_update_time = 'unknown';
$timestamp = @filemtime(join_path(DATA_PATH, 'last_update.txt'));
if ($timestamp !== false) {
$this->view->last_update_time = timestamptodate($timestamp);
}
}
public function indexAction() {
Minz_View::prependTitle(_t('admin.update.title') . ' · ');
if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) {
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.update.file_is_nok', FRESHRSS_PATH)
);
} elseif (file_exists(UPDATE_FILENAME)) {
// There is an update file to apply!
$version = @file_get_contents(join_path(DATA_PATH, 'last_update.txt'));
if (empty($version)) {
$version = 'unknown';
}
$this->view->update_to_apply = true;
$this->view->message = array(
'status' => 'good',
'title' => _t('gen.short.ok'),
'body' => _t('feedback.update.can_apply', $version)
);
}
}
public function checkAction() {
$this->view->change_view('update', 'index');
if (file_exists(UPDATE_FILENAME)) {
// There is already an update file to apply: we don't need to check
// the webserver!
// Or if already check during the last hour, do nothing.
Minz_Request::forward(array('c' => 'update'), true);
return;
}
$auto_update_url = FreshRSS_Context::$system_conf->auto_update_url . '?v=' . FRESHRSS_VERSION;
$c = curl_init($auto_update_url);
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
$result = curl_exec($c);
$c_status = curl_getinfo($c, CURLINFO_HTTP_CODE);
$c_error = curl_error($c);
curl_close($c);
if ($c_status !== 200) {
Minz_Log::warning(
'Error during update (HTTP code ' . $c_status . '): ' . $c_error
);
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.update.server_not_found', $auto_update_url)
);
return;
}
$res_array = explode("\n", $result, 2);
$status = $res_array[0];
if (strpos($status, 'UPDATE') !== 0) {
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.update.none')
);
@touch(join_path(DATA_PATH, 'last_update.txt'));
return;
}
$script = $res_array[1];
if (file_put_contents(UPDATE_FILENAME, $script) !== false) {
$version = explode(' ', $status, 2);
$version = $version[1];
@file_put_contents(join_path(DATA_PATH, 'last_update.txt'), $version);
Minz_Request::forward(array('c' => 'update'), true);
} else {
$this->view->message = array(
'status' => 'bad',
'title' => _t('gen.short.damn'),
'body' => _t('feedback.update.error', 'Cannot save the update script')
);
}
}
public function applyAction() {
if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH)) {
Minz_Request::forward(array('c' => 'update'), true);
}
require(UPDATE_FILENAME);
if (Minz_Request::param('post_conf', false)) {
$res = do_post_update();
Minz_ExtensionManager::callHook('post_update');
if ($res === true) {
@unlink(UPDATE_FILENAME);
@file_put_contents(join_path(DATA_PATH, 'last_update.txt'), '');
Minz_Request::good(_t('feedback.update.finished'));
} else {
Minz_Request::bad(_t('feedback.update.error', $res),
array('c' => 'update', 'a' => 'index'));
}
}
if (Minz_Request::isPost()) {
save_info_update();
}
if (!need_info_update()) {
$res = apply_update();
if ($res === true) {
Minz_Request::forward(array(
'c' => 'update',
'a' => 'apply',
'params' => array('post_conf' => true)
), true);
} else {
Minz_Request::bad(_t('feedback.update.error', $res),
array('c' => 'update', 'a' => 'index'));
}
}
}
/**
* This action displays information about installation.
*/
public function checkInstallAction() {
Minz_View::prependTitle(_t('admin.check_install.title') . ' · ');
$this->view->status_php = check_install_php();
$this->view->status_files = check_install_files();
$this->view->status_database = check_install_database();
}
}

View file

@ -1,275 +0,0 @@
<?php
/**
* Controller to handle user actions.
*/
class FreshRSS_user_Controller extends Minz_ActionController {
// Will also have to be computed client side on mobile devices,
// so do not use a too high cost
const BCRYPT_COST = 9;
/**
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
*
* @todo clean up the access condition.
*/
public function firstAction() {
if (!FreshRSS_Auth::hasAccess() && !(
Minz_Request::actionName() === 'create' &&
!max_registrations_reached()
)) {
Minz_Error::error(403);
}
}
/**
* This action displays the user profile page.
*/
public function profileAction() {
Minz_View::prependTitle(_t('conf.profile.title') . ' · ');
Minz_View::appendScript(Minz_Url::display(
'/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
));
if (Minz_Request::isPost()) {
$ok = true;
$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
if ($passwordPlain != '') {
Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
$_POST['newPasswordPlain'] = '';
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 != '');
FreshRSS_Context::$user_conf->passwordHash = $passwordHash;
}
Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash);
$passwordPlain = Minz_Request::param('apiPasswordPlain', '', true);
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 != '');
FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash;
}
// TODO: why do we need of hasAccess here?
if (FreshRSS_Auth::hasAccess('admin')) {
FreshRSS_Context::$user_conf->mail_login = Minz_Request::param('mail_login', '', true);
}
$email = FreshRSS_Context::$user_conf->mail_login;
Minz_Session::_param('mail', $email);
$ok &= FreshRSS_Context::$user_conf->save();
if ($email != '') {
$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
@unlink($personaFile);
$ok &= (file_put_contents($personaFile, Minz_Session::param('currentUser', '_')) !== false);
}
if ($ok) {
Minz_Request::good(_t('feedback.profile.updated'),
array('c' => 'user', 'a' => 'profile'));
} else {
Minz_Request::bad(_t('feedback.profile.error'),
array('c' => 'user', 'a' => 'profile'));
}
}
}
/**
* This action displays the user management page.
*/
public function manageAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
Minz_View::prependTitle(_t('admin.user.title') . ' · ');
// Get the correct current user.
$username = Minz_Request::param('u', Minz_Session::param('currentUser'));
if (!FreshRSS_UserDAO::exist($username)) {
$username = Minz_Session::param('currentUser');
}
$this->view->current_user = $username;
// Get information about the current user.
$entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user);
$this->view->nb_articles = $entryDAO->count();
$this->view->size_user = $entryDAO->size();
}
/**
* This action creates a new user.
*
* Request parameters are:
* - new_user_language
* - new_user_name
* - new_user_passwordPlain
* - new_user_email
* - r (i.e. a redirection url, optional)
*
* @todo clean up this method. Idea: write a method to init a user with basic information.
* @todo handle r redirection in Minz_Request::forward directly?
*/
public function createAction() {
if (Minz_Request::isPost() && (
FreshRSS_Auth::hasAccess('admin') ||
!max_registrations_reached()
)) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
$languages = Minz_Translate::availableLanguages();
if (!isset($languages[$new_user_language])) {
$new_user_language = FreshRSS_Context::$user_conf->language;
}
$new_user_name = Minz_Request::param('new_user_name');
$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
if ($ok) {
$default_user = FreshRSS_Context::$system_conf->default_user;
$ok &= (strcasecmp($new_user_name, $default_user) !== 0); //It is forbidden to alter the default user
$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers())); //Not an existing user, case-insensitive
$configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
$ok &= !file_exists($configPath);
}
if ($ok) {
$passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
$passwordHash = '';
if ($passwordPlain != '') {
Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
$_POST['new_user_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 != '');
}
if (empty($passwordHash)) {
$passwordHash = '';
}
$new_user_email = filter_var($_POST['new_user_email'], FILTER_VALIDATE_EMAIL);
if (empty($new_user_email)) {
$new_user_email = '';
} else {
$personaFile = join_path(DATA_PATH, 'persona', $new_user_email . '.txt');
@unlink($personaFile);
$ok &= (file_put_contents($personaFile, $new_user_name) !== false);
}
}
if ($ok) {
mkdir(join_path(DATA_PATH, 'users', $new_user_name));
$config_array = array(
'language' => $new_user_language,
'passwordHash' => $passwordHash,
'mail_login' => $new_user_email,
);
$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';') !== false);
}
if ($ok) {
$userDAO = new FreshRSS_UserDAO();
$ok &= $userDAO->createUser($new_user_name);
}
invalidateHttpCache();
$notif = array(
'type' => $ok ? 'good' : 'bad',
'content' => _t('feedback.user.created' . (!$ok ? '.error' : ''), $new_user_name)
);
Minz_Session::_param('notification', $notif);
}
$redirect_url = urldecode(Minz_Request::param('r', false, true));
if (!$redirect_url) {
$redirect_url = array('c' => 'user', 'a' => 'manage');
}
Minz_Request::forward($redirect_url, true);
}
/**
* This action delete an existing user.
*
* Request parameter is:
* - username
*
* @todo clean up this method. Idea: create a User->clean() method.
*/
public function deleteAction() {
$username = Minz_Request::param('username');
$redirect_url = urldecode(Minz_Request::param('r', false, true));
if (!$redirect_url) {
$redirect_url = array('c' => 'user', 'a' => 'manage');
}
$self_deletion = Minz_Session::param('currentUser', '_') === $username;
if (Minz_Request::isPost() && (
FreshRSS_Auth::hasAccess('admin') ||
$self_deletion
)) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$ok = ctype_alnum($username);
$user_data = join_path(DATA_PATH, 'users', $username);
if ($ok) {
$default_user = FreshRSS_Context::$system_conf->default_user;
$ok &= (strcasecmp($username, $default_user) !== 0); //It is forbidden to delete the default user
}
if ($ok && $self_deletion) {
// We check the password if it's a self-destruction
$nonce = Minz_Session::param('nonce');
$challenge = Minz_Request::param('challenge', '');
$ok &= FreshRSS_FormAuth::checkCredentials(
$username, FreshRSS_Context::$user_conf->passwordHash,
$nonce, $challenge
);
}
if ($ok) {
$ok &= is_dir($user_data);
}
if ($ok) {
$userDAO = new FreshRSS_UserDAO();
$ok &= $userDAO->deleteUser($username);
$ok &= recursive_unlink($user_data);
//TODO: delete Persona file
}
if ($ok && $self_deletion) {
FreshRSS_Auth::removeAccess();
$redirect_url = array('c' => 'index', 'a' => 'index');
}
invalidateHttpCache();
$notif = array(
'type' => $ok ? 'good' : 'bad',
'content' => _t('feedback.user.deleted' . (!$ok ? '.error' : ''), $username)
);
Minz_Session::_param('notification', $notif);
}
Minz_Request::forward($redirect_url, true);
}
}

View file

@ -1,9 +0,0 @@
<?php
class FreshRSS_BadUrl_Exception extends FreshRSS_Feed_Exception {
public function __construct($url) {
parent::__construct('`' . $url . '` is not a valid URL');
}
}

View file

@ -1,8 +0,0 @@
<?php
/**
* An exception raised when a context is invalid
*/
class FreshRSS_Context_Exception extends Exception {
}

View file

@ -1,5 +0,0 @@
<?php
class FreshRSS_DAO_Exception extends Exception {
}

View file

@ -1,5 +0,0 @@
<?php
class FreshRSS_EntriesGetter_Exception extends Exception {
}

View file

@ -1,5 +0,0 @@
<?php
class FreshRSS_Feed_Exception extends Exception {
}

View file

@ -1,136 +0,0 @@
<?php
class FreshRSS extends Minz_FrontController {
/**
* Initialize the different FreshRSS / Minz components.
*
* PLEASE DON'T CHANGE THE ORDER OF INITIALIZATIONS UNLESS YOU KNOW WHAT
* YOU DO!!
*
* Here is the list of components:
* - Create a configuration setter and register it to system conf
* - Init extension manager and enable system extensions (has to be done asap)
* - Init authentication system
* - Init user configuration (need auth system)
* - Init FreshRSS context (need user conf)
* - Init i18n (need context)
* - Init sharing system (need user conf and i18n)
* - Init generic styles and scripts (need user conf)
* - Init notifications
* - Enable user extensions (need all the other initializations)
*/
public function init() {
if (!isset($_SESSION)) {
Minz_Session::init('FreshRSS');
}
// Register the configuration setter for the system configuration
$configuration_setter = new FreshRSS_ConfigurationSetter();
$system_conf = Minz_Configuration::get('system');
$system_conf->_configurationSetter($configuration_setter);
// Load list of extensions and enable the "system" ones.
Minz_ExtensionManager::init();
// Auth has to be initialized before using currentUser session parameter
// because it's this part which create this parameter.
self::initAuth();
// Then, register the user configuration and use the configuration setter
// created above.
$current_user = Minz_Session::param('currentUser', '_');
Minz_Configuration::register('user',
join_path(USERS_PATH, $current_user, 'config.php'),
join_path(USERS_PATH, '_', 'config.default.php'),
$configuration_setter);
// Finish to initialize the other FreshRSS / Minz components.
FreshRSS_Context::init();
self::initI18n();
self::loadNotifications();
// Enable extensions for the current (logged) user.
if (FreshRSS_Auth::hasAccess()) {
$ext_list = FreshRSS_Context::$user_conf->extensions_enabled;
Minz_ExtensionManager::enableByList($ext_list);
}
}
private static function initAuth() {
FreshRSS_Auth::init();
if (Minz_Request::isPost() && !is_referer_from_same_domain()) {
// Basic protection against XSRF attacks
FreshRSS_Auth::removeAccess();
$http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER'];
Minz_Translate::init('en'); //TODO: Better choice of fallback language
Minz_Error::error(
403,
array('error' => array(
_t('feedback.access.denied'),
' [HTTP_REFERER=' . htmlspecialchars($http_referer) . ']'
))
);
}
}
private static function initI18n() {
Minz_Session::_param('language', FreshRSS_Context::$user_conf->language);
Minz_Translate::init(FreshRSS_Context::$user_conf->language);
}
public static function loadStylesAndScripts() {
$theme = FreshRSS_Themes::load(FreshRSS_Context::$user_conf->theme);
if ($theme) {
foreach($theme['files'] as $file) {
if ($file[0] === '_') {
$theme_id = 'base-theme';
$filename = substr($file, 1);
} else {
$theme_id = $theme['id'];
$filename = $file;
}
$filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename);
$url = '/themes/' . $theme_id . '/' . $filename . '?' . $filetime;
header('Link: <' . Minz_Url::display($url, '', 'root') . '>;rel=preload', false); //HTTP2
Minz_View::appendStyle(Minz_Url::display($url));
}
}
Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')));
Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
if (FreshRSS_Context::$system_conf->auth_type === 'persona') {
// TODO move it in a plugin
// Needed for login AND logout with Persona.
Minz_View::appendScript('https://login.persona.org/include.js');
$file_mtime = @filemtime(PUBLIC_PATH . '/scripts/persona.js');
Minz_View::appendScript(Minz_Url::display('/scripts/persona.js?' . $file_mtime));
}
}
private static function loadNotifications() {
$notif = Minz_Session::param('notification');
if ($notif) {
Minz_View::_param('notification', $notif);
Minz_Session::_param('notification');
}
}
public static function preLayout() {
switch (Minz_Request::controllerName()) {
case 'index':
header("Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *");
break;
case 'stats':
header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'");
break;
default:
header("Content-Security-Policy: default-src 'self'");
break;
}
header("X-Content-Type-Options: nosniff");
FreshRSS_Share::load(join_path(DATA_PATH, 'shares.php'));
self::loadStylesAndScripts();
}
}

View file

@ -1,254 +0,0 @@
<?php
/**
* This class handles all authentication process.
*/
class FreshRSS_Auth {
/**
* Determines if user is connected.
*/
private static $login_ok = false;
/**
* This method initializes authentication system.
*/
public static function init() {
self::$login_ok = Minz_Session::param('loginOk', false);
$current_user = Minz_Session::param('currentUser', '');
if ($current_user === '') {
$conf = Minz_Configuration::get('system');
$current_user = $conf->default_user;
Minz_Session::_param('currentUser', $current_user);
}
if (self::$login_ok) {
self::giveAccess();
} elseif (self::accessControl()) {
self::giveAccess();
FreshRSS_UserDAO::touch($current_user);
} else {
// Be sure all accesses are removed!
self::removeAccess();
}
}
/**
* This method checks if user is allowed to connect.
*
* Required session parameters are also set in this method (such as
* currentUser).
*
* @return boolean true if user can be connected, false else.
*/
private static function accessControl() {
$conf = Minz_Configuration::get('system');
$auth_type = $conf->auth_type;
switch ($auth_type) {
case 'form':
$credentials = FreshRSS_FormAuth::getCredentialsFromCookie();
$current_user = '';
if (isset($credentials[1])) {
$current_user = trim($credentials[0]);
Minz_Session::_param('currentUser', $current_user);
Minz_Session::_param('passwordHash', trim($credentials[1]));
}
return $current_user != '';
case 'http_auth':
$current_user = httpAuthUser();
$login_ok = $current_user != '';
if ($login_ok) {
Minz_Session::_param('currentUser', $current_user);
}
return $login_ok;
case 'persona':
$email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL);
$persona_file = DATA_PATH . '/persona/' . $email . '.txt';
if (($current_user = @file_get_contents($persona_file)) !== false) {
$current_user = trim($current_user);
Minz_Session::_param('currentUser', $current_user);
Minz_Session::_param('mail', $email);
return true;
}
return false;
case 'none':
return true;
default:
// TODO load extension
return false;
}
}
/**
* Gives access to the current user.
*/
public static function giveAccess() {
$current_user = Minz_Session::param('currentUser');
$user_conf = get_user_configuration($current_user);
$system_conf = Minz_Configuration::get('system');
switch ($system_conf->auth_type) {
case 'form':
self::$login_ok = Minz_Session::param('passwordHash') === $user_conf->passwordHash;
break;
case 'http_auth':
self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0;
break;
case 'persona':
self::$login_ok = strcasecmp(Minz_Session::param('mail'), $user_conf->mail_login) === 0;
break;
case 'none':
self::$login_ok = true;
break;
default:
// TODO: extensions
self::$login_ok = false;
}
Minz_Session::_param('loginOk', self::$login_ok);
}
/**
* Returns if current user has access to the given scope.
*
* @param string $scope general (default) or admin
* @return boolean true if user has corresponding access, false else.
*/
public static function hasAccess($scope = 'general') {
$conf = Minz_Configuration::get('system');
$default_user = $conf->default_user;
$ok = self::$login_ok;
switch ($scope) {
case 'general':
break;
case 'admin':
$ok &= Minz_Session::param('currentUser') === $default_user;
break;
default:
$ok = false;
}
return $ok;
}
/**
* Removes all accesses for the current user.
*/
public static function removeAccess() {
Minz_Session::_param('loginOk');
self::$login_ok = false;
$conf = Minz_Configuration::get('system');
Minz_Session::_param('currentUser', $conf->default_user);
switch ($conf->auth_type) {
case 'form':
Minz_Session::_param('passwordHash');
FreshRSS_FormAuth::deleteCookie();
break;
case 'persona':
Minz_Session::_param('mail');
break;
case 'http_auth':
case 'none':
// Nothing to do...
break;
default:
// TODO: extensions
}
}
/**
* Return if authentication is enabled on this instance of FRSS.
*/
public static function accessNeedsLogin() {
$conf = Minz_Configuration::get('system');
$auth_type = $conf->auth_type;
return $auth_type !== 'none';
}
/**
* Return if authentication requires a PHP action.
*/
public static function accessNeedsAction() {
$conf = Minz_Configuration::get('system');
$auth_type = $conf->auth_type;
return $auth_type === 'form' || $auth_type === 'persona';
}
}
class FreshRSS_FormAuth {
public static function checkCredentials($username, $hash, $nonce, $challenge) {
if (!ctype_alnum($username) ||
!ctype_graph($challenge) ||
!ctype_alnum($nonce)) {
Minz_Log::debug('Invalid credential parameters:' .
' user=' . $username .
' challenge=' . $challenge .
' nonce=' . $nonce);
return false;
}
if (!function_exists('password_verify')) {
include_once(LIB_PATH . '/password_compat.php');
}
return password_verify($nonce . $hash, $challenge);
}
public static function getCredentialsFromCookie() {
$token = Minz_Session::getLongTermCookie('FreshRSS_login');
if (!ctype_alnum($token)) {
return array();
}
$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
$mtime = @filemtime($token_file);
if ($mtime + 2629744 < time()) {
// Token has expired (> 1 month) or does not exist.
// TODO: 1 month -> use a configuration instead
@unlink($token_file);
return array();
}
$credentials = @file_get_contents($token_file);
return $credentials === false ? array() : explode("\t", $credentials, 2);
}
public static function makeCookie($username, $password_hash) {
do {
$conf = Minz_Configuration::get('system');
$token = sha1($conf->salt . $username . uniqid(mt_rand(), true));
$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
} while (file_exists($token_file));
if (@file_put_contents($token_file, $username . "\t" . $password_hash) === false) {
return false;
}
$expire = time() + 2629744; //1 month //TODO: Use a configuration instead
Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
return $token;
}
public static function deleteCookie() {
$token = Minz_Session::getLongTermCookie('FreshRSS_login');
Minz_Session::deleteLongTermCookie('FreshRSS_login');
if (ctype_alnum($token)) {
@unlink(DATA_PATH . '/tokens/' . $token . '.txt');
}
if (rand(0, 10) === 1) {
self::purgeTokens();
}
}
public static function purgeTokens() {
$oldest = time() - 2629744; // 1 month // TODO: Use a configuration instead
foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) {
// $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7
$extension = pathinfo($file_info->getFilename(), PATHINFO_EXTENSION);
if ($extension === 'txt' && $file_info->getMTime() < $oldest) {
@unlink($file_info->getPathname());
}
}
}
}

View file

@ -1,80 +0,0 @@
<?php
class FreshRSS_Category extends Minz_Model {
private $id = 0;
private $name;
private $nbFeed = -1;
private $nbNotRead = -1;
private $feeds = null;
private $hasFeedsWithError = false;
public function __construct($name = '', $feeds = null) {
$this->_name($name);
if (isset($feeds)) {
$this->_feeds($feeds);
$this->nbFeed = 0;
$this->nbNotRead = 0;
foreach ($feeds as $feed) {
$this->nbFeed++;
$this->nbNotRead += $feed->nbNotRead();
$this->hasFeedsWithError |= $feed->inError();
}
}
}
public function id() {
return $this->id;
}
public function name() {
return $this->name;
}
public function nbFeed() {
if ($this->nbFeed < 0) {
$catDAO = new FreshRSS_CategoryDAO();
$this->nbFeed = $catDAO->countFeed($this->id());
}
return $this->nbFeed;
}
public function nbNotRead() {
if ($this->nbNotRead < 0) {
$catDAO = new FreshRSS_CategoryDAO();
$this->nbNotRead = $catDAO->countNotRead($this->id());
}
return $this->nbNotRead;
}
public function feeds() {
if ($this->feeds === null) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->feeds = $feedDAO->listByCategory($this->id());
$this->nbFeed = 0;
$this->nbNotRead = 0;
foreach ($this->feeds as $feed) {
$this->nbFeed++;
$this->nbNotRead += $feed->nbNotRead();
$this->hasFeedsWithError |= $feed->inError();
}
}
return $this->feeds;
}
public function hasFeedsWithError() {
return $this->hasFeedsWithError;
}
public function _id($value) {
$this->id = $value;
}
public function _name($value) {
$this->name = substr(trim($value), 0, 255);
}
public function _feeds($values) {
if (!is_array($values)) {
$values = array($values);
}
$this->feeds = $values;
}
}

View file

@ -1,257 +0,0 @@
<?php
class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function addCategory($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)';
$stm = $this->bd->prepare($sql);
$values = array(
substr($valuesTmp['name'], 0, 255),
);
if ($stm && $stm->execute($values)) {
return $this->bd->lastInsertId();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addCategory: ' . $info[2]);
return false;
}
}
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=? WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array(
$valuesTmp['name'],
$id
);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateCategory: ' . $info[2]);
return false;
}
}
public function deleteCategory($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error deleteCategory: ' . $info[2]);
return false;
}
}
public function searchById($id) {
$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$cat = self::daoToCategory($res);
if (isset($cat[0])) {
return $cat[0];
} else {
return null;
}
}
public function searchByName($name) {
$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?';
$stm = $this->bd->prepare($sql);
$values = array($name);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$cat = self::daoToCategory($res);
if (isset($cat[0])) {
return $cat[0];
} else {
return null;
}
}
public function listCategories($prePopulateFeeds = true, $details = false) {
if ($prePopulateFeeds) {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
. ($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 '
. 'GROUP BY f.id, c_id '
. 'ORDER BY c.name, f.name';
$stm = $this->bd->prepare($sql);
$stm->execute();
return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC));
} else {
$sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name';
$stm = $this->bd->prepare($sql);
$stm->execute();
return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC));
}
}
public function getDefault() {
$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$cat = self::daoToCategory($res);
if (isset($cat[0])) {
return $cat[0];
} else {
return false;
}
}
public function checkDefault() {
$def_cat = $this->searchById(1);
if ($def_cat == null) {
$cat = new FreshRSS_Category(_t('gen.short.default_category'));
$cat->_id(1);
$values = array(
'id' => $cat->id(),
'name' => $cat->name(),
);
$this->addCategory($values);
}
}
public function count() {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
public function countFeed($id) {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
public function countNotRead($id) {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
public static function findFeed($categories, $feed_id) {
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->id() === $feed_id) {
return $feed;
}
}
}
return null;
}
public static function CountUnreads($categories, $minPriority = 0) {
$n = 0;
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->priority() >= $minPriority) {
$n += $feed->nbNotRead();
}
}
}
return $n;
}
public static function daoToCategoryPrepopulated($listDAO) {
$list = array();
if (!is_array($listDAO)) {
$listDAO = array($listDAO);
}
$previousLine = null;
$feedsDao = array();
foreach ($listDAO as $line) {
if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) {
// End of the current category, we add it to the $list
$cat = new FreshRSS_Category(
$previousLine['c_name'],
FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id'])
);
$cat->_id($previousLine['c_id']);
$list[$previousLine['c_id']] = $cat;
$feedsDao = array(); //Prepare for next category
}
$previousLine = $line;
$feedsDao[] = $line;
}
// add the last category
if ($previousLine != null) {
$cat = new FreshRSS_Category(
$previousLine['c_name'],
FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id'])
);
$cat->_id($previousLine['c_id']);
$list[$previousLine['c_id']] = $cat;
}
return $list;
}
public static function daoToCategory($listDAO) {
$list = array();
if (!is_array($listDAO)) {
$listDAO = array($listDAO);
}
foreach ($listDAO as $key => $dao) {
$cat = new FreshRSS_Category(
$dao['name']
);
$cat->_id($dao['id']);
$list[$key] = $cat;
}
return $list;
}
}

View file

@ -1,389 +0,0 @@
<?php
class FreshRSS_ConfigurationSetter {
/**
* Return if the given key is supported by this setter.
* @param $key the key to test.
* @return true if the key is supported, false else.
*/
public function support($key) {
$name_setter = '_' . $key;
return is_callable(array($this, $name_setter));
}
/**
* Set the given key in data with the current value.
* @param $data an array containing the list of all configuration data.
* @param $key the key to update.
* @param $value the value to set.
*/
public function handle(&$data, $key, $value) {
$name_setter = '_' . $key;
call_user_func_array(array($this, $name_setter), array(&$data, $value));
}
/**
* A helper to set boolean values.
*
* @param $value the tested value.
* @return true if value is true and different from no, false else.
*/
private function handleBool($value) {
return ((bool)$value) && $value !== 'no';
}
/**
* The (long) list of setters for user configuration.
*/
private function _apiPasswordHash(&$data, $value) {
$data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
}
private function _content_width(&$data, $value) {
$value = strtolower($value);
if (!in_array($value, array('thin', 'medium', 'large', 'no_limit'))) {
$value = 'thin';
}
$data['content_width'] = $value;
}
private function _default_state(&$data, $value) {
$data['default_state'] = (int)$value;
}
private function _default_view(&$data, $value) {
switch ($value) {
case 'all':
$data['default_view'] = $value;
$data['default_state'] = (FreshRSS_Entry::STATE_READ +
FreshRSS_Entry::STATE_NOT_READ);
break;
case 'adaptive':
case 'unread':
default:
$data['default_view'] = $value;
$data['default_state'] = FreshRSS_Entry::STATE_NOT_READ;
}
}
// It works for system config too!
private function _extensions_enabled(&$data, $value) {
if (!is_array($value)) {
$value = array($value);
}
$data['extensions_enabled'] = $value;
}
private function _html5_notif_timeout(&$data, $value) {
$value = intval($value);
$data['html5_notif_timeout'] = $value >= 0 ? $value : 0;
}
private function _keep_history_default(&$data, $value) {
$value = intval($value);
$data['keep_history_default'] = $value >= -1 ? $value : 0;
}
// It works for system config too!
private function _language(&$data, $value) {
$value = strtolower($value);
$languages = Minz_Translate::availableLanguages();
if (!in_array($value, $languages)) {
$value = 'en';
}
$data['language'] = $value;
}
private function _mail_login(&$data, $value) {
$value = filter_var($value, FILTER_VALIDATE_EMAIL);
$data['mail_login'] = $value ? $value : '';
}
private function _old_entries(&$data, $value) {
$value = intval($value);
$data['old_entries'] = $value > 0 ? $value : 3;
}
private function _passwordHash(&$data, $value) {
$data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
}
private function _posts_per_page(&$data, $value) {
$value = intval($value);
$data['posts_per_page'] = $value > 0 ? $value : 10;
}
private function _queries(&$data, $values) {
$data['queries'] = array();
foreach ($values as $value) {
if ($value instanceof FreshRSS_UserQuery) {
$data['queries'][] = $value->toArray();
} elseif (is_array($value)) {
$data['queries'][] = $value;
}
}
}
private function _sharing(&$data, $values) {
$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['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) {
continue;
}
} else {
$value['url'] = null;
}
$data['sharing'][] = $value;
}
}
private function _shortcuts(&$data, $values) {
if (!is_array($values)) {
return;
}
$data['shortcuts'] = $values;
}
private function _sort_order(&$data, $value) {
$data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
}
private function _ttl_default(&$data, $value) {
$value = intval($value);
$data['ttl_default'] = $value >= -1 ? $value : 3600;
}
private function _view_mode(&$data, $value) {
$value = strtolower($value);
if (!in_array($value, array('global', 'normal', 'reader'))) {
$value = 'normal';
}
$data['view_mode'] = $value;
}
/**
* A list of boolean setters.
*/
private function _anon_access(&$data, $value) {
$data['anon_access'] = $this->handleBool($value);
}
private function _auto_load_more(&$data, $value) {
$data['auto_load_more'] = $this->handleBool($value);
}
private function _auto_remove_article(&$data, $value) {
$data['auto_remove_article'] = $this->handleBool($value);
}
private function _mark_updated_article_unread(&$data, $value) {
$data['mark_updated_article_unread'] = $this->handleBool($value);
}
private function _display_categories(&$data, $value) {
$data['display_categories'] = $this->handleBool($value);
}
private function _display_posts(&$data, $value) {
$data['display_posts'] = $this->handleBool($value);
}
private function _hide_read_feeds(&$data, $value) {
$data['hide_read_feeds'] = $this->handleBool($value);
}
private function _lazyload(&$data, $value) {
$data['lazyload'] = $this->handleBool($value);
}
private function _mark_when(&$data, $values) {
foreach ($values as $key => $value) {
$data['mark_when'][$key] = $this->handleBool($value);
}
}
private function _onread_jump_next(&$data, $value) {
$data['onread_jump_next'] = $this->handleBool($value);
}
private function _reading_confirm(&$data, $value) {
$data['reading_confirm'] = $this->handleBool($value);
}
private function _sticky_post(&$data, $value) {
$data['sticky_post'] = $this->handleBool($value);
}
private function _bottomline_date(&$data, $value) {
$data['bottomline_date'] = $this->handleBool($value);
}
private function _bottomline_favorite(&$data, $value) {
$data['bottomline_favorite'] = $this->handleBool($value);
}
private function _bottomline_link(&$data, $value) {
$data['bottomline_link'] = $this->handleBool($value);
}
private function _bottomline_read(&$data, $value) {
$data['bottomline_read'] = $this->handleBool($value);
}
private function _bottomline_sharing(&$data, $value) {
$data['bottomline_sharing'] = $this->handleBool($value);
}
private function _bottomline_tags(&$data, $value) {
$data['bottomline_tags'] = $this->handleBool($value);
}
private function _topline_date(&$data, $value) {
$data['topline_date'] = $this->handleBool($value);
}
private function _topline_favorite(&$data, $value) {
$data['topline_favorite'] = $this->handleBool($value);
}
private function _topline_link(&$data, $value) {
$data['topline_link'] = $this->handleBool($value);
}
private function _topline_read(&$data, $value) {
$data['topline_read'] = $this->handleBool($value);
}
/**
* The (not so long) list of setters for system configuration.
*/
private function _allow_anonymous(&$data, $value) {
$data['allow_anonymous'] = $this->handleBool($value) && FreshRSS_Auth::accessNeedsAction();
}
private function _allow_anonymous_refresh(&$data, $value) {
$data['allow_anonymous_refresh'] = $this->handleBool($value) && $data['allow_anonymous'];
}
private function _api_enabled(&$data, $value) {
$data['api_enabled'] = $this->handleBool($value);
}
private function _auth_type(&$data, $value) {
$value = strtolower($value);
if (!in_array($value, array('form', 'http_auth', 'persona', 'none'))) {
$value = 'none';
}
$data['auth_type'] = $value;
$this->_allow_anonymous($data, $data['allow_anonymous']);
}
private function _db(&$data, $value) {
if (!isset($value['type'])) {
return;
}
switch ($value['type']) {
case 'mysql':
if (empty($value['host']) ||
empty($value['user']) ||
empty($value['base']) ||
!isset($value['password'])) {
return;
}
$data['db']['type'] = $value['type'];
$data['db']['host'] = $value['host'];
$data['db']['user'] = $value['user'];
$data['db']['base'] = $value['base'];
$data['db']['password'] = $value['password'];
$data['db']['prefix'] = isset($value['prefix']) ? $value['prefix'] : '';
break;
case 'sqlite':
$data['db']['type'] = $value['type'];
$data['db']['host'] = '';
$data['db']['user'] = '';
$data['db']['base'] = '';
$data['db']['password'] = '';
$data['db']['prefix'] = '';
break;
default:
return;
}
}
private function _default_user(&$data, $value) {
$user_list = listUsers();
if (in_array($value, $user_list)) {
$data['default_user'] = $value;
}
}
private function _environment(&$data, $value) {
$value = strtolower($value);
if (!in_array($value, array('silent', 'development', 'production'))) {
$value = 'production';
}
$data['environment'] = $value;
}
private function _limits(&$data, $values) {
$max_small_int = 16384;
$limits_keys = array(
'cache_duration' => array(
'min' => 0,
),
'timeout' => array(
'min' => 0,
),
'max_inactivity' => array(
'min' => 0,
),
'max_feeds' => array(
'min' => 0,
'max' => $max_small_int,
),
'max_categories' => array(
'min' => 0,
'max' => $max_small_int,
),
'max_registrations' => array(
'min' => 0,
),
);
foreach ($values as $key => $value) {
if (!isset($limits_keys[$key])) {
continue;
}
$value = intval($value);
$limits = $limits_keys[$key];
if (
(!isset($limits['min']) || $value >= $limits['min']) &&
(!isset($limits['max']) || $value <= $limits['max'])
) {
$data['limits'][$key] = $value;
}
}
}
private function _unsafe_autologin_enabled(&$data, $value) {
$data['unsafe_autologin_enabled'] = $this->handleBool($value);
}
private function _auto_update_url(&$data, $value) {
if (!$value) {
return;
}
$data['auto_update_url'] = $value;
}
}

View file

@ -1,314 +0,0 @@
<?php
/**
* The context object handles the current configuration file and different
* useful functions associated to the current view state.
*/
class FreshRSS_Context {
public static $user_conf = null;
public static $system_conf = null;
public static $categories = array();
public static $name = '';
public static $description = '';
public static $total_unread = 0;
public static $total_starred = array(
'all' => 0,
'read' => 0,
'unread' => 0,
);
public static $get_unread = 0;
public static $current_get = array(
'all' => false,
'starred' => false,
'feed' => false,
'category' => false,
);
public static $next_get = 'a';
public static $state = 0;
public static $order = 'DESC';
public static $number = 0;
public static $search;
public static $first_id = '';
public static $next_id = '';
public static $id_max = '';
/**
* Initialize the context.
*
* Set the correct configurations and $categories variables.
*/
public static function init() {
// Init configuration.
self::$system_conf = Minz_Configuration::get('system');
self::$user_conf = Minz_Configuration::get('user');
$catDAO = new FreshRSS_CategoryDAO();
self::$categories = $catDAO->listCategories();
}
/**
* Returns if the current state includes $state parameter.
*/
public static function isStateEnabled($state) {
return self::$state & $state;
}
/**
* Returns the current state with or without $state parameter.
*/
public static function getRevertState($state) {
if (self::$state & $state) {
return self::$state & ~$state;
} else {
return self::$state | $state;
}
}
/**
* Return the current get as a string or an array.
*
* If $array is true, the first item of the returned value is 'f' or 'c' and
* the second is the id.
*/
public static function currentGet($array = false) {
if (self::$current_get['all']) {
return 'a';
} elseif (self::$current_get['starred']) {
return 's';
} elseif (self::$current_get['feed']) {
if ($array) {
return array('f', self::$current_get['feed']);
} else {
return 'f_' . self::$current_get['feed'];
}
} elseif (self::$current_get['category']) {
if ($array) {
return array('c', self::$current_get['category']);
} else {
return 'c_' . self::$current_get['category'];
}
}
}
/**
* Return true if the current request targets a feed (and not a category or all articles), false otherwise.
*/
public static function isFeed() {
return self::$current_get['feed'] != false;
}
/**
* Return true if $get parameter correspond to the $current_get attribute.
*/
public static function isCurrentGet($get) {
$type = $get[0];
$id = substr($get, 2);
switch($type) {
case 'a':
return self::$current_get['all'];
case 's':
return self::$current_get['starred'];
case 'f':
return self::$current_get['feed'] == $id;
case 'c':
return self::$current_get['category'] == $id;
default:
return false;
}
}
/**
* Set the current $get attribute.
*
* Valid $get parameter are:
* - a
* - s
* - f_<feed id>
* - c_<category id>
*
* $name and $get_unread attributes are also updated as $next_get
* Raise an exception if id or $get is invalid.
*/
public static function _get($get) {
$type = $get[0];
$id = substr($get, 2);
$nb_unread = 0;
switch($type) {
case 'a':
self::$current_get['all'] = true;
self::$name = _t('index.feed.title');
self::$get_unread = self::$total_unread;
break;
case 's':
self::$current_get['starred'] = true;
self::$name = _t('index.feed.title_fav');
self::$get_unread = self::$total_starred['unread'];
// Update state if favorite is not yet enabled.
self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE;
break;
case 'f':
// We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description
$feed = FreshRSS_Context::$system_conf->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id);
if ($feed === null) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($id);
if (!$feed) {
throw new FreshRSS_Context_Exception('Invalid feed: ' . $id);
}
}
self::$current_get['feed'] = $id;
self::$current_get['category'] = $feed->category();
self::$name = $feed->name();
self::$description = $feed->description();
self::$get_unread = $feed->nbNotRead();
break;
case 'c':
// We try to find the corresponding category.
self::$current_get['category'] = $id;
if (!isset(self::$categories[$id])) {
$catDAO = new FreshRSS_CategoryDAO();
$cat = $catDAO->searchById($id);
if (!$cat) {
throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
}
} else {
$cat = self::$categories[$id];
}
self::$name = $cat->name();
self::$get_unread = $cat->nbNotRead();
break;
default:
throw new FreshRSS_Context_Exception('Invalid getter: ' . $get);
}
self::_nextGet();
}
/**
* Set the value of $next_get attribute.
*/
public static function _nextGet() {
$get = self::currentGet();
// By default, $next_get == $get
self::$next_get = $get;
if (self::$user_conf->onread_jump_next && strlen($get) > 2) {
$another_unread_id = '';
$found_current_get = false;
switch ($get[0]) {
case 'f':
// We search the next feed with at least one unread article in
// same category as the currend feed.
foreach (self::$categories as $cat) {
if ($cat->id() != self::$current_get['category']) {
// We look into the category of the current feed!
continue;
}
foreach ($cat->feeds() as $feed) {
if ($feed->id() == self::$current_get['feed']) {
// Here is our current feed! Fine, the next one will
// be a potential candidate.
$found_current_get = true;
continue;
}
if ($feed->nbNotRead() > 0) {
$another_unread_id = $feed->id();
if ($found_current_get) {
// We have found our current feed and now we
// have an feed with unread articles. Leave the
// loop!
break;
}
}
}
break;
}
// If no feed have been found, next_get is the current category.
self::$next_get = empty($another_unread_id) ?
'c_' . self::$current_get['category'] :
'f_' . $another_unread_id;
break;
case 'c':
// We search the next category with at least one unread article.
foreach (self::$categories as $cat) {
if ($cat->id() == self::$current_get['category']) {
// Here is our current category! Next one could be our
// champion if it has unread articles.
$found_current_get = true;
continue;
}
if ($cat->nbNotRead() > 0) {
$another_unread_id = $cat->id();
if ($found_current_get) {
// Unread articles and the current category has
// already been found? Leave the loop!
break;
}
}
}
// No unread category? The main stream will be our destination!
self::$next_get = empty($another_unread_id) ?
'a' :
'c_' . $another_unread_id;
break;
}
}
}
/**
* Determine if the auto remove is available in the current context.
* This feature is available if:
* - it is activated in the configuration
* - the "read" state is not enable
* - the "unread" state is enable
*
* @return boolean
*/
public static function isAutoRemoveAvailable() {
if (!self::$user_conf->auto_remove_article) {
return false;
}
if (self::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
return false;
}
if (!self::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ)) {
return false;
}
return true;
}
/**
* Determine if the "sticky post" option is enabled. It can be enable
* by the user when it is selected in the configuration page or by the
* application when the context allows to auto-remove articles when they
* are read.
*
* @return boolean
*/
public static function isStickyPostEnabled() {
if (self::$user_conf->sticky_post) {
return true;
}
if (self::isAutoRemoveAvailable()) {
return true;
}
return false;
}
}

View file

@ -1,83 +0,0 @@
<?php
/**
* This class is used to test database is well-constructed.
*/
class FreshRSS_DatabaseDAO extends Minz_ModelPdo {
public function tablesAreCorrect() {
$sql = 'SHOW TABLES';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$tables = array(
$this->prefix . 'category' => false,
$this->prefix . 'feed' => false,
$this->prefix . 'entry' => false,
);
foreach ($res as $value) {
$tables[array_pop($value)] = true;
}
return count(array_keys($tables, true, true)) == count($tables);
}
public function getSchema($table) {
$sql = 'DESC ' . $this->prefix . $table;
$stm = $this->bd->prepare($sql);
$stm->execute();
return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function checkTable($table, $schema) {
$columns = $this->getSchema($table);
$ok = (count($columns) == count($schema));
foreach ($columns as $c) {
$ok &= in_array($c['name'], $schema);
}
return $ok;
}
public function categoryIsCorrect() {
return $this->checkTable('category', array(
'id', 'name'
));
}
public function feedIsCorrect() {
return $this->checkTable('feed', array(
'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate',
'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl',
'cache_nbEntries', 'cache_nbUnreads'
));
}
public function entryIsCorrect() {
return $this->checkTable('entry', array(
'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'is_read',
'is_favorite', 'id_feed', 'tags'
));
}
public function daoToSchema($dao) {
return array(
'name' => $dao['Field'],
'type' => strtolower($dao['Type']),
'notnull' => (bool)$dao['Null'],
'default' => $dao['Default'],
);
}
public function listDaoToSchema($listDAO) {
$list = array();
foreach ($listDAO as $dao) {
$list[] = $this->daoToSchema($dao);
}
return $list;
}
}

View file

@ -1,48 +0,0 @@
<?php
/**
* This class is used to test database is well-constructed (SQLite).
*/
class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO {
public function tablesAreCorrect() {
$sql = 'SELECT name FROM sqlite_master WHERE type="table"';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$tables = array(
'category' => false,
'feed' => false,
'entry' => false,
);
foreach ($res as $value) {
$tables[$value['name']] = true;
}
return count(array_keys($tables, true, true)) == count($tables);
}
public function getSchema($table) {
$sql = 'PRAGMA table_info(' . $table . ')';
$stm = $this->bd->prepare($sql);
$stm->execute();
return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function entryIsCorrect() {
return $this->checkTable('entry', array(
'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'is_read',
'is_favorite', 'id_feed', 'tags'
));
}
public function daoToSchema($dao) {
return array(
'name' => $dao['name'],
'type' => strtolower($dao['type']),
'notnull' => $dao['notnull'] === '1' ? true : false,
'default' => $dao['dflt_value'],
);
}
}

View file

@ -1,7 +0,0 @@
<?php
class FreshRSS_Days {
const TODAY = 0;
const YESTERDAY = 1;
const BEFORE_YESTERDAY = 2;
}

View file

@ -1,207 +0,0 @@
<?php
class FreshRSS_Entry extends Minz_Model {
const STATE_READ = 1;
const STATE_NOT_READ = 2;
const STATE_ALL = 3;
const STATE_FAVORITE = 4;
const STATE_NOT_FAVORITE = 8;
private $id = 0;
private $guid;
private $title;
private $author;
private $content;
private $link;
private $date;
private $hash = null;
private $is_read; //Nullable boolean
private $is_favorite;
private $feed;
private $tags;
public function __construct($feed = '', $guid = '', $title = '', $author = '', $content = '',
$link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
$this->_guid($guid);
$this->_title($title);
$this->_author($author);
$this->_content($content);
$this->_link($link);
$this->_date($pubdate);
$this->_isRead($is_read);
$this->_isFavorite($is_favorite);
$this->_feed($feed);
$this->_tags(preg_split('/[\s#]/', $tags));
}
public function id() {
return $this->id;
}
public function guid() {
return $this->guid;
}
public function title() {
return $this->title;
}
public function author() {
return $this->author === null ? '' : $this->author;
}
public function content() {
return $this->content;
}
public function link() {
return $this->link;
}
public function date($raw = false) {
if ($raw) {
return $this->date;
} else {
return timestamptodate($this->date);
}
}
public function dateAdded($raw = false) {
$date = intval(substr($this->id, 0, -6));
if ($raw) {
return $date;
} else {
return timestamptodate($date);
}
}
public function isRead() {
return $this->is_read;
}
public function isFavorite() {
return $this->is_favorite;
}
public function feed($object = false) {
if ($object) {
$feedDAO = FreshRSS_Factory::createFeedDao();
return $feedDAO->searchById($this->feed);
} else {
return $this->feed;
}
}
public function tags($inString = false) {
if ($inString) {
return empty($this->tags) ? '' : '#' . implode(' #', $this->tags);
} else {
return $this->tags;
}
}
public function hash() {
if ($this->hash === null) {
//Do not include $this->date because it may be automatically generated when lacking
$this->hash = md5($this->link . $this->title . $this->author . $this->content . $this->tags(true));
}
return $this->hash;
}
public function _id($value) {
$this->id = $value;
}
public function _guid($value) {
$this->guid = $value;
}
public function _title($value) {
$this->hash = null;
$this->title = $value;
}
public function _author($value) {
$this->hash = null;
$this->author = $value;
}
public function _content($value) {
$this->hash = null;
$this->content = $value;
}
public function _link($value) {
$this->hash = null;
$this->link = $value;
}
public function _date($value) {
$this->hash = null;
$value = intval($value);
$this->date = $value > 1 ? $value : time();
}
public function _isRead($value) {
$this->is_read = $value === null ? null : (bool)$value;
}
public function _isFavorite($value) {
$this->is_favorite = $value;
}
public function _feed($value) {
$this->feed = $value;
}
public function _tags($value) {
$this->hash = null;
if (!is_array($value)) {
$value = array($value);
}
foreach ($value as $key => $t) {
if (!$t) {
unset($value[$key]);
}
}
$this->tags = $value;
}
public function isDay($day, $today) {
$date = $this->dateAdded(true);
switch ($day) {
case FreshRSS_Days::TODAY:
$tomorrow = $today + 86400;
return $date >= $today && $date < $tomorrow;
case FreshRSS_Days::YESTERDAY:
$yesterday = $today - 86400;
return $date >= $yesterday && $date < $today;
case FreshRSS_Days::BEFORE_YESTERDAY:
$yesterday = $today - 86400;
return $date < $yesterday;
default:
return false;
}
}
public function loadCompleteContent($pathEntries) {
// Gestion du contenu
// On cherche à récupérer les articles en entier... même si le flux ne le propose pas
if ($pathEntries) {
$entryDAO = FreshRSS_Factory::createEntryDao();
$entry = $entryDAO->searchByGuid($this->feed, $this->guid);
if ($entry) {
// l'article existe déjà en BDD, en se contente de recharger ce contenu
$this->content = $entry->content();
} else {
try {
// l'article n'est pas en BDD, on va le chercher sur le site
$this->content = get_content_by_parsing(
htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries
);
} catch (Exception $e) {
// rien à faire, on garde l'ancien contenu(requête a échoué)
}
}
}
}
public function toArray() {
return array(
'id' => $this->id(),
'guid' => $this->guid(),
'title' => $this->title(),
'author' => $this->author(),
'content' => $this->content(),
'link' => $this->link(),
'date' => $this->date(true),
'hash' => $this->hash(),
'is_read' => $this->isRead(),
'is_favorite' => $this->isFavorite(),
'id_feed' => $this->feed(),
'tags' => $this->tags(true),
);
}
}

View file

@ -1,742 +0,0 @@
<?php
class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function isCompressed() {
return parent::$sharedDbType !== 'sqlite';
}
public function hasNativeHex() {
return parent::$sharedDbType !== 'sqlite';
}
protected function addColumn($name) {
Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name);
$hasTransaction = false;
try {
$stm = null;
if ($name === 'lastSeen') { //v1.1.1
if (!$this->bd->inTransaction()) {
$this->bd->beginTransaction();
$hasTransaction = true;
}
$stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0');
if ($stm && $stm->execute()) {
$stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7
if ($stm && $stm->execute()) {
if ($hasTransaction) {
$this->bd->commit();
}
return true;
}
}
if ($hasTransaction) {
$this->bd->rollBack();
}
} elseif ($name === 'hash') { //v1.1.1
$stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)');
return $stm && $stm->execute();
}
} catch (Exception $e) {
Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage());
if ($hasTransaction) {
$this->bd->rollBack();
}
}
return false;
}
protected function autoAddColumn($errorInfo) {
if (isset($errorInfo[0])) {
if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
foreach (array('lastSeen', 'hash') as $column) {
if (stripos($errorInfo[2], $column) !== false) {
return $this->addColumn($column);
}
}
}
}
return false;
}
private $addEntryPrepared = null;
public function addEntry($valuesTmp) {
if ($this->addEntryPrepared === null) {
$sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, '
. ($this->isCompressed() ? 'content_bin' : 'content')
. ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) '
. 'VALUES(?, ?, ?, ?, '
. ($this->isCompressed() ? 'COMPRESS(?)' : '?')
. ', ?, ?, ?, '
. ($this->hasNativeHex() ? 'X?' : '?')
. ', ?, ?, ?, ?)';
$this->addEntryPrepared = $this->bd->prepare($sql);
}
$values = array(
$valuesTmp['id'],
substr($valuesTmp['guid'], 0, 760),
substr($valuesTmp['title'], 0, 255),
substr($valuesTmp['author'], 0, 255),
$valuesTmp['content'],
substr($valuesTmp['link'], 0, 1023),
$valuesTmp['date'],
time(),
$this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+
$valuesTmp['is_read'] ? 1 : 0,
$valuesTmp['is_favorite'] ? 1 : 0,
$valuesTmp['id_feed'],
substr($valuesTmp['tags'], 0, 1023),
);
if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) {
return $this->bd->lastInsertId();
} else {
$info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo();
if ($this->autoAddColumn($info)) {
return $this->addEntry($valuesTmp);
} elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']);
}
return false;
}
}
private $updateEntryPrepared = null;
public function updateEntry($valuesTmp) {
if (!isset($valuesTmp['is_read'])) {
$valuesTmp['is_read'] = null;
}
if ($this->updateEntryPrepared === null) {
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET title=?, author=?, '
. ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?')
. ', link=?, date=?, lastSeen=?, hash='
. ($this->hasNativeHex() ? 'X?' : '?')
. ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
. 'tags=? '
. 'WHERE id_feed=? AND guid=?';
$this->updateEntryPrepared = $this->bd->prepare($sql);
}
$values = array(
substr($valuesTmp['title'], 0, 255),
substr($valuesTmp['author'], 0, 255),
$valuesTmp['content'],
substr($valuesTmp['link'], 0, 1023),
$valuesTmp['date'],
time(),
$this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']),
);
if ($valuesTmp['is_read'] !== null) {
$values[] = $valuesTmp['is_read'] ? 1 : 0;
}
$values = array_merge($values, array(
substr($valuesTmp['tags'], 0, 1023),
$valuesTmp['id_feed'],
substr($valuesTmp['guid'], 0, 760),
));
if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) {
return $this->bd->lastInsertId();
} else {
$info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo();
if ($this->autoAddColumn($info)) {
return $this->updateEntry($valuesTmp);
}
Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while updating entry with GUID ' . $valuesTmp['guid'] . ' in feed ' . $valuesTmp['id_feed']);
return false;
}
}
/**
* Toggle favorite marker on one or more article
*
* @todo simplify the query by removing the str_repeat. I am pretty sure
* there is an other way to do that.
*
* @param integer|array $ids
* @param boolean $is_favorite
* @return false|integer
*/
public function markFavorite($ids, $is_favorite = true) {
if (!is_array($ids)) {
$ids = array($ids);
}
if (count($ids) < 1) {
return 0;
}
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_favorite=? '
. 'WHERE 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();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markFavorite: ' . $info[2]);
return false;
}
}
/**
* Update the unread article cache held on every feed details.
* Depending on the parameters, it updates the cache on one feed, on all
* feeds from one category or on all feeds.
*
* @todo It can use the query builder refactoring to build that query
*
* @param false|integer $catId category ID
* @param false|integer $feedId feed ID
* @return boolean
*/
protected function updateCacheUnreads($catId = false, $feedId = false) {
$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 '
. 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
. 'WHERE 1';
$values = array();
if ($feedId !== false) {
$sql .= ' AND f.id=?';
$values[] = $id;
}
if ($catId !== false) {
$sql .= ' AND f.category=?';
$values[] = $catId;
}
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
return true;
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]);
return false;
}
}
/**
* Toggle the read marker on one or more article.
* Then the cache is updated.
*
* @todo change the way the query is build because it seems there is
* unnecessary code in here. For instance, the part with the str_repeat.
* @todo remove code duplication. It seems the code is basically the
* same if it is an array or not.
*
* @param integer|array $ids
* @param boolean $is_read
* @return integer affected rows
*/
public function markRead($ids, $is_read = true) {
if (is_array($ids)) { //Many IDs at once (used by API)
if (count($ids) < 6) { //Speed heuristics
$affected = 0;
foreach ($ids as $id) {
$affected += $this->markRead($id, $is_read);
}
return $affected;
}
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=? '
. 'WHERE 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 == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markRead: ' . $info[2]);
return false;
}
$affected = $stm->rowCount();
if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
return false;
}
return $affected;
} else {
$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=? AND e.is_read=?';
$values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markRead: ' . $info[2]);
return false;
}
}
}
/**
* Mark all entries as read depending on parameters.
* If $onlyFavorites is true, it is used when the user mark as read in
* the favorite pseudo-category.
* If $priorityMin is greater than 0, it is used when the user mark as
* read in the main feed pseudo-category.
* Then the cache is updated.
*
* If $idMax equals 0, a deprecated debug message is logged
*
* @todo refactor this method along with markReadCat and markReadFeed
* since they are all doing the same thing. I think we need to build a
* tool to generate the query instead of having queries all over the
* place. It will be reused also for the filtering making every thing
* separated.
*
* @param integer $idMax fail safe article ID
* @param boolean $onlyFavorites
* @param integer $priorityMin
* @return integer affected rows
*/
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
}
$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 <= ?';
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);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadEntries: ' . $info[2]);
return false;
}
$affected = $stm->rowCount();
if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
return false;
}
return $affected;
}
/**
* Mark all the articles in a category as read.
* There is a fail safe to prevent to mark as read articles that are
* loaded during the mark as read action. Then the cache is updated.
*
* If $idMax equals 0, a deprecated debug message is logged
*
* @param integer $id category ID
* @param integer $idMax fail safe article ID
* @return integer affected rows
*/
public function markReadCat($id, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadCat(0) is deprecated!');
}
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
. 'SET e.is_read=1 '
. 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?';
$values = array($id, $idMax);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadCat: ' . $info[2]);
return false;
}
$affected = $stm->rowCount();
if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) {
return false;
}
return $affected;
}
/**
* Mark all the articles in a feed as read.
* There is a fail safe to prevent to mark as read articles that are
* loaded during the mark as read action. Then the cache is updated.
*
* If $idMax equals 0, a deprecated debug message is logged
*
* @param integer $id_feed feed ID
* @param integer $idMax fail safe article ID
* @return integer affected rows
*/
public function markReadFeed($id_feed, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadFeed(0) is deprecated!');
}
$this->bd->beginTransaction();
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=1 '
. 'WHERE id_feed=? AND is_read=0 AND id <= ?';
$values = array($id_feed, $idMax);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadFeed: ' . $info[2]);
$this->bd->rollBack();
return false;
}
$affected = $stm->rowCount();
if ($affected > 0) {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET cache_nbUnreads=cache_nbUnreads-' . $affected
. ' WHERE id=?';
$values = array($id_feed);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadFeed: ' . $info[2]);
$this->bd->rollBack();
return false;
}
}
$this->bd->commit();
return $affected;
}
public function searchByGuid($id_feed, $guid) {
// un guid est unique pour un flux donné
$sql = 'SELECT id, guid, title, author, '
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
. ', link, date, is_read, is_favorite, id_feed, tags '
. 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?';
$stm = $this->bd->prepare($sql);
$values = array(
$id_feed,
$guid,
);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$entries = self::daoToEntry($res);
return isset($entries[0]) ? $entries[0] : null;
}
public function searchById($id) {
$sql = 'SELECT id, guid, title, author, '
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
. ', link, date, is_read, is_favorite, id_feed, tags '
. 'FROM `' . $this->prefix . 'entry` WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$entries = self::daoToEntry($res);
return isset($entries[0]) ? $entries[0] : null;
}
protected function sqlConcat($s1, $s2) {
return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL
}
private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
if (!$state) {
$state = FreshRSS_Entry::STATE_ALL;
}
$where = '';
$joinFeed = false;
$values = array();
switch ($type) {
case 'a':
$where .= 'f.priority > 0 ';
$joinFeed = true;
break;
case 's': //Deprecated: use $state instead
$where .= 'e1.is_favorite=1 ';
break;
case 'c':
$where .= 'f.category=? ';
$values[] = intval($id);
$joinFeed = true;
break;
case 'f':
$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 . ']!');
}
if ($state & FreshRSS_Entry::STATE_NOT_READ) {
if (!($state & FreshRSS_Entry::STATE_READ)) {
$where .= 'AND e1.is_read=0 ';
}
}
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':
break;
default:
throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!');
}
/*if ($firstId === '' && parent::$sharedDbType === 'mysql') {
$firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. TODO: check if this is needed again, after the filtering for old articles has been removed in 0.9-dev
}*/
if ($firstId !== '') {
$where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
}
if ($date_min > 0) {
$where .= 'AND e1.id >= ' . $date_min . '000000 ';
}
$search = '';
if ($filter) {
if ($filter->getIntitle()) {
$search .= 'AND e1.title LIKE ? ';
$values[] = "%{$filter->getIntitle()}%";
}
if ($filter->getInurl()) {
$search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
$values[] = "%{$filter->getInurl()}%";
}
if ($filter->getAuthor()) {
$search .= 'AND e1.author LIKE ? ';
$values[] = "%{$filter->getAuthor()}%";
}
if ($filter->getMinDate()) {
$search .= 'AND e1.id >= ? ';
$values[] = "{$filter->getMinDate()}000000";
}
if ($filter->getMaxDate()) {
$search .= 'AND e1.id <= ? ';
$values[] = "{$filter->getMaxDate()}000000";
}
if ($filter->getMinPubdate()) {
$search .= 'AND e1.date >= ? ';
$values[] = $filter->getMinPubdate();
}
if ($filter->getMaxPubdate()) {
$search .= 'AND e1.date <= ? ';
$values[] = $filter->getMaxPubdate();
}
if ($filter->getTags()) {
$tags = $filter->getTags();
foreach ($tags as $tag) {
$search .= 'AND e1.tags LIKE ? ';
$values[] = "%{$tag}%";
}
}
if ($filter->getSearch()) {
$search_values = $filter->getSearch();
foreach ($search_values as $search_value) {
$search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
$values[] = "%{$search_value}%";
}
}
}
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) {
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
$sql = 'SELECT e.id, e.guid, e.title, e.author, '
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
. ', e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags '
. 'FROM `' . $this->prefix . 'entry` e '
. 'INNER JOIN ('
. $sql
. ') e2 ON e2.id=e.id '
. 'ORDER BY e.id ' . $order;
$stm = $this->bd->prepare($sql);
$stm->execute($values);
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) { //For API
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
$stm = $this->bd->prepare($sql);
$stm->execute($values);
return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
}
public function listHashForFeedGuids($id_feed, $guids) {
if (count($guids) < 1) {
return array();
}
$sql = 'SELECT guid, hex(hash) AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)';
$stm = $this->bd->prepare($sql);
$values = array($id_feed);
$values = array_merge($values, $guids);
if ($stm && $stm->execute($values)) {
$result = array();
$rows = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
$result[$row['guid']] = $row['hexHash'];
}
return $result;
} else {
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
if ($this->autoAddColumn($info)) {
return $this->listHashForFeedGuids($id_feed, $guids);
}
Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while querying feed ' . $id_feed);
return false;
}
}
public function updateLastSeen($id_feed, $guids) {
if (count($guids) < 1) {
return 0;
}
$sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)';
$stm = $this->bd->prepare($sql);
$values = array(time(), $id_feed);
$values = array_merge($values, $guids);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo();
if ($this->autoAddColumn($info)) {
return $this->updateLastSeen($id_feed, $guids);
}
Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
. ' while updating feed ' . $id_feed);
return false;
}
}
public function countUnreadRead() {
$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0'
. ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0 AND is_read=0';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
$all = empty($res[0]) ? 0 : $res[0];
$unread = empty($res[1]) ? 0 : $res[1];
return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
}
public function count($minPriority = null) {
$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id';
if ($minPriority !== null) {
$sql = ' WHERE priority > ' . intval($minPriority);
}
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
}
public function countNotRead($minPriority = null) {
$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE is_read=0';
if ($minPriority !== null) {
$sql = ' AND priority > ' . intval($minPriority);
}
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
}
public function countUnreadReadFavorites() {
$sql = 'SELECT c FROM ('
. 'SELECT COUNT(id) AS c, 1 as o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 '
. 'UNION SELECT COUNT(id) AS c, 2 AS o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read=0'
. ') u ORDER BY o';
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
$all = empty($res[0]) ? 0 : $res[0];
$unread = empty($res[1]) ? 0 : $res[1];
return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
}
public function optimizeTable() {
$sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL
$stm = $this->bd->prepare($sql);
$stm->execute();
}
public function size($all = false) {
$db = FreshRSS_Context::$system_conf->db;
$sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?'; //MySQL
$values = array($db['base']);
if (!$all) {
$sql .= ' AND table_name LIKE ?';
$values[] = $this->prefix . '%';
}
$stm = $this->bd->prepare($sql);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
return $res[0];
}
public static function daoToEntry($listDAO) {
$list = array();
if (!is_array($listDAO)) {
$listDAO = array($listDAO);
}
foreach ($listDAO as $key => $dao) {
$entry = new FreshRSS_Entry(
$dao['id_feed'],
$dao['guid'],
$dao['title'],
$dao['author'],
$dao['content'],
$dao['link'],
$dao['date'],
$dao['is_read'],
$dao['is_favorite'],
$dao['tags']
);
if (isset($dao['id'])) {
$entry->_id($dao['id']);
}
$list[] = $entry;
}
unset($listDAO);
return $list;
}
}

View file

@ -1,189 +0,0 @@
<?php
class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
protected function autoAddColumn($errorInfo) {
if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR
if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) {
$showCreate = $tableInfo->fetchColumn();
Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoAddColumn: ' . $showCreate);
foreach (array('lastSeen', 'hash') as $column) {
if (stripos($showCreate, $column) === false) {
return $this->addColumn($column);
}
}
}
}
return false;
}
protected function sqlConcat($s1, $s2) {
return $s1 . '||' . $s2;
}
protected function updateCacheUnreads($catId = false, $feedId = false) {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET cache_nbUnreads=('
. 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e '
. 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0) '
. 'WHERE 1';
$values = array();
if ($feedId !== false) {
$sql .= ' AND id=?';
$values[] = $feedId;
}
if ($catId !== false) {
$sql .= ' AND category=?';
$values[] = $catId;
}
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
return true;
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]);
return false;
}
}
/**
* Toggle the read marker on one or more article.
* Then the cache is updated.
*
* @todo change the way the query is build because it seems there is
* unnecessary code in here. For instance, the part with the str_repeat.
* @todo remove code duplication. It seems the code is basically the
* same if it is an array or not.
*
* @param integer|array $ids
* @param boolean $is_read
* @return integer affected rows
*/
public function markRead($ids, $is_read = true) {
if (is_array($ids)) { //Many IDs at once (used by API)
if (true) { //Speed heuristics //TODO: Not implemented yet for SQLite (so always call IDs one by one)
$affected = 0;
foreach ($ids as $id) {
$affected += $this->markRead($id, $is_read);
}
return $affected;
}
} else {
$this->bd->beginTransaction();
$sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?';
$values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markRead 1: ' . $info[2]);
$this->bd->rollBack();
return false;
}
$affected = $stm->rowCount();
if ($affected > 0) {
$sql = 'UPDATE `' . $this->prefix . 'feed` SET cache_nbUnreads=cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
. 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)';
$values = array($ids);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markRead 2: ' . $info[2]);
$this->bd->rollBack();
return false;
}
}
$this->bd->commit();
return $affected;
}
}
/**
* Mark all entries as read depending on parameters.
* If $onlyFavorites is true, it is used when the user mark as read in
* the favorite pseudo-category.
* If $priorityMin is greater than 0, it is used when the user mark as
* read in the main feed pseudo-category.
* Then the cache is updated.
*
* If $idMax equals 0, a deprecated debug message is logged
*
* @todo refactor this method along with markReadCat and markReadFeed
* since they are all doing the same thing. I think we need to build a
* tool to generate the query instead of having queries all over the
* place. It will be reused also for the filtering making every thing
* separated.
*
* @param integer $idMax fail safe article ID
* @param boolean $onlyFavorites
* @param integer $priorityMin
* @return integer affected rows
*/
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
}
$sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?';
if ($onlyFavorites) {
$sql .= ' AND is_favorite=1';
} elseif ($priorityMin >= 0) {
$sql .= ' AND id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.priority > ' . intval($priorityMin) . ')';
}
$values = array($idMax);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadEntries: ' . $info[2]);
return false;
}
$affected = $stm->rowCount();
if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
return false;
}
return $affected;
}
/**
* Mark all the articles in a category as read.
* There is a fail safe to prevent to mark as read articles that are
* loaded during the mark as read action. Then the cache is updated.
*
* If $idMax equals 0, a deprecated debug message is logged
*
* @param integer $id category ID
* @param integer $idMax fail safe article ID
* @return integer affected rows
*/
public function markReadCat($id, $idMax = 0) {
if ($idMax == 0) {
$idMax = time() . '000000';
Minz_Log::debug('Calling markReadCat(0) is deprecated!');
}
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=1 '
. 'WHERE is_read=0 AND id <= ? AND '
. 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)';
$values = array($idMax, $id);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error markReadCat: ' . $info[2]);
return false;
}
$affected = $stm->rowCount();
if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) {
return false;
}
return $affected;
}
public function optimizeTable() {
//TODO: Search for an equivalent in SQLite
}
public function size($all = false) {
return @filesize(join_path(DATA_PATH, 'users', $this->current_user, 'db.sqlite'));
}
}

View file

@ -1,41 +0,0 @@
<?php
class FreshRSS_Factory {
public static function createFeedDao($username = null) {
$conf = Minz_Configuration::get('system');
if ($conf->db['type'] === 'sqlite') {
return new FreshRSS_FeedDAOSQLite($username);
} else {
return new FreshRSS_FeedDAO($username);
}
}
public static function createEntryDao($username = null) {
$conf = Minz_Configuration::get('system');
if ($conf->db['type'] === 'sqlite') {
return new FreshRSS_EntryDAOSQLite($username);
} else {
return new FreshRSS_EntryDAO($username);
}
}
public static function createStatsDAO($username = null) {
$conf = Minz_Configuration::get('system');
if ($conf->db['type'] === 'sqlite') {
return new FreshRSS_StatsDAOSQLite($username);
} else {
return new FreshRSS_StatsDAO($username);
}
}
public static function createDatabaseDAO($username = null) {
$conf = Minz_Configuration::get('system');
if ($conf->db['type'] === 'sqlite') {
return new FreshRSS_DatabaseDAOSQLite($username);
} else {
return new FreshRSS_DatabaseDAO($username);
}
}
}

View file

@ -1,490 +0,0 @@
<?php
class FreshRSS_Feed extends Minz_Model {
private $id = 0;
private $url;
private $category = 1;
private $nbEntries = -1;
private $nbNotRead = -1;
private $entries = null;
private $name = '';
private $website = '';
private $description = '';
private $lastUpdate = 0;
private $priority = 10;
private $pathEntries = '';
private $httpAuth = '';
private $error = false;
private $keep_history = -2;
private $ttl = -2;
private $hash = null;
private $lockPath = '';
private $hubUrl = '';
private $selfUrl = '';
public function __construct($url, $validate=true) {
if ($validate) {
$this->_url($url);
} else {
$this->url = $url;
}
}
public static function example() {
$f = new FreshRSS_Feed('http://example.net/', false);
$f->faviconPrepare();
return $f;
}
public function id() {
return $this->id;
}
public function hash() {
if ($this->hash === null) {
$salt = FreshRSS_Context::$system_conf->salt;
$this->hash = hash('crc32b', $salt . $this->url);
}
return $this->hash;
}
public function url() {
return $this->url;
}
public function selfUrl() {
return $this->selfUrl;
}
public function hubUrl() {
return $this->hubUrl;
}
public function category() {
return $this->category;
}
public function entries() {
return $this->entries === null ? array() : $this->entries;
}
public function name() {
return $this->name;
}
public function website() {
return $this->website;
}
public function description() {
return $this->description;
}
public function lastUpdate() {
return $this->lastUpdate;
}
public function priority() {
return $this->priority;
}
public function pathEntries() {
return $this->pathEntries;
}
public function httpAuth($raw = true) {
if ($raw) {
return $this->httpAuth;
} else {
$pos_colon = strpos($this->httpAuth, ':');
$user = substr($this->httpAuth, 0, $pos_colon);
$pass = substr($this->httpAuth, $pos_colon + 1);
return array(
'username' => $user,
'password' => $pass
);
}
}
public function inError() {
return $this->error;
}
public function keepHistory() {
return $this->keep_history;
}
public function ttl() {
return $this->ttl;
}
// public function ttlExpire() {
// $ttl = $this->ttl;
// if ($ttl == -2) { //Default
// $ttl = FreshRSS_Context::$user_conf->ttl_default;
// }
// if ($ttl == -1) { //Never
// $ttl = 64000000; //~2 years. Good enough for PubSubHubbub logic
// }
// return $this->lastUpdate + $ttl;
// }
public function nbEntries() {
if ($this->nbEntries < 0) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->nbEntries = $feedDAO->countEntries($this->id());
}
return $this->nbEntries;
}
public function nbNotRead() {
if ($this->nbNotRead < 0) {
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->nbNotRead = $feedDAO->countNotRead($this->id());
}
return $this->nbNotRead;
}
public function faviconPrepare() {
$file = DATA_PATH . '/favicons/' . $this->hash() . '.txt';
if (!file_exists($file)) {
$t = $this->website;
if ($t == '') {
$t = $this->url;
}
file_put_contents($file, $t);
}
}
public static function faviconDelete($hash) {
$path = DATA_PATH . '/favicons/' . $hash;
@unlink($path . '.ico');
@unlink($path . '.txt');
}
public function favicon() {
return Minz_Url::display('/f.php?' . $this->hash());
}
public function _id($value) {
$this->id = $value;
}
public function _url($value, $validate=true) {
$this->hash = null;
if ($validate) {
$value = checkUrl($value);
}
if (empty($value)) {
throw new FreshRSS_BadUrl_Exception($value);
}
$this->url = $value;
}
public function _category($value) {
$value = intval($value);
$this->category = $value >= 0 ? $value : 0;
}
public function _name($value) {
$this->name = $value === null ? '' : $value;
}
public function _website($value, $validate=true) {
if ($validate) {
$value = checkUrl($value);
}
if (empty($value)) {
$value = '';
}
$this->website = $value;
}
public function _description($value) {
$this->description = $value === null ? '' : $value;
}
public function _lastUpdate($value) {
$this->lastUpdate = $value;
}
public function _priority($value) {
$value = intval($value);
$this->priority = $value >= 0 ? $value : 10;
}
public function _pathEntries($value) {
$this->pathEntries = $value;
}
public function _httpAuth($value) {
$this->httpAuth = $value;
}
public function _error($value) {
$this->error = (bool)$value;
}
public function _keepHistory($value) {
$value = intval($value);
$value = min($value, 1000000);
$value = max($value, -2);
$this->keep_history = $value;
}
public function _ttl($value) {
$value = intval($value);
$value = min($value, 100000000);
$value = max($value, -2);
$this->ttl = $value;
}
public function _nbNotRead($value) {
$this->nbNotRead = intval($value);
}
public function _nbEntries($value) {
$this->nbEntries = intval($value);
}
public function load($loadDetails = false) {
if ($this->url !== null) {
if (CACHE_PATH === false) {
throw new Minz_FileNotExistException(
'CACHE_PATH',
Minz_Exception::ERROR
);
} else {
$url = htmlspecialchars_decode($this->url, ENT_QUOTES);
if ($this->httpAuth != '') {
$url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
}
$feed = customSimplePie();
if (substr($url, -11) === '#force_feed') {
$feed->force_feed(true);
$url = substr($url, 0, -11);
}
$feed->set_feed_url($url);
if (!$loadDetails) { //Only activates auto-discovery when adding a new feed
$feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE);
}
$mtime = $feed->init();
if ((!$mtime) || $feed->error()) {
$errorMessage = $feed->error();
throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']');
}
$links = $feed->get_links('self');
$this->selfUrl = isset($links[0]) ? $links[0] : null;
$links = $feed->get_links('hub');
$this->hubUrl = isset($links[0]) ? $links[0] : null;
if ($loadDetails) {
// si on a utilisé l'auto-discover, notre url va avoir changé
$subscribe_url = $feed->subscribe_url(false);
$title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '&lt;', '>' => '&gt;', '"' => '&quot;')); //HTML to HTML-PRE //ENT_COMPAT except &
$this->_name($title == '' ? $url : $title);
$this->_website(html_only_entity_decode($feed->get_link()));
$this->_description(html_only_entity_decode($feed->get_description()));
} else {
//The case of HTTP 301 Moved Permanently
$subscribe_url = $feed->subscribe_url(true);
}
$clean_url = SimplePie_Misc::url_remove_credentials($subscribe_url);
if ($subscribe_url !== null && $subscribe_url !== $url) {
$this->_url($clean_url);
}
if (($mtime === true) || ($mtime > $this->lastUpdate)) {
//Minz_Log::debug('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url);
$this->loadEntries($feed); // et on charge les articles du flux
} else {
//Minz_Log::debug('FreshRSS use cache for ' . $clean_url);
$this->entries = array();
}
$feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
unset($feed);
}
}
}
public function loadEntries($feed) {
$entries = array();
foreach ($feed->get_items() as $item) {
$title = html_only_entity_decode(strip_tags($item->get_title()));
$author = $item->get_author();
$link = $item->get_permalink();
$date = @strtotime($item->get_date());
// gestion des tags (catégorie == tag)
$tags_tmp = $item->get_categories();
$tags = array();
if ($tags_tmp !== null) {
foreach ($tags_tmp as $tag) {
$tags[] = html_only_entity_decode($tag->get_label());
}
}
$content = html_only_entity_decode($item->get_content());
$elinks = array();
foreach ($item->get_enclosures() as $enclosure) {
$elink = $enclosure->get_link();
if (empty($elinks[$elink])) {
$elinks[$elink] = '1';
$mime = strtolower($enclosure->get_type());
if (strpos($mime, 'image/') === 0) {
$content .= '<br /><img lazyload="" postpone="" src="' . $elink . '" alt="" />';
} elseif (strpos($mime, 'audio/') === 0) {
$content .= '<br /><audio lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
} elseif (strpos($mime, 'video/') === 0) {
$content .= '<br /><video lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />';
} else {
unset($elinks[$elink]);
}
}
}
$entry = new FreshRSS_Entry(
$this->id(),
$item->get_id(),
$title === null ? '' : $title,
$author === null ? '' : html_only_entity_decode($author->name),
$content === null ? '' : $content,
$link === null ? '' : $link,
$date ? $date : time()
);
$entry->_tags($tags);
// permet de récupérer le contenu des flux tronqués
$entry->loadCompleteContent($this->pathEntries());
$entries[] = $entry;
unset($item);
}
$this->entries = $entries;
}
function lock() {
$this->lockPath = TMP_PATH . '/' . $this->hash() . '.freshrss.lock';
if (file_exists($this->lockPath) && ((time() - @filemtime($this->lockPath)) > 3600)) {
@unlink($this->lockPath);
}
if (($handle = @fopen($this->lockPath, 'x')) === false) {
return false;
}
//register_shutdown_function('unlink', $this->lockPath);
@fclose($handle);
return true;
}
function unlock() {
@unlink($this->lockPath);
}
//<PubSubHubbub>
function pubSubHubbubEnabled() {
$url = $this->selfUrl ? $this->selfUrl : $this->url;
$hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json';
if ($hubFile = @file_get_contents($hubFilename)) {
$hubJson = json_decode($hubFile, true);
if ($hubJson && empty($hubJson['error']) &&
(empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) {
return true;
}
}
return false;
}
function pubSubHubbubError($error = true) {
$url = $this->selfUrl ? $this->selfUrl : $this->url;
$hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json';
$hubFile = @file_get_contents($hubFilename);
$hubJson = $hubFile ? json_decode($hubFile, true) : array();
if (!isset($hubJson['error']) || $hubJson['error'] !== (bool)$error) {
$hubJson['error'] = (bool)$error;
file_put_contents($hubFilename, json_encode($hubJson));
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t"
. 'Set error to ' . ($error ? 1 : 0) . ' for ' . $url . "\n", FILE_APPEND);
}
return false;
}
function pubSubHubbubPrepare() {
$key = '';
if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl && @is_dir(PSHB_PATH)) {
$path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl);
$hubFilename = $path . '/!hub.json';
if ($hubFile = @file_get_contents($hubFilename)) {
$hubJson = json_decode($hubFile, true);
if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
$text = 'Invalid JSON for PubSubHubbub: ' . $this->url;
Minz_Log::warning($text);
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
return false;
}
if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy
$text = 'PubSubHubbub lease ends at '
. date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end'])
. ' and needs renewal: ' . $this->url;
Minz_Log::warning($text);
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
$key = $hubJson['key']; //To renew our lease
} elseif (((!empty($hubJson['error'])) || empty($hubJson['lease_end'])) &&
(empty($hubJson['lease_start']) || $hubJson['lease_start'] < time() - (3600 * 23))) { //Do not renew too often
$key = $hubJson['key']; //To renew our lease
}
} else {
@mkdir($path, 0777, true);
$key = sha1($path . FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true));
$hubJson = array(
'hub' => $this->hubUrl,
'key' => $key,
);
file_put_contents($hubFilename, json_encode($hubJson));
@mkdir(PSHB_PATH . '/keys/');
file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl));
$text = 'PubSubHubbub prepared for ' . $this->url;
Minz_Log::debug($text);
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
}
$currentUser = Minz_Session::param('currentUser');
if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) {
touch($path . '/' . $currentUser . '.txt');
}
}
return $key;
}
//Parameter true to subscribe, false to unsubscribe.
function pubSubHubbubSubscribe($state) {
if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) {
$hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json';
$hubFile = @file_get_contents($hubFilename);
if ($hubFile === false) {
Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url);
return false;
}
$hubJson = json_decode($hubFile, true);
if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url);
return false;
}
$callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?k=' . $hubJson['key']);
if ($callbackUrl == '') {
Minz_Log::warning('Invalid callback for PubSubHubbub: ' . $this->url);
return false;
}
if (!$state) { //unsubscribe
$hubJson['lease_end'] = time() - 60;
file_put_contents($hubFilename, json_encode($hubJson));
}
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $this->hubUrl,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => _t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')',
CURLOPT_POSTFIELDS => 'hub.verify=sync'
. '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe')
. '&hub.topic=' . urlencode($this->selfUrl)
. '&hub.callback=' . urlencode($callbackUrl)
)
);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" .
'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl .
' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND);
if (substr($info['http_code'], 0, 1) == '2') {
return true;
} else {
$hubJson['lease_start'] = time(); //Prevent trying again too soon
$hubJson['error'] = true;
file_put_contents($hubFilename, json_encode($hubJson));
return false;
}
}
return false;
}
//</PubSubHubbub>
}

View file

@ -1,391 +0,0 @@
<?php
class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function addFeed($valuesTmp) {
$sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)';
$stm = $this->bd->prepare($sql);
$values = array(
substr($valuesTmp['url'], 0, 511),
$valuesTmp['category'],
substr($valuesTmp['name'], 0, 255),
substr($valuesTmp['website'], 0, 255),
substr($valuesTmp['description'], 0, 1023),
$valuesTmp['lastUpdate'],
base64_encode($valuesTmp['httpAuth']),
);
if ($stm && $stm->execute($values)) {
return $this->bd->lastInsertId();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addFeed: ' . $info[2]);
return false;
}
}
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) {
$set .= $key . '=?, ';
if ($key == 'httpAuth') {
$valuesTmp[$key] = base64_encode($v);
}
}
$set = substr($set, 0, -2);
$sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?';
$stm = $this->bd->prepare($sql);
foreach ($valuesTmp as $v) {
$values[] = $v;
}
$values[] = $id;
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateFeed: ' . $info[2]);
return false;
}
}
public function updateLastUpdate($id, $inError = 0, $updateCache = true) {
if ($updateCache) {
$sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
. 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
. 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),'
. 'lastUpdate=?, error=? '
. 'WHERE id=?';
} else {
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET lastUpdate=?, error=? '
. 'WHERE id=?';
}
$values = array(
time(),
$inError,
$id,
);
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateLastUpdate: ' . $info[2]);
return false;
}
}
public function changeCategory($idOldCat, $idNewCat) {
$catDAO = new FreshRSS_CategoryDAO();
$newCat = $catDAO->searchById($idNewCat);
if (!$newCat) {
$newCat = $catDAO->getDefault();
}
$sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?';
$stm = $this->bd->prepare($sql);
$values = array(
$newCat->id(),
$idOldCat
);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error changeCategory: ' . $info[2]);
return false;
}
}
public function deleteFeed($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error deleteFeed: ' . $info[2]);
return false;
}
}
public function deleteFeedByCategory($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
if ($stm && $stm->execute($values)) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]);
return false;
}
}
public function searchById($id) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$feed = self::daoToFeed($res);
if (isset($feed[$id])) {
return $feed[$id];
} else {
return null;
}
}
public function searchByUrl($url) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?';
$stm = $this->bd->prepare($sql);
$values = array($url);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
$feed = current(self::daoToFeed($res));
if (isset($feed) && $feed !== false) {
return $feed;
} else {
return null;
}
}
public function listFeeds() {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name';
$stm = $this->bd->prepare($sql);
$stm->execute();
return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
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($defaultCacheDuration = 3600) {
if ($defaultCacheDuration < 0) {
$defaultCacheDuration = 2147483647;
}
$sql = 'SELECT id, url, name, website, lastUpdate, pathEntries, httpAuth, keep_history, ttl '
. 'FROM `' . $this->prefix . 'feed` '
. 'WHERE ttl <> -1 AND lastUpdate < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) '
. 'ORDER BY lastUpdate';
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute())) {
$sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT -2'; //v0.7.3
$stm = $this->bd->prepare($sql2);
$stm->execute();
$stm = $this->bd->prepare($sql);
$stm->execute();
}
return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function listByCategory($cat) {
$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name';
$stm = $this->bd->prepare($sql);
$values = array($cat);
$stm->execute($values);
return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function countEntries($id) {
$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
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);
$values = array($id);
$stm->execute($values);
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0]['count'];
}
public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
$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()) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateCachedValues: ' . $info[2]);
return false;
}
}
public function truncate($id) {
$sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
$stm = $this->bd->prepare($sql);
$values = array($id);
$this->bd->beginTransaction();
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error truncate: ' . $info[2]);
$this->bd->rollBack();
return false;
}
$affected = $stm->rowCount();
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET cache_nbEntries=0, cache_nbUnreads=0 WHERE id=?';
$values = array($id);
$stm = $this->bd->prepare($sql);
if (!($stm && $stm->execute($values))) {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error truncate: ' . $info[2]);
$this->bd->rollBack();
return false;
}
$this->bd->commit();
return $affected;
}
public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) or updateCachedValues() just after
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
. 'WHERE id_feed=:id_feed AND id<=:id_max '
. 'AND is_favorite=0 ' //Do not remove favourites
. 'AND lastSeen < (SELECT maxLastSeen FROM (SELECT (MAX(e3.lastSeen)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance
. 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery'
$stm = $this->bd->prepare($sql);
if ($stm) {
$id_max = intval($date_min) . '000000';
$stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
$stm->bindParam(':id_max', $id_max, PDO::PARAM_STR);
$stm->bindParam(':keep', $keep, PDO::PARAM_INT);
}
if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error cleanOldEntries: ' . $info[2]);
return false;
}
}
public static function daoToFeed($listDAO, $catID = null) {
$list = array();
if (!is_array($listDAO)) {
$listDAO = array($listDAO);
}
foreach ($listDAO as $key => $dao) {
if (!isset($dao['name'])) {
continue;
}
if (isset($dao['id'])) {
$key = $dao['id'];
}
if ($catID === null) {
$category = isset($dao['category']) ? $dao['category'] : 0;
} else {
$category = $catID ;
}
$myFeed = new FreshRSS_Feed(isset($dao['url']) ? $dao['url'] : '', false);
$myFeed->_category($category);
$myFeed->_name($dao['name']);
$myFeed->_website(isset($dao['website']) ? $dao['website'] : '', false);
$myFeed->_description(isset($dao['description']) ? $dao['description'] : '');
$myFeed->_lastUpdate(isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0);
$myFeed->_priority(isset($dao['priority']) ? $dao['priority'] : 10);
$myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : '');
$myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : '');
$myFeed->_error(isset($dao['error']) ? $dao['error'] : 0);
$myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2);
$myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : -2);
$myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0);
$myFeed->_nbEntries(isset($dao['cache_nbEntries']) ? $dao['cache_nbEntries'] : 0);
if (isset($dao['id'])) {
$myFeed->_id($dao['id']);
}
$list[$key] = $myFeed;
}
return $list;
}
}

View file

@ -1,19 +0,0 @@
<?php
class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO {
public function updateCachedValues() { //For one single feed, call updateLastUpdate($id)
$sql = 'UPDATE `' . $this->prefix . 'feed` '
. 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),'
. 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)';
$stm = $this->bd->prepare($sql);
if ($stm && $stm->execute()) {
return $stm->rowCount();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error updateCachedValues: ' . $info[2]);
return false;
}
}
}

View file

@ -1,26 +0,0 @@
<?php
class FreshRSS_Log extends Minz_Model {
private $date;
private $level;
private $information;
public function date() {
return $this->date;
}
public function level() {
return $this->level;
}
public function info() {
return $this->information;
}
public function _date($date) {
$this->date = $date;
}
public function _level($level) {
$this->level = $level;
}
public function _info($information) {
$this->information = $information;
}
}

View file

@ -1,30 +0,0 @@
<?php
class FreshRSS_LogDAO {
public static function lines() {
$logs = array();
$handle = @fopen(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'), 'r');
if ($handle) {
while (($line = fgets($handle)) !== false) {
if (preg_match('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) {
$myLog = new FreshRSS_Log ();
$myLog->_date($matches[1]);
$myLog->_level($matches[2]);
$myLog->_info($matches[3]);
$logs[] = $myLog;
}
}
fclose($handle);
}
return array_reverse($logs);
}
public static function truncate() {
file_put_contents(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'), '');
if (FreshRSS_Auth::hasAccess('admin')) {
file_put_contents(join_path(DATA_PATH, 'users', '_', 'log.txt'), '');
file_put_contents(join_path(DATA_PATH, 'users', '_', 'log_api.txt'), '');
file_put_contents(join_path(DATA_PATH, 'users', '_', 'log_pshb.txt'), '');
}
}
}

View file

@ -1,229 +0,0 @@
<?php
require_once(LIB_PATH . '/lib_date.php');
/**
* Contains a search from the search form.
*
* It allows to extract meaningful bits of the search and store them in a
* convenient object
*/
class FreshRSS_Search {
// This contains the user input string
private $raw_input = '';
// The following properties are extracted from the raw input
private $intitle;
private $min_date;
private $max_date;
private $min_pubdate;
private $max_pubdate;
private $inurl;
private $author;
private $tags;
private $search;
public function __construct($input) {
if (strcmp($input, '') == 0) {
return;
}
$this->raw_input = $input;
$input = $this->parseIntitleSearch($input);
$input = $this->parseAuthorSearch($input);
$input = $this->parseInurlSearch($input);
$input = $this->parsePubdateSearch($input);
$input = $this->parseDateSearch($input);
$input = $this->parseTagsSeach($input);
$this->parseSearch($input);
}
public function __toString() {
return $this->getRawInput();
}
public function getRawInput() {
return $this->raw_input;
}
public function getIntitle() {
return $this->intitle;
}
public function getMinDate() {
return $this->min_date;
}
public function getMaxDate() {
return $this->max_date;
}
public function getMinPubdate() {
return $this->min_pubdate;
}
public function getMaxPubdate() {
return $this->max_pubdate;
}
public function getInurl() {
return $this->inurl;
}
public function getAuthor() {
return $this->author;
}
public function getTags() {
return $this->tags;
}
public function getSearch() {
return $this->search;
}
/**
* Parse the search string to find intitle keyword and the search related
* to it.
* The search is the first word following the keyword.
*
* @param string $input
* @return string
*/
private function parseIntitleSearch($input) {
if (preg_match('/intitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->intitle = $matches['search'];
return str_replace($matches[0], '', $input);
}
if (preg_match('/intitle:(?P<search>\w*)/', $input, $matches)) {
$this->intitle = $matches['search'];
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find author keyword and the search related
* to it.
* The search is the first word following the keyword except when using
* a delimiter. Supported delimiters are single quote (') and double
* quotes (").
*
* @param string $input
* @return string
*/
private function parseAuthorSearch($input) {
if (preg_match('/author:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->author = $matches['search'];
return str_replace($matches[0], '', $input);
}
if (preg_match('/author:(?P<search>\w*)/', $input, $matches)) {
$this->author = $matches['search'];
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find inurl keyword and the search related
* to it.
* The search is the first word following the keyword except.
*
* @param string $input
* @return string
*/
private function parseInurlSearch($input) {
if (preg_match('/inurl:(?P<search>[^\s]*)/', $input, $matches)) {
$this->inurl = $matches['search'];
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find date keyword and the search related
* to it.
* The search is the first word following the keyword.
*
* @param string $input
* @return string
*/
private function parseDateSearch($input) {
if (preg_match('/date:(?P<search>[^\s]*)/', $input, $matches)) {
list($this->min_date, $this->max_date) = parseDateInterval($matches['search']);
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find pubdate keyword and the search related
* to it.
* The search is the first word following the keyword.
*
* @param string $input
* @return string
*/
private function parsePubdateSearch($input) {
if (preg_match('/pubdate:(?P<search>[^\s]*)/', $input, $matches)) {
list($this->min_pubdate, $this->max_pubdate) = parseDateInterval($matches['search']);
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find tags keyword (# followed by a word)
* and the search related to it.
* The search is the first word following the #.
*
* @param string $input
* @return string
*/
private function parseTagsSeach($input) {
if (preg_match_all('/#(?P<search>[^\s]+)/', $input, $matches)) {
$this->tags = $matches['search'];
return str_replace($matches[0], '', $input);
}
return $input;
}
/**
* Parse the search string to find search values.
* Every word is a distinct search value, except when using a delimiter.
* Supported delimiters are single quote (') and double quotes (").
*
* @param string $input
* @return string
*/
private function parseSearch($input) {
$input = $this->cleanSearch($input);
if (strcmp($input, '') == 0) {
return;
}
if (preg_match_all('/(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) {
$this->search = $matches['search'];
$input = str_replace($matches[0], '', $input);
}
$input = $this->cleanSearch($input);
if (strcmp($input, '') == 0) {
return;
}
if (is_array($this->search)) {
$this->search = array_merge($this->search, explode(' ', $input));
} else {
$this->search = explode(' ', $input);
}
}
/**
* Remove all unnecessary spaces in the search
*
* @param string $input
* @return string
*/
private function cleanSearch($input) {
$input = preg_replace('/\s+/', ' ', $input);
return trim($input);
}
}

View file

@ -1,6 +0,0 @@
<?php
interface FreshRSS_Searchable {
public function searchById($id);
}

View file

@ -1,238 +0,0 @@
<?php
/**
* Manage the sharing options in FreshRSS.
*/
class FreshRSS_Share {
/**
* The list of available sharing options.
*/
private static $list_sharing = array();
/**
* Register a new sharing option.
* @param $share_options is an array defining the share option.
*/
public static function register($share_options) {
$type = $share_options['type'];
if (isset(self::$list_sharing[$type])) {
return;
}
$help_url = isset($share_options['help']) ? $share_options['help'] : '';
self::$list_sharing[$type] = new FreshRSS_Share(
$type, $share_options['url'], $share_options['transform'],
$share_options['form'], $help_url
);
}
/**
* Register sharing options in a file.
* @param $filename the name of the file to load.
*/
public static function load($filename) {
$shares_from_file = @include($filename);
if (!is_array($shares_from_file)) {
$shares_from_file = array();
}
foreach ($shares_from_file as $share_type => $share_options) {
$share_options['type'] = $share_type;
self::register($share_options);
}
}
/**
* Return the list of sharing options.
* @return an array of FreshRSS_Share objects.
*/
public static function enum() {
return self::$list_sharing;
}
/**
* Return FreshRSS_Share object related to the given type.
* @param $type the share type, null if $type is not registered.
*/
public static function get($type) {
if (!isset(self::$list_sharing[$type])) {
return null;
}
return self::$list_sharing[$type];
}
/**
*
*/
private $type = '';
private $name = '';
private $url_transform = '';
private $transform = array();
private $form_type = 'simple';
private $help_url = '';
private $custom_name = null;
private $base_url = null;
private $title = null;
private $link = null;
/**
* Create a FreshRSS_Share object.
* @param $type is a unique string defining the kind of share option.
* @param $url_transform defines the url format to use in order to share.
* @param $transform is an array of transformations to apply on link and title.
* @param $form_type defines which form we have to use to complete. "simple"
* is typically for a centralized service while "advanced" is for
* decentralized ones.
* @param $help_url is an optional url to give help on this option.
*/
private function __construct($type, $url_transform, $transform = array(),
$form_type, $help_url = '') {
$this->type = $type;
$this->name = _t('gen.share.' . $type);
$this->url_transform = $url_transform;
$this->help_url = $help_url;
if (!is_array($transform)) {
$transform = array();
}
$this->transform = $transform;
if (!in_array($form_type, array('simple', 'advanced'))) {
$form_type = 'simple';
}
$this->form_type = $form_type;
}
/**
* Update a FreshRSS_Share object with information from an array.
* @param $options is a list of informations to update where keys should be
* in this list: name, url, title, link.
*/
public function update($options) {
$available_options = array(
'name' => 'custom_name',
'url' => 'base_url',
'title' => 'title',
'link' => 'link',
);
foreach ($options as $key => $value) {
if (isset($available_options[$key])) {
$this->{$available_options[$key]} = $value;
}
}
}
/**
* Return the current type of the share option.
*/
public function type() {
return $this->type;
}
/**
* Return the current form type of the share option.
*/
public function formType() {
return $this->form_type;
}
/**
* Return the current help url of the share option.
*/
public function help() {
return $this->help_url;
}
/**
* Return the current name of the share option.
*/
public function name($real = false) {
if ($real || is_null($this->custom_name) || empty($this->custom_name)) {
return $this->name;
} else {
return $this->custom_name;
}
}
/**
* Return the current base url of the share option.
*/
public function baseUrl() {
return $this->base_url;
}
/**
* Return the current url by merging url_transform and base_url.
*/
public function url() {
$matches = array(
'~URL~',
'~TITLE~',
'~LINK~',
);
$replaces = array(
$this->base_url,
$this->title(),
$this->link(),
);
return str_replace($matches, $replaces, $this->url_transform);
}
/**
* Return the title.
* @param $raw true if we should get the title without transformations.
*/
public function title($raw = false) {
if ($raw) {
return $this->title;
}
return $this->transform($this->title, $this->getTransform('title'));
}
/**
* Return the link.
* @param $raw true if we should get the link without transformations.
*/
public function link($raw = false) {
if ($raw) {
return $this->link;
}
return $this->transform($this->link, $this->getTransform('link'));
}
/**
* Transform a data with the given functions.
* @param $data the data to transform.
* @param $tranform an array containing a list of functions to apply.
* @return the transformed data.
*/
private static function transform($data, $transform) {
if (!is_array($transform) || empty($transform)) {
return $data;
}
foreach ($transform as $action) {
$data = call_user_func($action, $data);
}
return $data;
}
/**
* Get the list of transformations for the given attribute.
* @param $attr the attribute of which we want the transformations.
* @return an array containing a list of transformations to apply.
*/
private function getTransform($attr) {
if (array_key_exists($attr, $this->transform)) {
return $this->transform[$attr];
}
return $this->transform;
}
}

View file

@ -1,427 +0,0 @@
<?php
class FreshRSS_StatsDAO extends Minz_ModelPdo {
const ENTRY_COUNT_PERIOD = 30;
/**
* Calculates entry repartition for all feeds and for main stream.
*
* @return array
*/
public function calculateEntryRepartition() {
return array(
'main_stream' => $this->calculateEntryRepartitionPerFeed(null, true),
'all_feeds' => $this->calculateEntryRepartitionPerFeed(null, false),
);
}
/**
* Calculates entry repartition for the selection.
* The repartition includes:
* - total entries
* - read entries
* - unread entries
* - favorite entries
*
* @param null|integer $feed feed id
* @param boolean $only_main
* @return array
*/
public function calculateEntryRepartitionPerFeed($feed = null, $only_main = false) {
$filter = '';
if ($only_main) {
$filter .= 'AND f.priority = 10';
}
if (!is_null($feed)) {
$filter .= "AND e.id_feed = {$feed}";
}
$sql = <<<SQL
SELECT COUNT(1) AS `total`,
COUNT(1) - SUM(e.is_read) AS `unread`,
SUM(e.is_read) AS `read`,
SUM(e.is_favorite) AS `favorite`
FROM {$this->prefix}entry AS e
, {$this->prefix}feed AS f
WHERE e.id_feed = f.id
{$filter}
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $res[0];
}
/**
* Calculates entry count per day on a 30 days period.
* Returns the result as a JSON object.
*
* @return JSON object
*/
public function calculateEntryCount() {
$count = $this->initEntryCountArray();
$period = self::ENTRY_COUNT_PERIOD;
// Get stats per day for the last 30 days
$sql = <<<SQL
SELECT DATEDIFF(FROM_UNIXTIME(e.date), NOW()) AS day,
COUNT(1) AS count
FROM {$this->prefix}entry AS e
WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -{$period} DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
GROUP BY day
ORDER BY day ASC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($res as $value) {
$count[$value['day']] = (int) $value['count'];
}
return $this->convertToSerie($count);
}
/**
* Calculates entry average per day on a 30 days period.
*
* @return integer
*/
public function calculateEntryAverage() {
$period = self::ENTRY_COUNT_PERIOD;
// Get stats per day for the last 30 days
$sql = <<<SQL
SELECT COUNT(1) / {$period} AS average
FROM {$this->prefix}entry AS e
WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -{$period} DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetch(PDO::FETCH_NAMED);
return round($res['average'], 2);
}
/**
* Initialize an array for the entry count.
*
* @return array
*/
protected function initEntryCountArray() {
return $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1);
}
/**
* Calculates the number of article per hour of the day per feed
*
* @param integer $feed id
* @return string
*/
public function calculateEntryRepartitionPerFeedPerHour($feed = null) {
return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed);
}
/**
* Calculates the number of article per day of week per feed
*
* @param integer $feed id
* @return string
*/
public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) {
return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed);
}
/**
* Calculates the number of article per month per feed
*
* @param integer $feed
* @return string
*/
public function calculateEntryRepartitionPerFeedPerMonth($feed = null) {
return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed);
}
/**
* Calculates the number of article per period per feed
*
* @param string $period format string to use for grouping
* @param integer $feed id
* @return string
*/
protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
$restrict = '';
if ($feed) {
$restrict = "WHERE e.id_feed = {$feed}";
}
$sql = <<<SQL
SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
, COUNT(1) AS count
FROM {$this->prefix}entry AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_NAMED);
foreach ($res as $value) {
$repartition[(int) $value['period']] = (int) $value['count'];
}
return $this->convertToSerie($repartition);
}
/**
* Calculates the average number of article per hour per feed
*
* @param integer $feed id
* @return integer
*/
public function calculateEntryAveragePerFeedPerHour($feed = null) {
return $this->calculateEntryAveragePerFeedPerPeriod(1 / 24, $feed);
}
/**
* Calculates the average number of article per day of week per feed
*
* @param integer $feed id
* @return integer
*/
public function calculateEntryAveragePerFeedPerDayOfWeek($feed = null) {
return $this->calculateEntryAveragePerFeedPerPeriod(7, $feed);
}
/**
* Calculates the average number of article per month per feed
*
* @param integer $feed id
* @return integer
*/
public function calculateEntryAveragePerFeedPerMonth($feed = null) {
return $this->calculateEntryAveragePerFeedPerPeriod(30, $feed);
}
/**
* Calculates the average number of article per feed
*
* @param float $period number used to divide the number of day in the period
* @param integer $feed id
* @return integer
*/
protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) {
$restrict = '';
if ($feed) {
$restrict = "WHERE e.id_feed = {$feed}";
}
$sql = <<<SQL
SELECT COUNT(1) AS count
, MIN(date) AS date_min
, MAX(date) AS date_max
FROM {$this->prefix}entry AS e
{$restrict}
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetch(PDO::FETCH_NAMED);
$date_min = new \DateTime();
$date_min->setTimestamp($res['date_min']);
$date_max = new \DateTime();
$date_max->setTimestamp($res['date_max']);
$interval = $date_max->diff($date_min, true);
$interval_in_days = $interval->format('%a');
if ($interval_in_days <= 0) {
// Surely only one article.
// We will return count / (period/period) == count.
$interval_in_days = $period;
}
return $res['count'] / ($interval_in_days / $period);
}
/**
* Initialize an array for statistics depending on a range
*
* @param integer $min
* @param integer $max
* @return array
*/
protected function initStatsArray($min, $max) {
return array_map(function () {
return 0;
}, array_flip(range($min, $max)));
}
/**
* Calculates feed count per category.
* Returns the result as a JSON object.
*
* @return JSON object
*/
public function calculateFeedByCategory() {
$sql = <<<SQL
SELECT c.name AS label
, COUNT(f.id) AS data
FROM {$this->prefix}category AS c,
{$this->prefix}feed AS f
WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $this->convertToPieSerie($res);
}
/**
* Calculates entry count per category.
* Returns the result as a JSON string.
*
* @return JSON object
*/
public function calculateEntryByCategory() {
$sql = <<<SQL
SELECT c.name AS label
, COUNT(e.id) AS data
FROM {$this->prefix}category AS c,
{$this->prefix}feed AS f,
{$this->prefix}entry AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY label
ORDER BY data DESC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
return $this->convertToPieSerie($res);
}
/**
* Calculates the 10 top feeds based on their number of entries
*
* @return array
*/
public function calculateTopFeed() {
$sql = <<<SQL
SELECT f.id AS id
, MAX(f.name) AS name
, MAX(c.name) AS category
, COUNT(e.id) AS count
FROM {$this->prefix}category AS c,
{$this->prefix}feed AS f,
{$this->prefix}entry AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY f.id
ORDER BY count DESC
LIMIT 10
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
return $stm->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Calculates the last publication date for each feed
*
* @return array
*/
public function calculateFeedLastDate() {
$sql = <<<SQL
SELECT MAX(f.id) as id
, MAX(f.name) AS name
, MAX(date) AS last_date
, COUNT(*) AS nb_articles
FROM {$this->prefix}feed AS f,
{$this->prefix}entry AS e
WHERE f.id = e.id_feed
GROUP BY f.id
ORDER BY name
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
return $stm->fetchAll(PDO::FETCH_ASSOC);
}
protected function convertToSerie($data) {
$serie = array();
foreach ($data as $key => $value) {
$serie[] = array($key, $value);
}
return $serie;
}
protected function convertToPieSerie($data) {
$serie = array();
foreach ($data as $value) {
$value['data'] = array(array(0, (int) $value['data']));
$serie[] = $value;
}
return $serie;
}
/**
* Gets days ready for graphs
*
* @return string
*/
public function getDays() {
return $this->convertToTranslatedJson(array(
'sun',
'mon',
'tue',
'wed',
'thu',
'fri',
'sat',
));
}
/**
* Gets months ready for graphs
*
* @return string
*/
public function getMonths() {
return $this->convertToTranslatedJson(array(
'jan',
'feb',
'mar',
'apr',
'may',
'jun',
'jul',
'aug',
'sep',
'oct',
'nov',
'dec',
));
}
/**
* Translates array content
*
* @param array $data
* @return JSON object
*/
private function convertToTranslatedJson($data = array()) {
$translated = array_map(function($a) {
return _t('gen.date.' . $a);
}, $data);
return $translated;
}
}

View file

@ -1,87 +0,0 @@
<?php
class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
/**
* Calculates entry count per day on a 30 days period.
* Returns the result as a JSON object.
*
* @return JSON object
*/
public function calculateEntryCount() {
$count = $this->initEntryCountArray();
$period = parent::ENTRY_COUNT_PERIOD;
// Get stats per day for the last 30 days
$sql = <<<SQL
SELECT round(julianday(e.date, 'unixepoch') - julianday('now')) AS day,
COUNT(1) AS count
FROM {$this->prefix}entry AS e
WHERE strftime('%Y%m%d', e.date, 'unixepoch')
BETWEEN strftime('%Y%m%d', 'now', '-{$period} days')
AND strftime('%Y%m%d', 'now', '-1 day')
GROUP BY day
ORDER BY day ASC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($res as $value) {
$count[(int) $value['day']] = (int) $value['count'];
}
return $this->convertToSerie($count);
}
/**
* Calculates entry average per day on a 30 days period.
*
* @return integer
*/
public function calculateEntryAverage() {
$period = self::ENTRY_COUNT_PERIOD;
// Get stats per day for the last 30 days
$sql = <<<SQL
SELECT COUNT(1) / {$period} AS average
FROM {$this->prefix}entry AS e
WHERE strftime('%Y%m%d', e.date, 'unixepoch')
BETWEEN strftime('%Y%m%d', 'now', '-{$period} days')
AND strftime('%Y%m%d', 'now', '-1 day')
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetch(PDO::FETCH_NAMED);
return round($res['average'], 2);
}
protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
if ($feed) {
$restrict = "WHERE e.id_feed = {$feed}";
} else {
$restrict = '';
}
$sql = <<<SQL
SELECT strftime('{$period}', e.date, 'unixepoch') AS period
, COUNT(1) AS count
FROM {$this->prefix}entry AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
SQL;
$stm = $this->bd->prepare($sql);
$stm->execute();
$res = $stm->fetchAll(PDO::FETCH_NAMED);
$repartition = array();
foreach ($res as $value) {
$repartition[(int) $value['period']] = (int) $value['count'];
}
return $this->convertToSerie($repartition);
}
}

View file

@ -1,118 +0,0 @@
<?php
class FreshRSS_Themes extends Minz_Model {
private static $themesUrl = '/themes/';
private static $defaultIconsUrl = '/themes/icons/';
public static $defaultTheme = 'Origine';
public static function getList() {
return array_values(array_diff(
scandir(PUBLIC_PATH . self::$themesUrl),
array('..', '.')
));
}
public static function get() {
$themes_list = self::getList();
$list = array();
foreach ($themes_list as $theme_dir) {
$theme = self::get_infos($theme_dir);
if ($theme) {
$list[$theme_dir] = $theme;
}
}
return $list;
}
public static function get_infos($theme_id) {
$theme_dir = PUBLIC_PATH . self::$themesUrl . $theme_id ;
if (is_dir($theme_dir)) {
$json_filename = $theme_dir . '/metadata.json';
if (file_exists($json_filename)) {
$content = file_get_contents($json_filename);
$res = json_decode($content, true);
if ($res &&
!empty($res['name']) &&
isset($res['files']) &&
is_array($res['files'])) {
$res['id'] = $theme_id;
return $res;
}
}
}
return false;
}
private static $themeIconsUrl;
private static $themeIcons;
public static function load($theme_id) {
$infos = self::get_infos($theme_id);
if (!$infos) {
if ($theme_id !== self::$defaultTheme) { //Fall-back to default theme
return self::load(self::$defaultTheme);
}
$themes_list = self::getList();
if (!empty($themes_list)) {
if ($theme_id !== $themes_list[0]) { //Fall-back to first theme
return self::load($themes_list[0]);
}
}
return false;
}
self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/';
self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff(
scandir(PUBLIC_PATH . self::$themeIconsUrl),
array('..', '.')
), 1) : array();
return $infos;
}
public static function icon($name, $urlOnly = false) {
static $alts = array(
'add' => '✚',
'all' => '☰',
'bookmark' => '★',
'bookmark-add' => '✚',
'category' => '☷',
'category-white' => '☷',
'close' => '❌',
'configure' => '⚙',
'down' => '▽',
'favorite' => '★',
'help' => 'ⓘ',
'icon' => '⊚',
'import' => '⤓',
'key' => '⚿',
'link' => '↗',
'login' => '🔒',
'logout' => '🔓',
'next' => '⏩',
'non-starred' => '☆',
'prev' => '⏪',
'read' => '☑',
'rss' => '☄',
'unread' => '☐',
'refresh' => '🔃', //↻
'search' => '🔍',
'share' => '♺',
'starred' => '★',
'stats' => '%',
'tag' => '⚐',
'up' => '△',
'view-normal' => '☰',
'view-global' => '☷',
'view-reader' => '☕',
);
if (!isset($alts[$name])) {
return '';
}
$url = $name . '.svg';
$url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) :
(self::$defaultIconsUrl . $url);
return $urlOnly ? Minz_Url::display($url) :
'<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />';
}
}

View file

@ -1,68 +0,0 @@
<?php
class FreshRSS_UserDAO extends Minz_ModelPdo {
public function createUser($username) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
$userPDO = new Minz_ModelPdo($username);
$ok = false;
if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL
$sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', _t('gen.short.default_category'));
$stm = $userPDO->bd->prepare($sql);
$ok = $stm && $stm->execute();
} else { //E.g. SQLite
global $SQL_CREATE_TABLES;
if (is_array($SQL_CREATE_TABLES)) {
$ok = true;
foreach ($SQL_CREATE_TABLES as $instruction) {
$sql = sprintf($instruction, '', _t('gen.short.default_category'));
$stm = $userPDO->bd->prepare($sql);
$ok &= ($stm && $stm->execute());
}
}
}
if ($ok) {
return true;
} else {
$info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error : ' . $info[2]);
return false;
}
}
public function deleteUser($username) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
if ($db['type'] === 'sqlite') {
return unlink(join_path(DATA_PATH, 'users', $username, 'db.sqlite'));
} else {
$userPDO = new Minz_ModelPdo($username);
$sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
$stm = $userPDO->bd->prepare($sql);
if ($stm && $stm->execute()) {
return true;
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error : ' . $info[2]);
return false;
}
}
}
public static function exist($username) {
return is_dir(join_path(DATA_PATH , 'users', $username));
}
public static function touch($username) {
return touch(join_path(DATA_PATH , 'users', $username, 'config.php'));
}
public static function mtime($username) {
return @filemtime(join_path(DATA_PATH , 'users', $username, 'config.php'));
}
}

View file

@ -1,226 +0,0 @@
<?php
/**
* Contains the description of a user query
*
* It allows to extract the meaningful bits of the query to be manipulated in an
* easy way.
*/
class FreshRSS_UserQuery {
private $deprecated = false;
private $get;
private $get_name;
private $get_type;
private $name;
private $order;
private $search;
private $state;
private $url;
private $feed_dao;
private $category_dao;
/**
* @param array $query
* @param FreshRSS_Searchable $feed_dao
* @param FreshRSS_Searchable $category_dao
*/
public function __construct($query, FreshRSS_Searchable $feed_dao = null, FreshRSS_Searchable $category_dao = null) {
$this->category_dao = $category_dao;
$this->feed_dao = $feed_dao;
if (isset($query['get'])) {
$this->parseGet($query['get']);
}
if (isset($query['name'])) {
$this->name = $query['name'];
}
if (isset($query['order'])) {
$this->order = $query['order'];
}
if (!isset($query['search'])) {
$query['search'] = '';
}
// linked to deeply with the search object, need to use dependency injection
$this->search = new FreshRSS_Search($query['search']);
if (isset($query['state'])) {
$this->state = $query['state'];
}
if (isset($query['url'])) {
$this->url = $query['url'];
}
}
/**
* Convert the current object to an array.
*
* @return array
*/
public function toArray() {
return array_filter(array(
'get' => $this->get,
'name' => $this->name,
'order' => $this->order,
'search' => $this->search->__toString(),
'state' => $this->state,
'url' => $this->url,
));
}
/**
* Parse the get parameter in the query string to extract its name and
* type
*
* @param string $get
*/
private function parseGet($get) {
$this->get = $get;
if (preg_match('/(?P<type>[acfs])(_(?P<id>\d+))?/', $get, $matches)) {
switch ($matches['type']) {
case 'a':
$this->parseAll();
break;
case 'c':
$this->parseCategory($matches['id']);
break;
case 'f':
$this->parseFeed($matches['id']);
break;
case 's':
$this->parseFavorite();
break;
}
}
}
/**
* Parse the query string when it is an "all" query
*/
private function parseAll() {
$this->get_name = 'all';
$this->get_type = 'all';
}
/**
* Parse the query string when it is a "category" query
*
* @param integer $id
* @throws FreshRSS_DAO_Exception
*/
private function parseCategory($id) {
if (is_null($this->category_dao)) {
throw new FreshRSS_DAO_Exception('Category DAO is not loaded in UserQuery');
}
$category = $this->category_dao->searchById($id);
if ($category) {
$this->get_name = $category->name();
} else {
$this->deprecated = true;
}
$this->get_type = 'category';
}
/**
* Parse the query string when it is a "feed" query
*
* @param integer $id
* @throws FreshRSS_DAO_Exception
*/
private function parseFeed($id) {
if (is_null($this->feed_dao)) {
throw new FreshRSS_DAO_Exception('Feed DAO is not loaded in UserQuery');
}
$feed = $this->feed_dao->searchById($id);
if ($feed) {
$this->get_name = $feed->name();
} else {
$this->deprecated = true;
}
$this->get_type = 'feed';
}
/**
* Parse the query string when it is a "favorite" query
*/
private function parseFavorite() {
$this->get_name = 'favorite';
$this->get_type = 'favorite';
}
/**
* Check if the current user query is deprecated.
* It is deprecated if the category or the feed used in the query are
* not existing.
*
* @return boolean
*/
public function isDeprecated() {
return $this->deprecated;
}
/**
* Check if the user query has parameters.
* If the type is 'all', it is considered equal to no parameters
*
* @return boolean
*/
public function hasParameters() {
if ($this->get_type === 'all') {
return false;
}
if ($this->hasSearch()) {
return true;
}
if ($this->state) {
return true;
}
if ($this->order) {
return true;
}
if ($this->get) {
return true;
}
return false;
}
/**
* Check if there is a search in the search object
*
* @return boolean
*/
public function hasSearch() {
return $this->search->getRawInput() != "";
}
public function getGet() {
return $this->get;
}
public function getGetName() {
return $this->get_name;
}
public function getGetType() {
return $this->get_type;
}
public function getName() {
return $this->name;
}
public function getOrder() {
return $this->order;
}
public function getSearch() {
return $this->search;
}
public function getState() {
return $this->state;
}
public function getUrl() {
return $this->url;
}
}

View file

@ -1,64 +0,0 @@
<?php
define('SQL_CREATE_TABLES', '
CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY (`name`) -- v0.7
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
ENGINE = INNODB;
CREATE TABLE IF NOT EXISTS `%1$sfeed` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
`url` varchar(511) CHARACTER SET latin1 NOT NULL,
`category` SMALLINT DEFAULT 0, -- v0.7
`name` varchar(255) NOT NULL,
`website` varchar(255) CHARACTER SET latin1,
`description` text,
`lastUpdate` int(11) DEFAULT 0, -- Until year 2038
`priority` tinyint(2) NOT NULL DEFAULT 10,
`pathEntries` varchar(511) DEFAULT NULL,
`httpAuth` varchar(511) DEFAULT NULL,
`error` boolean DEFAULT 0,
`keep_history` MEDIUMINT NOT NULL DEFAULT -2, -- v0.7
`ttl` INT NOT NULL DEFAULT -2, -- v0.7.3
`cache_nbEntries` int DEFAULT 0, -- v0.7
`cache_nbUnreads` int DEFAULT 0, -- v0.7
PRIMARY KEY (`id`),
FOREIGN KEY (`category`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE KEY (`url`), -- v0.7
INDEX (`name`), -- v0.7
INDEX (`priority`), -- v0.7
INDEX (`keep_history`) -- v0.7
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
ENGINE = INNODB;
CREATE TABLE IF NOT EXISTS `%1$sentry` (
`id` bigint NOT NULL, -- v0.7
`guid` varchar(760) CHARACTER SET latin1 NOT NULL, -- Maximum for UNIQUE is 767B
`title` varchar(255) NOT NULL,
`author` varchar(255),
`content_bin` blob, -- v0.7
`link` varchar(1023) CHARACTER SET latin1 NOT NULL,
`date` int(11), -- Until year 2038
`lastSeen` INT(11) DEFAULT 0, -- v1.1.1, Until year 2038
`hash` BINARY(16), -- v1.1.1
`is_read` boolean NOT NULL DEFAULT 0,
`is_favorite` boolean NOT NULL DEFAULT 0,
`id_feed` SMALLINT, -- v0.7
`tags` varchar(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE KEY (`id_feed`,`guid`), -- v0.7
INDEX (`is_favorite`), -- v0.7
INDEX (`is_read`), -- v0.7
INDEX `entry_lastSeen_index` (`lastSeen`) -- v1.1.1
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
ENGINE = INNODB;
INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS @ GitHub", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);
');
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');

View file

@ -1,62 +0,0 @@
<?php
global $SQL_CREATE_TABLES;
$SQL_CREATE_TABLES = array(
'CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` varchar(255) NOT NULL,
UNIQUE (`name`)
);',
'CREATE TABLE IF NOT EXISTS `%1$sfeed` (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`url` varchar(511) NOT NULL,
`%1$scategory` SMALLINT DEFAULT 0,
`name` varchar(255) NOT NULL,
`website` varchar(255),
`description` text,
`lastUpdate` int(11) DEFAULT 0, -- Until year 2038
`priority` tinyint(2) NOT NULL DEFAULT 10,
`pathEntries` varchar(511) DEFAULT NULL,
`httpAuth` varchar(511) DEFAULT NULL,
`error` boolean DEFAULT 0,
`keep_history` MEDIUMINT NOT NULL DEFAULT -2,
`ttl` INT NOT NULL DEFAULT -2,
`cache_nbEntries` int DEFAULT 0,
`cache_nbUnreads` int DEFAULT 0,
FOREIGN KEY (`%1$scategory`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
UNIQUE (`url`)
);',
'CREATE INDEX IF NOT EXISTS feed_name_index ON `%1$sfeed`(`name`);',
'CREATE INDEX IF NOT EXISTS feed_priority_index ON `%1$sfeed`(`priority`);',
'CREATE INDEX IF NOT EXISTS feed_keep_history_index ON `%1$sfeed`(`keep_history`);',
'CREATE TABLE IF NOT EXISTS `%1$sentry` (
`id` bigint NOT NULL,
`guid` varchar(760) NOT NULL,
`title` varchar(255) NOT NULL,
`author` varchar(255),
`content` text,
`link` varchar(1023) NOT NULL,
`date` int(11), -- Until year 2038
`lastSeen` INT(11) DEFAULT 0, -- v1.1.1, Until year 2038
`hash` BINARY(16), -- v1.1.1
`is_read` boolean NOT NULL DEFAULT 0,
`is_favorite` boolean NOT NULL DEFAULT 0,
`id_feed` SMALLINT,
`tags` varchar(1023),
PRIMARY KEY (`id`),
FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
UNIQUE (`id_feed`,`guid`)
);',
'CREATE INDEX IF NOT EXISTS entry_is_favorite_index ON `%1$sentry`(`is_favorite`);',
'CREATE INDEX IF NOT EXISTS entry_is_read_index ON `%1$sentry`(`is_read`);',
'CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `%1$sentry`(`lastSeen`);', //v1.1.1
'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
);
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');

View file

@ -1,86 +0,0 @@
<?php
require(dirname(__FILE__) . '/../constants.php');
require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
session_cache_limiter('');
ob_implicit_flush(false);
ob_start();
echo 'Results: ', "\n"; //Buffered
if (defined('STDOUT')) {
$begin_date = date_create('now');
fwrite(STDOUT, 'Starting feed actualization at ' . $begin_date->format('c') . "\n"); //Unbuffered
}
// Set the header params ($_GET) to call the FRSS application.
$_GET['c'] = 'feed';
$_GET['a'] = 'actualize';
$_GET['ajax'] = 1;
$_GET['force'] = true;
$_SERVER['HTTP_HOST'] = '';
$log_file = join_path(USERS_PATH, '_', 'log.txt');
$app = new FreshRSS();
$system_conf = Minz_Configuration::get('system');
$system_conf->auth_type = 'none'; // avoid necessity to be logged in (not saved!)
// Create the list of users to actualize.
// Users are processed in a random order but always start with admin
$users = listUsers();
shuffle($users);
if ($system_conf->default_user !== ''){
array_unshift($users, $system_conf->default_user);
$users = array_unique($users);
}
$limits = $system_conf->limits;
$min_last_activity = time() - $limits['max_inactivity'];
foreach ($users as $user) {
if (($user !== $system_conf->default_user) &&
(FreshRSS_UserDAO::mtime($user) < $min_last_activity)) {
Minz_Log::notice('FreshRSS skip inactive user ' . $user, $log_file);
if (defined('STDOUT')) {
fwrite(STDOUT, 'FreshRSS skip inactive user ' . $user . "\n"); //Unbuffered
}
continue;
}
Minz_Log::notice('FreshRSS actualize ' . $user, $log_file);
if (defined('STDOUT')) {
fwrite(STDOUT, 'Actualize ' . $user . "...\n"); //Unbuffered
}
echo $user, ' '; //Buffered
Minz_Session::_param('currentUser', $user);
new Minz_ModelPdo($user); //TODO: FIXME: Quick-fix while waiting for a better FreshRSS() constructor/init
FreshRSS_Auth::giveAccess();
$app->init();
$app->run();
if (!invalidateHttpCache()) {
Minz_Log::notice('FreshRSS write access problem in ' . join_path(USERS_PATH, $user, 'log.txt'),
$log_file);
if (defined('STDERR')) {
fwrite(STDERR, 'Write access problem in ' . join_path(USERS_PATH, $user, 'log.txt') . "\n");
}
}
}
Minz_Log::notice('FreshRSS actualize done.', $log_file);
if (defined('STDOUT')) {
fwrite(STDOUT, 'Done.' . "\n");
$end_date = date_create('now');
$duration = date_diff($end_date, $begin_date);
fwrite(STDOUT, 'Ending feed actualization at ' . $end_date->format('c') . "\n"); //Unbuffered
fwrite(STDOUT, 'Feed actualizations took ' . $duration->format('%a day(s), %h hour(s), %i minute(s) and %s seconds') . ' for ' . count($users) . " users\n"); //Unbuffered
}
echo 'End.', "\n";
ob_end_flush();

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Umožnit anonymně číst články výchozího uživatele (%s)',
'allow_anonymous_refresh' => 'Umožnit anonymní obnovení článků',
'api_enabled' => 'Povolit přístup k <abbr>API</abbr> <small>(vyžadováno mobilními aplikacemi)</small>',
'form' => 'Webový formulář (tradiční, vyžaduje JavaScript)',
'http' => 'HTTP (pro pokročilé uživatele s HTTPS)',
'none' => 'Žádný (nebezpečné)',
'persona' => 'Mozilla Persona (moderní, vyžaduje JavaScript)',
'title' => 'Přihlášení',
'title_reset' => 'Reset přihlášení',
'token' => 'Authentizační token',
'token_help' => 'Umožňuje přístup k RSS kanálu článků výchozího uživatele bez přihlášení:',
'type' => 'Způsob přihlášení',
'unsafe_autologin' => 'Povolit nebezpečné automatické přihlášení přes: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/cache</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře cache jsou v pořádku.',
),
'categories' => array(
'nok' => 'Tabulka kategorií je nastavena špatně.',
'ok' => 'Tabulka kategorií je v pořádku.',
),
'connection' => array(
'nok' => 'Nelze navázat spojení s databází.',
'ok' => 'Připojení k databázi je v pořádku.',
),
'ctype' => array(
'nok' => 'Nemáte požadovanou knihovnu pro ověřování znaků (php-ctype).',
'ok' => 'Máte požadovanou knihovnu pro ověřování znaků (ctype).',
),
'curl' => array(
'nok' => 'Nemáte cURL (balíček php5-curl).',
'ok' => 'Máte rozšíření cURL.',
),
'data' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře data jsou v pořádku.',
),
'database' => 'Instalace databáze',
'dom' => array(
'nok' => 'Nemáte požadovanou knihovnu pro procházení DOM (balíček php-xml).',
'ok' => 'Máte požadovanou knihovnu pro procházení DOM.',
),
'entries' => array(
'nok' => 'Tabulka článků je nastavena špatně.',
'ok' => 'Tabulka kategorií je v pořádku.',
),
'favicons' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
),
'feeds' => array(
'nok' => 'Tabulka kanálů je nastavena špatně.',
'ok' => 'Tabulka kanálů je v pořádku.',
),
'files' => 'Instalace souborů',
'json' => array(
'nok' => 'Nemáte JSON (balíček php5-json).',
'ok' => 'Máte rozšíření JSON.',
),
'minz' => array(
'nok' => 'Nemáte framework Minz.',
'ok' => 'Máte framework Minz.',
),
'pcre' => array(
'nok' => 'Nemáte požadovanou knihovnu pro regulární výrazy (php-pcre).',
'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
),
'pdo' => array(
'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/persona</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře Mozilla Persona jsou v pořádku.',
),
'php' => array(
'_' => 'PHP instalace',
'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
'ok' => 'Vaše verze PHP je %s a je kompatibilní s FreshRSS.',
),
'tables' => array(
'nok' => 'V databázi chybí jedna nevo více tabulek.',
'ok' => 'V databázi jsou všechny tabulky.',
),
'title' => 'Kontrola instalace',
'tokens' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/tokens</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře tokens jsou v pořádku.',
),
'users' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/users</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře users jsou v pořádku.',
),
'zip' => array(
'nok' => 'Nemáte rozšíření ZIP (balíček php5-zip).',
'ok' => 'Máte rozšíření ZIP.',
),
),
'extensions' => array(
'disabled' => 'Vypnuto',
'empty_list' => 'Není naistalováno žádné rozšíření',
'enabled' => 'Zapnuto',
'no_configure_view' => 'Toto rozšíření nemá žádné možnosti nastavení.',
'system' => array(
'_' => 'Systémová rozšíření',
'no_rights' => 'Systémová rozšíření (na ně nemáte oprávnění)',
),
'title' => 'Rozšíření',
'user' => 'Uživatelská rozšíření',
),
'stats' => array(
'_' => 'Statistika',
'all_feeds' => 'Všechny kanály',
'category' => 'Kategorie',
'entry_count' => 'Počet článků',
'entry_per_category' => 'Článků na kategorii',
'entry_per_day' => 'Článků za den (posledních 30 dní)',
'entry_per_day_of_week' => 'Za den v týdnu (průměr: %.2f zprávy)',
'entry_per_hour' => 'Za hodinu (průměr: %.2f zprávy)',
'entry_per_month' => 'Za měsíc (průměr: %.2f zprávy)',
'entry_repartition' => 'Rozdělení článků',
'feed' => 'Kanál',
'feed_per_category' => 'Článků na kategorii',
'idle' => 'Neaktivní kanály',
'main' => 'Přehled',
'main_stream' => 'Všechny kanály',
'menu' => array(
'idle' => 'Neaktivní kanály',
'main' => 'Přehled',
'repartition' => 'Rozdělení článků',
),
'no_idle' => 'Žádné neaktivní kanály!',
'number_entries' => '%d článků',
'percent_of_total' => '%% ze všech',
'repartition' => 'Rozdělení článků',
'status_favorites' => 'Oblíbené',
'status_read' => 'Přečtené',
'status_total' => 'Celkem',
'status_unread' => 'Nepřečtené',
'title' => 'Statistika',
'top_feed' => 'Top ten kanálů',
),
'system' => array(
'_' => 'System configuration', // @todo translate
'auto-update-url' => 'Auto-update server URL', // @todo translate
'instance-name' => 'Instance name', // @todo translate
'max-categories' => 'Categories per user limit', // @todo translate
'max-feeds' => 'Feeds per user limit', // @todo translate
'registration' => array(
'help' => '0 znamená žádná omezení účtu',
'number' => 'Maximální počet účtů',
),
),
'update' => array(
'_' => 'Aktualizace systému',
'apply' => 'Použít',
'check' => 'Zkontrolovat aktualizace',
'current_version' => 'Vaše instalace FreshRSS je verze %s.',
'last' => 'Poslední kontrola: %s',
'none' => 'Žádné nové aktualizace',
'title' => 'Aktualizovat systém',
),
'user' => array(
'articles_and_size' => '%s článků (%s)',
'create' => 'Vytvořit nového uživatele',
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Jazyk',
'number' => 'Zatím je vytvořen %d účet',
'numbers' => 'Zatím je vytvořeno %d účtů',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
'password_format' => 'Alespoň 7 znaků',
'title' => 'Správa uživatelů',
'user_list' => 'Seznam uživatelů',
'username' => 'Přihlašovací jméno',
'users' => 'Uživatelé',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Archivace',
'advanced' => 'Pokročilé',
'delete_after' => 'Smazat články starší než',
'help' => 'Více možností je dostupných v nastavení jednotlivých kanálů',
'keep_history_by_feed' => 'Zachovat tento minimální počet článků v každém kanálu',
'optimize' => 'Optimalizovat databázi',
'optimize_help' => 'Občasná údržba zmenší velikost databáze',
'purge_now' => 'Vyčistit nyní',
'title' => 'Archivace',
'ttl' => 'Neaktualizovat častěji než',
),
'display' => array(
'_' => 'Zobrazení',
'icon' => array(
'bottom_line' => 'Spodní řádek',
'entry' => 'Ikony článků',
'publication_date' => 'Datum vydání',
'related_tags' => 'Související tagy',
'sharing' => 'Sdílení',
'top_line' => 'Horní řádek',
),
'language' => 'Jazyk',
'notif_html5' => array(
'seconds' => 'sekund (0 znamená žádný timeout)',
'timeout' => 'Timeout HTML5 notifikací',
),
'theme' => 'Vzhled',
'title' => 'Zobrazení',
'width' => array(
'content' => 'Šířka obsahu',
'large' => 'Velká',
'medium' => 'Střední',
'no_limit' => 'Bez limitu',
'thin' => 'Tenká',
),
),
'query' => array(
'_' => 'Uživatelské dotazy',
'deprecated' => 'Tento dotaz již není platný. Odkazovaná kategorie nebo kanál byly smazány.',
'filter' => 'Filtr aplikován:',
'get_all' => 'Zobrazit všechny články',
'get_category' => 'Zobrazit "%s" kategorii',
'get_favorite' => 'Zobrazit oblíbené články',
'get_feed' => 'Zobrazit "%s" článkek',
'no_filter' => 'Zrušit filtr',
'none' => 'Ještě jste nevytvořil žádný uživatelský dotaz.',
'number' => 'Dotaz n°%d',
'order_asc' => 'Zobrazit nejdříve nejstarší články',
'order_desc' => 'Zobrazit nejdříve nejnovější články',
'search' => 'Hledat "%s"',
'state_0' => 'Zobrazit všechny články',
'state_1' => 'Zobrazit přečtené články',
'state_2' => 'Zobrazit nepřečtené články',
'state_3' => 'Zobrazit všechny články',
'state_4' => 'Zobrazit oblíbené články',
'state_5' => 'Zobrazit oblíbené přečtené články',
'state_6' => 'Zobrazit oblíbené nepřečtené články',
'state_7' => 'Zobrazit oblíbené články',
'state_8' => 'Zobrazit všechny články vyjma oblíbených',
'state_9' => 'Zobrazit všechny přečtené články vyjma oblíbených',
'state_10' => 'Zobrazit všechny nepřečtené články vyjma oblíbených',
'state_11' => 'Zobrazit všechny články vyjma oblíbených',
'state_12' => 'Zobrazit všechny články',
'state_13' => 'Zobrazit přečtené články',
'state_14' => 'Zobrazit nepřečtené články',
'state_15' => 'Zobrazit všechny články',
'title' => 'Uživatelské dotazy',
),
'profile' => array(
'_' => 'Správa profilu',
'delete' => array(
'_' => 'Smazání účtu',
'warn' => 'Váš účet bude smazán spolu se všemi souvisejícími daty',
),
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(tzn. pro mobilní aplikace)</small>',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
'password_format' => 'Alespoň 7 znaků',
'title' => 'Profil',
),
'reading' => array(
'_' => 'Čtení',
'after_onread' => 'Po “označit vše jako přečtené”,',
'articles_per_page' => 'Počet článků na stranu',
'auto_load_more' => 'Načítat další články dole na stránce',
'auto_remove_article' => 'Po přečtení články schovat',
'mark_updated_article_unread' => 'Označte aktualizované položky jako nepřečtené',
'confirm_enabled' => 'Vyžadovat potvrzení pro akci “označit vše jako přečtené”',
'display_articles_unfolded' => 'Ve výchozím stavu zobrazovat články otevřené',
'display_categories_unfolded' => 'Ve výchozím stavu zobrazovat kategorie zavřené',
'hide_read_feeds' => 'Schovat kategorie a kanály s nulovým počtem nepřečtených článků (nefunguje s nastavením “Zobrazit všechny články”)',
'img_with_lazyload' => 'Použít "lazy load" mód pro načítaní obrázků',
'jump_next' => 'skočit na další nepřečtený (kanál nebo kategorii)',
'number_divided_when_reader' => 'V režimu “Čtení” děleno dvěma.',
'read' => array(
'article_open_on_website' => 'když je otevřen původní web s článkem',
'article_viewed' => 'během čtení článku',
'scroll' => 'během skrolování',
'upon_reception' => 'po načtení článku',
'when' => 'Označit článek jako přečtený…',
),
'show' => array(
'_' => 'Počet zobrazených článků',
'adaptive' => 'Vyberte zobrazení',
'all_articles' => 'Zobrazit všechny články',
'unread' => 'Zobrazit jen nepřečtené',
),
'sort' => array(
'_' => 'Řazení',
'newer_first' => 'Nejdříve nejnovější',
'older_first' => 'Nejdříve nejstarší',
),
'sticky_post' => 'Při otevření posunout článek nahoru',
'title' => 'Čtení',
'view' => array(
'default' => 'Výchozí',
'global' => 'Přehled',
'normal' => 'Normální',
'reader' => 'Čtení',
),
),
'sharing' => array(
'_' => 'Sdílení',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'Více informací',
'print' => 'Tisk',
'shaarli' => 'Shaarli',
'share_name' => 'Jméno pro zobrazení',
'share_url' => 'Jakou URL použít pro sdílení',
'title' => 'Sdílení',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Zkratky',
'article_action' => 'Články - akce',
'auto_share' => 'Sdílet',
'auto_share_help' => 'Je-li nastavena pouze jedna možnost sdílení, bude použita. Další možnosti jsou dostupné pomocí jejich čísla.',
'close_dropdown' => 'Zavřít menu',
'collapse_article' => 'Srolovat',
'first_article' => 'Skočit na první článek',
'focus_search' => 'Hledání',
'help' => 'Zobrazit documentaci',
'javascript' => 'Pro použití zkratek musí být povolen JavaScript',
'last_article' => 'Skočit na poslední článek',
'load_more' => 'Načíst více článků',
'mark_read' => 'Označit jako přečtené',
'mark_favorite' => 'Označit jako oblíbené',
'navigation' => 'Navigace',
'navigation_help' => 'Pomocí přepínače "Shift" fungují navigační zkratky v rámci kanálů.<br/>Pomocí přepínače "Alt" fungují v rámci kategorií.',
'next_article' => 'Skočit na další článek',
'other_action' => 'Ostatní akce',
'previous_article' => 'Skočit na předchozí článek',
'see_on_website' => 'Navštívit původní webovou stránku',
'shift_for_all_read' => '+ <code>shift</code> označí vše jako přečtené',
'title' => 'Zkratky',
'user_filter' => 'Aplikovat uživatelské filtry',
'user_filter_help' => 'Je-li nastaven pouze jeden filtr, bude použit. Další filtry jsou dostupné pomocí jejich čísla.',
),
'user' => array(
'articles_and_size' => '%s článků (%s)',
'current' => 'Aktuální uživatel',
'is_admin' => 'je administrátor',
'users' => 'Uživatelé',
),
);

View file

@ -1,110 +0,0 @@
<?php
return array(
'admin' => array(
'optimization_complete' => 'Optimalizace dokončena',
),
'access' => array(
'denied' => 'Nemáte oprávnění přistupovat na tuto stránku',
'not_found' => 'Tato stránka neexistuje',
),
'auth' => array(
'form' => array(
'not_set' => 'Nastal problém s konfigurací přihlašovacího systému. Zkuste to prosím později.',
'set' => 'Webový formulář je nyní výchozí přihlašovací systém.',
),
'login' => array(
'invalid' => 'Login není platný',
'success' => 'Jste přihlášen',
),
'logout' => array(
'success' => 'Jste odhlášen',
),
'no_password_set' => 'Heslo administrátora nebylo nastaveno. Tato funkce není k dispozici.',
'not_persona' => 'Resetovat lze pouze systém Persona.',
),
'conf' => array(
'error' => 'Během ukládání nastavení došlo k chybě',
'query_created' => 'Dotaz "%s" byl vytvořen.',
'shortcuts_updated' => 'Zkratky byly aktualizovány',
'updated' => 'Nastavení bylo aktualizováno',
),
'extensions' => array(
'already_enabled' => '%s je již zapnut',
'disable' => array(
'ko' => '%s nelze vypnout. Pro více detailů <a href="%s">zkontrolujte logy FressRSS</a>.',
'ok' => '%s je nyní vypnut',
),
'enable' => array(
'ko' => '%s nelze zapnout. Pro více detailů <a href="%s">zkontrolujte logy FressRSS</a>.',
'ok' => '%s je nyní zapnut',
),
'no_access' => 'Nemáte přístup k %s',
'not_enabled' => '%s není ještě zapnut',
'not_found' => '%s neexistuje',
),
'import_export' => array(
'export_no_zip_extension' => 'Na serveru není naistalována podpora zip. Zkuste prosím exportovat soubory jeden po druhém.',
'feeds_imported' => 'Vaše kanály byly naimportovány a nyní budou aktualizovány',
'feeds_imported_with_errors' => 'Vaše kanály byly naimportovány, došlo ale k nějakým chybám',
'file_cannot_be_uploaded' => 'Soubor nelze nahrát!',
'no_zip_extension' => 'Na serveru není naistalována podpora zip.',
'zip_error' => 'Během importu zip souboru došlo k chybě.',
),
'sub' => array(
'actualize' => 'Aktualizovat',
'category' => array(
'created' => 'Kategorie %s byla vytvořena.',
'deleted' => 'Kategorie byla smazána.',
'emptied' => 'Kategorie byla vyprázdněna',
'error' => 'Kategorii nelze aktualizovat',
'name_exists' => 'Název kategorie již existuje.',
'no_id' => 'Musíte upřesnit id kategorie.',
'no_name' => 'Název kategorie nemůže být prázdný.',
'not_delete_default' => 'Nelze smazat výchozí kategorii!',
'not_exist' => 'Tato kategorie neexistuje!',
'over_max' => 'Dosáhl jste maximálního počtu kategorií (%d)',
'updated' => 'Kategorie byla aktualizována.',
),
'feed' => array(
'actualized' => '<em>%s</em> bylo aktualizováno',
'actualizeds' => 'RSS kanály byly aktualizovány',
'added' => 'RSS kanál <em>%s</em> byl přidán',
'already_subscribed' => 'Již jste přihlášen k odběru <em>%s</em>',
'deleted' => 'Kanál byl smazán',
'error' => 'Kanál nelze aktualizovat',
'internal_problem' => 'RSS kanál nelze přidat. Pro detaily <a href="%s">zkontrolujte logy FressRSS</a>.',
'invalid_url' => 'URL <em>%s</em> není platné',
'marked_read' => 'Kanály byly označeny jako přečtené',
'n_actualized' => '%d kanálů bylo aktualizováno',
'n_entries_deleted' => '%d článků bylo smazáno',
'no_refresh' => 'Nelze obnovit žádné kanály…',
'not_added' => '<em>%s</em> nemůže být přidán',
'over_max' => 'Dosáhl jste maximálního počtu kanálů (%d)',
'updated' => 'Kanál byl aktualizován',
),
'purge_completed' => 'Vyprázdněno (smazáno %d článků)',
),
'update' => array(
'can_apply' => 'FreshRSS bude nyní upgradováno na <strong>verzi %s</strong>.',
'error' => 'Během upgrade došlo k chybě: %s',
'file_is_nok' => 'Zkontrolujte oprávnění adresáře <em>%s</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'finished' => 'Upgrade hotov!',
'none' => 'Novější verze není k dispozici',
'server_not_found' => 'Nelze nalézt server s instalačním souborem. [%s]',
),
'user' => array(
'created' => array(
'_' => 'Uživatel %s byl vytvořen',
'error' => 'Uživatele %s nelze vytvořit',
),
'deleted' => array(
'_' => 'Uživatel %s byl smazán',
'error' => 'Uživatele %s nelze smazat',
),
),
'profile' => array(
'error' => 'Váš profil nelze změnit',
'updated' => 'Váš profil byl změněn',
),
);

View file

@ -1,184 +0,0 @@
<?php
return array(
'action' => array(
'actualize' => 'Aktualizovat',
'back_to_rss_feeds' => '← Zpět na seznam RSS kanálů',
'cancel' => 'Zrušit',
'create' => 'Vytvořit',
'disable' => 'Zakázat',
'empty' => 'Vyprázdnit',
'enable' => 'Povolit',
'export' => 'Export',
'filter' => 'Filtrovat',
'import' => 'Import',
'manage' => 'Spravovat',
'mark_read' => 'Označit jako přečtené',
'mark_favorite' => 'Označit jako oblíbené',
'remove' => 'Odstranit',
'see_website' => 'Navštívit WWW stránku',
'submit' => 'Odeslat',
'truncate' => 'Smazat všechny články',
),
'auth' => array(
'email' => 'Email',
'keep_logged_in' => 'Zapamatovat přihlášení <small>(1 měsíc)</small>',
'login' => 'Login',
'login_persona' => 'Přihlášení pomocí Persona',
'login_persona_problem' => 'Problém s připojením k Persona?',
'logout' => 'Odhlášení',
'password' => array(
'_' => 'Heslo',
'format' => '<small>Alespoň 7 znaků</small>',
),
'registration' => array(
'_' => 'Nový účet',
'ask' => 'Vytvořit účet?',
'title' => 'Vytvoření účtu',
),
'reset' => 'Reset přihlášení',
'username' => array(
'_' => 'Uživatel',
'admin' => 'Název administrátorského účtu',
'format' => '<small>maximálně 16 alfanumerických znaků</small>',
),
'will_reset' => 'Přihlašovací systém bude vyresetován: místo sytému Persona bude použito přihlášení formulářem.',
),
'date' => array(
'Apr' => '\\D\\u\\b\\e\\n',
'Aug' => '\\S\\r\\p\\e\\n',
'Dec' => '\\P\\r\\o\\s\\i\\n\\e\\c',
'Feb' => '\\Ú\\n\\o\\r',
'Jan' => '\\L\\e\\d\\e\\n',
'Jul' => '\\Č\\e\\r\\v\\e\\n\\e\\c',
'Jun' => '\\Č\\e\\r\\v\\e\\n',
'Mar' => '\\B\\ř\\e\\z\\e\\n',
'May' => '\\K\\v\\ě\\t\\e\\n',
'Nov' => '\\L\\i\\s\\t\\o\\p\\a\\d',
'Oct' => '\\Ř\\í\\j\\e\\n',
'Sep' => '\\Z\\á\\ř\\í',
'apr' => 'dub',
'april' => 'Dub',
'aug' => 'srp',
'august' => 'Srp',
'before_yesterday' => 'Předevčírem',
'dec' => 'pro',
'december' => 'Pro',
'feb' => 'úno',
'february' => 'Úno',
'format_date' => 'j\\. %s Y',
'format_date_hour' => 'j\\. %s Y \\v H\\:i',
'fri' => 'Pá',
'jan' => 'led',
'january' => 'Led',
'jul' => 'čvn',
'july' => 'Čvn',
'jun' => 'čer',
'june' => 'Čer',
'last_3_month' => 'Minulé tři měsíce',
'last_6_month' => 'Minulých šest měsíců',
'last_month' => 'Minulý měsíc',
'last_week' => 'Minulý týden',
'last_year' => 'Minulý rok',
'mar' => 'bře',
'march' => 'Bře',
'may' => 'Kvě',
'mon' => 'Po',
'month' => 'měsíce',
'nov' => 'lis',
'november' => 'Lis',
'oct' => 'říj',
'october' => 'Říj',
'sat' => 'So',
'sep' => 'zář',
'september' => 'Zář',
'sun' => 'Ne',
'thu' => 'Čt',
'today' => 'Dnes',
'tue' => 'Út',
'wed' => 'St',
'yesterday' => 'Včera',
),
'freshrss' => array(
'_' => 'FreshRSS',
'about' => 'O FreshRSS',
),
'js' => array(
'category_empty' => 'Prázdná kategorie',
'confirm_action' => 'Jste si jist, že chcete provést tuto akci? Změny nelze vrátit zpět!',
'confirm_action_feed_cat' => 'Jste si jist, že chcete provést tuto akci? Přijdete o související oblíbené položky a uživatelské dotazy. Změny nelze vrátit zpět!',
'feedback' => array(
'body_new_articles' => 'Je %%d nových článků k přečtení v FreshRSS.',
'request_failed' => 'Požadavek selhal, což může být způsobeno problémy s připojení k internetu.',
'title_new_articles' => 'FreshRSS: nové články!',
),
'new_article' => 'Jsou k dispozici nové články, stránku obnovíte kliknutím zde.',
'should_be_activated' => 'JavaScript musí být povolen',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'O aplikaci',
'admin' => 'Administrace',
'archiving' => 'Archivace',
'authentication' => 'Přihlášení',
'check_install' => 'Ověření instalace',
'configuration' => 'Nastavení',
'display' => 'Zobrazení',
'extensions' => 'Rozšíření',
'logs' => 'Logy',
'queries' => 'Uživatelské dotazy',
'reading' => 'Čtení',
'search' => 'Hledat výraz nebo #tagy',
'sharing' => 'Sdílení',
'shortcuts' => 'Zkratky',
'stats' => 'Statistika',
'system' => 'System configuration',// @todo translate
'update' => 'Aktualizace',
'user_management' => 'Správa uživatelů',
'user_profile' => 'Profil',
),
'pagination' => array(
'first' => 'První',
'last' => 'Poslední',
'load_more' => 'Načíst více článků',
'mark_all_read' => 'Označit vše jako přečtené',
'next' => 'Další',
'nothing_to_load' => 'Žádné nové články',
'previous' => 'Předchozí',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Tisk',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Upozornění!',
'blank_to_disable' => 'Zakázat - ponechte prázdné',
'by_author' => 'Od <em>%s</em>',
'by_default' => 'Výchozí',
'damn' => 'Sakra!',
'default_category' => 'Nezařazeno',
'no' => 'Ne',
'ok' => 'Ok!',
'or' => 'nebo',
'yes' => 'Ano',
),
);

View file

@ -1,61 +0,0 @@
<?php
return array(
'about' => array(
'_' => 'O FreshRSS',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Hlášení chyb',
'credits' => 'Poděkování',
'credits_content' => 'Některé designové prvky pocházejí z <a href="http://twitter.github.io/bootstrap/">Bootstrap</a>, FreshRSS ale tuto platformu nevyužívá. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Ikony</a> pocházejí z <a href="https://www.gnome.org/">GNOME projektu</a>. Font <em>Open Sans</em> vytvořil <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicony jsou shromažďovány pomocí <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS je založen na PHP framework <a href="https://github.com/marienfressinaud/MINZ">Minz</a>.',
'freshrss_description' => 'FreshRSS je čtečka RSS kanálů určená k provozu na vlastním serveru, podobná <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> nebo <a href="http://projet.idleman.fr/leed/">Leed</a>. Je to nenáročný a jednoduchý, zároveň ale mocný a konfigurovatelný nástroj.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">na Github</a>',
'license' => 'Licence',
'project_website' => 'Stránka projektu',
'title' => 'O FreshRSS',
'version' => 'Verze',
'website' => 'Webové stránka',
),
'feed' => array(
'add' => 'Můžete přidat kanály.',
'empty' => 'Žádné články k zobrazení.',
'rss_of' => 'RSS kanál %s',
'title' => 'RSS kanály',
'title_global' => 'Přehled',
'title_fav' => 'Oblíbené',
),
'log' => array(
'_' => 'Logy',
'clear' => 'Vymazat logy',
'empty' => 'Log je prázdný',
'title' => 'Logy',
),
'menu' => array(
'about' => 'O FreshRSS',
'add_query' => 'Vytvořit dotaz',
'before_one_day' => 'Den nazpět',
'before_one_week' => 'Před týdnem',
'favorites' => 'Oblíbené (%s)',
'global_view' => 'Přehled',
'main_stream' => 'Všechny kanály',
'mark_all_read' => 'Označit vše jako přečtené',
'mark_cat_read' => 'Označit kategorii jako přečtenou',
'mark_feed_read' => 'Označit kanál jako přečtený',
'newer_first' => 'Nové nejdříve',
'non-starred' => 'Zobrazit vše vyjma oblíbených',
'normal_view' => 'Normální',
'older_first' => 'Nejstarší nejdříve',
'queries' => 'Uživatelské dotazy',
'read' => 'Zobrazovat přečtené',
'reader_view' => 'Čtení',
'rss_view' => 'RSS kanál',
'search_short' => 'Hledat',
'starred' => 'Zobrazit oblíbené',
'stats' => 'Statistika',
'subscription' => 'Správa subskripcí',
'unread' => 'Zobrazovat nepřečtené',
),
'share' => 'Sdílet',
'tag' => array(
'related' => 'Související tagy',
),
);

View file

@ -1,121 +0,0 @@
<?php
return array(
'action' => array(
'finish' => 'Dokončit instalaci',
'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
'keep_install' => 'Zachovat předchozí instalaci',
'next_step' => 'Přejít na další krok',
'reinstall' => 'Reinstalovat FreshRSS',
),
'auth' => array(
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Webový formulář (tradiční, vyžaduje JavaScript)',
'http' => 'HTTP (pro pokročilé uživatele s HTTPS)',
'none' => 'Žádný (nebezpečné)',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
'password_format' => 'Alespoň 7 znaků',
'persona' => 'Mozilla Persona (moderní, vyžaduje JavaScript)',
'type' => 'Způsob přihlášení',
),
'bdd' => array(
'_' => 'Databáze',
'conf' => array(
'_' => 'Nastavení databáze',
'ko' => 'Ověřte informace o databázi.',
'ok' => 'Nastavení databáze bylo uloženo.',
),
'host' => 'Hostitel',
'prefix' => 'Prefix tabulky',
'password' => 'Heslo',
'type' => 'Typ databáze',
'username' => 'Uživatel',
),
'check' => array(
'_' => 'Kontrola',
'already_installed' => 'Zjistili jsme, že FreshRSS je již nainstalován!',
'cache' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/cache</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře cache jsou v pořádku.',
),
'ctype' => array(
'nok' => 'Není nainstalována požadovaná knihovna pro ověřování znaků (php-ctype).',
'ok' => 'Je nainstalována požadovaná knihovna pro ověřování znaků (ctype).',
),
'curl' => array(
'nok' => 'Nemáte cURL (balíček php5-curl).',
'ok' => 'Máte rozšíření cURL.',
),
'data' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře data jsou v pořádku.',
),
'dom' => array(
'nok' => 'Nemáte požadovanou knihovnu pro procházení DOM.',
'ok' => 'Máte požadovanou knihovnu pro procházení DOM.',
),
'favicons' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
),
'http_referer' => array(
'nok' => 'Zkontrolujte prosím že neměníte HTTP REFERER.',
'ok' => 'Váš HTTP REFERER je znám a odpovídá Vašemu serveru.',
),
'json' => array(
'nok' => 'Pro parsování JSON chybí doporučená knihovna.',
'ok' => 'Máte doporučenou knihovnu pro parsování JSON.',
),
'minz' => array(
'nok' => 'Nemáte framework Minz.',
'ok' => 'Máte framework Minz.',
),
'pcre' => array(
'nok' => 'Nemáte požadovanou knihovnu pro regulární výrazy (php-pcre).',
'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
),
'pdo' => array(
'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/persona</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře Mozilla Persona jsou v pořádku.',
),
'php' => array(
'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
'ok' => 'Vaše verze PHP je %s a je kompatibilní s FreshRSS.',
),
'users' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/users</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře users jsou v pořádku.',
),
'xml' => array(
'nok' => 'Pro parsování XML chybí požadovaná knihovna.',
'ok' => 'Máte požadovanou knihovnu pro parsování XML.',
),
),
'conf' => array(
'_' => 'Obecná nastavení',
'ok' => 'Nastavení bylo uloženo.',
),
'congratulations' => 'Gratulujeme!',
'default_user' => 'Jméno výchozího uživatele <small>(maximálně 16 alfanumerických znaků)</small>',
'delete_articles_after' => 'Smazat články starší než',
'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
'javascript_is_better' => 'Práce s FreshRSS je příjemnější se zapnutým JavaScriptem',
'js' => array(
'confirm_reinstall' => 'Reinstalací FreshRSS ztratíte předchozí konfiguraci. Opravdu chcete pokračovat?',
),
'language' => array(
'_' => 'Jazyk',
'choose' => 'Vyberte jazyk FreshRSS',
'defined' => 'Jazyk byl nastaven.',
),
'not_deleted' => 'Nastala chyba, soubor <em>%s</em> musíte smazat ručně.',
'ok' => 'Instalace byla úspěšná.',
'step' => 'krok %d',
'steps' => 'Kroky',
'title' => 'Instalace · FreshRSS',
'this_is_the_end' => 'Konec',
);

View file

@ -1,62 +0,0 @@
<?php
return array(
'category' => array(
'_' => 'Kategorie',
'add' => 'Přidat kategorii',
'empty' => 'Vyprázdit kategorii',
'new' => 'Nová kategorie',
),
'feed' => array(
'add' => 'Přidat RSS kanál',
'advanced' => 'Pokročilé',
'archiving' => 'Archivace',
'auth' => array(
'configuration' => 'Přihlášení',
'help' => 'Umožní přístup k RSS kanálům chráneným HTTP autentizací',
'http' => 'HTTP přihlášení',
'password' => 'Heslo',
'username' => 'Přihlašovací jméno',
),
'css_help' => 'Stáhne zkrácenou verzi RSS kanálů (pozor, náročnější na čas!)',
'css_path' => 'Původní CSS soubor článku z webových stránek',
'description' => 'Popis',
'empty' => 'Kanál je prázdný. Ověřte prosím zda je ještě autorem udržován.',
'error' => 'Vyskytl se problém s kanálem. Ověřte že je vždy dostupný, prosím, a poté jej aktualizujte.',
'in_main_stream' => 'Zobrazit ve “Všechny kanály”',
'informations' => 'Informace',
'keep_history' => 'Zachovat tento minimální počet článků',
'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do <em>%s</em>.',
'no_selected' => 'Nejsou označeny žádné kanály.',
'number_entries' => '%d článků',
'stats' => 'Statistika',
'think_to_add' => 'Můžete přidat kanály.',
'title' => 'Název',
'title_add' => 'Přidat RSS kanál',
'ttl' => 'Neobnovovat častěji než',
'url' => 'URL kanálu',
'validator' => 'Zkontrolovat platnost kanálu',
'website' => 'URL webové stránky',
'pubsubhubbub' => 'Okamžité oznámení s PubSubHubbub',
),
'import_export' => array(
'export' => 'Export',
'export_opml' => 'Exportovat seznam kanálů (OPML)',
'export_starred' => 'Exportovat oblíbené',
'feed_list' => 'Seznam %s článků',
'file_to_import' => 'Soubor k importu<br />(OPML, Json nebo Zip)',
'file_to_import_no_zip' => 'Soubor k importu<br />(OPML nebo Json)',
'import' => 'Import',
'starred_list' => 'Seznam oblíbených článků',
'title' => 'Import / export',
),
'menu' => array(
'bookmark' => 'Přihlásit (FreshRSS bookmark)',
'import_export' => 'Import / export',
'subscription_management' => 'Správa subskripcí',
),
'title' => array(
'_' => 'Správa subskripcí',
'feed_management' => 'Správa RSS kanálů',
),
);

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Anonymes Lesen der Artikel des Standardbenutzers (%s) erlauben',
'allow_anonymous_refresh' => 'Anonymes Aktualisieren der Artikel erlauben',
'api_enabled' => '<abbr>API</abbr>-Zugriff erlauben <small>(für mobile Anwendungen benötigt)</small>',
'form' => 'Webformular (traditionell, benötigt JavaScript)',
'http' => 'HTTP (HTTPS für erfahrene Benutzer)',
'none' => 'Keine (gefährlich)',
'persona' => 'Mozilla Persona (modern, benötigt JavaScript)',
'title' => 'Authentifizierung',
'title_reset' => 'Zurücksetzen der Authentifizierung',
'token' => 'Authentifizierungs-Token',
'token_help' => 'Erlaubt den Zugriff auf die RSS-Ausgabe des Standardbenutzers ohne Authentifizierung.',
'type' => 'Authentifizierungsmethode',
'unsafe_autologin' => 'Erlaube unsicheres automatisches Anmelden mit folgendem Format: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/cache</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/cache</em> sind in Ordnung.',
),
'categories' => array(
'nok' => 'Die Tabelle <em>category</em> ist schlecht konfiguriert.',
'ok' => 'Die Tabelle <em>category</em> ist korrekt konfiguriert.',
),
'connection' => array(
'nok' => 'Verbindung zur Datenbank kann nicht aufgebaut werden.',
'ok' => 'Verbindung zur Datenbank konnte aufgebaut werden.',
),
'ctype' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek für die Überprüfung von Zeichentypen (php-ctype).',
'ok' => 'Sie haben die benötigte Bibliothek für die Überprüfung von Zeichentypen (ctype).',
),
'curl' => array(
'nok' => 'Ihnen fehlt cURL (Paket php5-curl).',
'ok' => 'Sie haben die cURL-Erweiterung.',
),
'data' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data</em> sind in Ordnung.',
),
'database' => 'Datenbank-Installation',
'dom' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek um DOM zu durchstöbern (Paket php-xml).',
'ok' => 'Sie haben die benötigte Bibliothek um DOM zu durchstöbern.',
),
'entries' => array(
'nok' => 'Die Tabelle <em>entry</em> ist schlecht konfiguriert.',
'ok' => 'Die Tabelle <em>entry</em> ist korrekt konfiguriert.',
),
'favicons' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/favicons</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/favicons</em> sind in Ordnung.',
),
'feeds' => array(
'nok' => 'Die Tabelle <em>feed</em> ist schlecht konfiguriert.',
'ok' => 'Die Tabelle <em>feed</em> ist korrekt konfiguriert.',
),
'files' => 'Datei-Installation',
'json' => array(
'nok' => 'Ihnen fehlt die JSON-Erweiterung (Paket php5-json).',
'ok' => 'Sie haben die JSON-Erweiterung.',
),
'minz' => array(
'nok' => 'Ihnen fehlt das Minz-Framework.',
'ok' => 'Sie haben das Minz-Framework.',
),
'pcre' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek für reguläre Ausdrücke (php-pcre).',
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/persona</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/persona</em> sind in Ordnung.',
),
'php' => array(
'_' => 'PHP-Installation',
'nok' => 'Ihre PHP-Version ist %s aber FreshRSS benötigt mindestens Version %s.',
'ok' => 'Ihre PHP-Version ist %s, welche kompatibel mit FreshRSS ist.',
),
'tables' => array(
'nok' => 'Es fehlen eine oder mehrere Tabellen in der Datenbank.',
'ok' => 'Tabellen existieren in der Datenbank.',
),
'title' => 'Installationsüberprüfung',
'tokens' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/tokens</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/tokens</em> sind in Ordnung.',
),
'users' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/users</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/users</em> sind in Ordnung.',
),
'zip' => array(
'nok' => 'Ihnen fehlt die ZIP-Erweiterung (Paket php5-zip).',
'ok' => 'Sie haben die ZIP-Erweiterung.',
),
),
'extensions' => array(
'disabled' => 'Deaktiviert',
'empty_list' => 'Es gibt keine installierte Erweiterung.',
'enabled' => 'Aktiviert',
'no_configure_view' => 'Diese Erweiterung kann nicht konfiguriert werden.',
'system' => array(
'_' => 'System-Erweiterungen',
'no_rights' => 'System-Erweiterung (Sie haben keine Berechtigung dafür)',
),
'title' => 'Erweiterungen',
'user' => 'Benutzer-Erweiterungen',
),
'stats' => array(
'_' => 'Statistiken',
'all_feeds' => 'Alle Feeds',
'category' => 'Kategorie',
'entry_count' => 'Anzahl der Einträge',
'entry_per_category' => 'Einträge pro Kategorie',
'entry_per_day' => 'Einträge pro Tag (letzten 30 Tage)',
'entry_per_day_of_week' => 'Pro Wochentag (Durchschnitt: %.2f Nachrichten)',
'entry_per_hour' => 'Pro Stunde (Durchschnitt: %.2f Nachrichten)',
'entry_per_month' => 'Pro Monat (Durchschnitt: %.2f Nachrichten)',
'entry_repartition' => 'Einträge-Verteilung',
'feed' => 'Feed',
'feed_per_category' => 'Feeds pro Kategorie',
'idle' => 'Inaktive Feeds',
'main' => 'Haupt-Statistiken',
'main_stream' => 'Haupt-Feeds',
'menu' => array(
'idle' => 'Inaktive Feeds',
'main' => 'Haupt-Statistiken',
'repartition' => 'Artikel-Verteilung',
),
'no_idle' => 'Es gibt keinen inaktiven Feed!',
'number_entries' => '%d Artikel',
'percent_of_total' => '%% Gesamt',
'repartition' => 'Artikel-Verteilung',
'status_favorites' => 'Favoriten',
'status_read' => 'Gelesen',
'status_total' => 'Gesamt',
'status_unread' => 'Ungelesen',
'title' => 'Statistiken',
'top_feed' => 'Top 10-Feeds',
),
'system' => array(
'_' => 'System configuration', // @todo translate
'auto-update-url' => 'Auto-update server URL', // @todo translate
'instance-name' => 'Instance name', // @todo translate
'max-categories' => 'Categories per user limit', // @todo translate
'max-feeds' => 'Feeds per user limit', // @todo translate
'registration' => array(
'help' => '0 meint, dass es kein Account Limit gibt',
'number' => 'Maximale Anzahl von Accounts',
),
),
'update' => array(
'_' => 'System aktualisieren',
'apply' => 'Anwenden',
'check' => 'Auf neue Aktualisierungen prüfen',
'current_version' => 'Ihre aktuelle Version von FreshRSS ist %s.',
'last' => 'Letzte Überprüfung: %s',
'none' => 'Keine ausstehende Aktualisierung',
'title' => 'System aktualisieren',
),
'user' => array(
'articles_and_size' => '%s Artikel (%s)',
'create' => 'Neuen Benutzer erstellen',
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Sprache',
'number' => 'Es wurde bis jetzt %d Account erstellt',
'numbers' => 'Es wurden bis jetzt %d Accounts erstellt',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
'password_format' => 'mindestens 7 Zeichen',
'title' => 'Benutzer verwalten',
'user_list' => 'Liste der Benutzer',
'username' => 'Nutzername',
'users' => 'Benutzer',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Archivierung',
'advanced' => 'Erweitert',
'delete_after' => 'Entferne Artikel nach',
'help' => 'Weitere Optionen sind in den Einstellungen der individuellen Feeds verfügbar.',
'keep_history_by_feed' => 'Minimale Anzahl an Artikeln, die pro Feed behalten werden',
'optimize' => 'Datenbank optimieren',
'optimize_help' => 'Sollte gelegentlich durchgeführt werden, um die Größe der Datenbank zu reduzieren.',
'purge_now' => 'Jetzt bereinigen',
'title' => 'Archivierung',
'ttl' => 'Aktualisiere automatisch nicht öfter als',
),
'display' => array(
'_' => 'Anzeige',
'icon' => array(
'bottom_line' => 'Fußzeile',
'entry' => 'Artikel-Symbole',
'publication_date' => 'Datum der Veröffentlichung',
'related_tags' => 'Verwandte Tags',
'sharing' => 'Teilen',
'top_line' => 'Kopfzeile',
),
'language' => 'Sprache',
'notif_html5' => array(
'seconds' => 'Sekunden (0 bedeutet keine Zeitüberschreitung)',
'timeout' => 'Zeitüberschreitung für HTML5-Benachrichtigung',
),
'theme' => 'Erscheinungsbild',
'title' => 'Anzeige',
'width' => array(
'content' => 'Inhaltsbreite',
'large' => 'Gross',
'medium' => 'Mittel',
'no_limit' => 'Keine Begrenzung',
'thin' => 'Klein',
),
),
'query' => array(
'_' => 'Benutzerabfragen',
'deprecated' => 'Diese Abfrage ist nicht länger gültig. Die referenzierte Kategorie oder der Feed ist gelöscht worden.',
'filter' => 'Angewendeter Filter:',
'get_all' => 'Alle Artikel anzeigen',
'get_category' => 'Kategorie "%s" anzeigen',
'get_favorite' => 'Lieblingsartikel anzeigen',
'get_feed' => 'Feed "%s" anzeigen',
'no_filter' => 'Kein Filter',
'none' => 'Sie haben bisher keine Benutzerabfrage erstellt.',
'number' => 'Abfrage Nr. %d',
'order_asc' => 'Älteste Artikel zuerst anzeigen',
'order_desc' => 'Neueste Artikel zuerst anzeigen',
'search' => 'Suche nach "%s"',
'state_0' => 'Alle Artikel anzeigen',
'state_1' => 'Gelesene Artikel anzeigen',
'state_2' => 'Ungelesene Artikel anzeigen',
'state_3' => 'Alle Artikel anzeigen',
'state_4' => 'Lieblingsartikel anzeigen',
'state_5' => 'Gelesene Lieblingsartikel anzeigen',
'state_6' => 'Ungelesene Lieblingsartikel anzeigen',
'state_7' => 'Lieblingsartikel anzeigen',
'state_8' => 'Keine Lieblingsartikel anzeigen',
'state_9' => 'Gelesene ohne Lieblingsartikel anzeigen',
'state_10' => 'Ungelesene ohne Lieblingsartikel anzeigen',
'state_11' => 'Keine Lieblingsartikel anzeigen',
'state_12' => 'Alle Artikel anzeigen',
'state_13' => 'Gelesene Artikel anzeigen',
'state_14' => 'Ungelesene Artikel anzeigen',
'state_15' => 'Alle Artikel anzeigen',
'title' => 'Benutzerabfragen',
),
'profile' => array(
'_' => 'Profil-Verwaltung',
'delete' => array(
'_' => 'Accountlöschung',
'warn' => 'Dein Account und alle damit bezogenen Daten werden gelöscht.',
),
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Passwort-API<br /><small>(z. B. für mobile Anwendungen)</small>',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
'password_format' => 'mindestens 7 Zeichen',
'title' => 'Profil',
),
'reading' => array(
'_' => 'Lesen',
'after_onread' => 'Nach „Alle als gelesen markieren“,',
'articles_per_page' => 'Anzahl der Artikel pro Seite',
'auto_load_more' => 'Die nächsten Artikel am Seitenende laden',
'auto_remove_article' => 'Artikel nach dem Lesen verstecken',
'mark_updated_article_unread' => 'Markieren Sie aktualisierte Artikel als ungelesen',
'confirm_enabled' => 'Bei der Aktion „Alle als gelesen markieren“ einen Bestätigungsdialog anzeigen',
'display_articles_unfolded' => 'Artikel standardmäßig ausgeklappt zeigen',
'display_categories_unfolded' => 'Kategorien standardmäßig eingeklappt zeigen',
'hide_read_feeds' => 'Kategorien & Feeds ohne ungelesene Artikel verstecken (funktioniert nicht mit der Einstellung „Alle Artikel zeigen“)',
'img_with_lazyload' => 'Verwende die "träges Laden"-Methode zum Laden von Bildern',
'jump_next' => 'springe zum nächsten ungelesenen Geschwisterelement (Feed oder Kategorie)',
'number_divided_when_reader' => 'Geteilt durch 2 in der Lese-Ansicht.',
'read' => array(
'article_open_on_website' => 'wenn der Artikel auf der Original-Webseite geöffnet wird',
'article_viewed' => 'wenn der Artikel angesehen wird',
'scroll' => 'beim Blättern',
'upon_reception' => 'beim Empfang des Artikels',
'when' => 'Artikel als gelesen markieren…',
),
'show' => array(
'_' => 'Artikel zum Anzeigen',
'adaptive' => 'Anzeige anpassen',
'all_articles' => 'Alle Artikel zeigen',
'unread' => 'Nur ungelesene zeigen',
),
'sort' => array(
'_' => 'Sortierreihenfolge',
'newer_first' => 'Neuere zuerst',
'older_first' => 'Ältere zuerst',
),
'sticky_post' => 'Wenn geöffnet, den Artikel ganz oben anheften',
'title' => 'Lesen',
'view' => array(
'default' => 'Standard-Ansicht',
'global' => 'Globale Ansicht',
'normal' => 'Normale Ansicht',
'reader' => 'Lese-Ansicht',
),
),
'sharing' => array(
'_' => 'Teilen',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'E-Mail',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'Weitere Informationen',
'print' => 'Drucken',
'shaarli' => 'Shaarli',
'share_name' => 'Anzuzeigender Teilen-Name',
'share_url' => 'Zu verwendende Teilen-URL',
'title' => 'Teilen',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Tastenkombination',
'article_action' => 'Artikelaktionen',
'auto_share' => 'Teilen',
'auto_share_help' => 'Wenn es nur eine Option zum Teilen gibt, wird diese verwendet. Ansonsten sind die Optionen über ihre Nummer erreichbar.',
'close_dropdown' => 'Menüs schließen',
'collapse_article' => 'Einklappen',
'first_article' => 'Zum ersten Artikel springen',
'focus_search' => 'Auf das Suchfeld zugreifen',
'help' => 'Dokumentation anzeigen',
'javascript' => 'JavaScript muss aktiviert sein, um Tastaturkürzel benutzen zu können',
'last_article' => 'Zum letzten Artikel springen',
'load_more' => 'Weitere Artikel laden',
'mark_read' => 'Als gelesen markieren',
'mark_favorite' => 'Als Favorit markieren',
'navigation' => 'Navigation',
'navigation_help' => 'Mit der "Umschalttaste" finden die Tastenkombination auf Feeds Anwendung.<br/>Mit der "Alt-Taste" finden die Tastenkombination auf Kategorien Anwendung.',
'next_article' => 'Zum nächsten Artikel springen',
'other_action' => 'Andere Aktionen',
'previous_article' => 'Zum vorherigen Artikel springen',
'see_on_website' => 'Auf der Original-Webseite ansehen',
'shift_for_all_read' => '+ <code>Umschalttaste</code>, um alle Artikel als gelesen zu markieren.',
'title' => 'Tastenkombination',
'user_filter' => 'Auf Benutzerfilter zugreifen',
'user_filter_help' => 'Wenn es nur einen Benutzerfilter gibt, wird dieser verwendet. Ansonsten sind die Filter über ihre Nummer erreichbar.',
),
'user' => array(
'articles_and_size' => '%s Artikel (%s)',
'current' => 'Aktueller Benutzer',
'is_admin' => 'ist Administrator',
'users' => 'Benutzer',
),
);

View file

@ -1,110 +0,0 @@
<?php
return array(
'admin' => array(
'optimization_complete' => 'Optimierung abgeschlossen',
),
'access' => array(
'denied' => 'Sie haben nicht die Berechtigung, diese Seite aufzurufen',
'not_found' => 'Sie suchen nach einer Seite, die nicht existiert',
),
'auth' => array(
'form' => array(
'not_set' => 'Während der Konfiguration des Authentifikationssystems trat ein Fehler auf. Bitte versuchen Sie es später erneut.',
'set' => 'Formular ist ab sofort ihr Standard-Authentifikationssystem.',
),
'login' => array(
'invalid' => 'Anmeldung ist ungültig',
'success' => 'Sie sind angemeldet',
),
'logout' => array(
'success' => 'Sie sind abgemeldet',
),
'no_password_set' => 'Administrator-Passwort ist nicht gesetzt worden. Dieses Feature ist nicht verfügbar.',
'not_persona' => 'Nur das Persona-System kann zurückgesetzt werden.',
),
'conf' => array(
'error' => 'Während der Speicherung der Konfiguration trat ein Fehler auf',
'query_created' => 'Abfrage "%s" ist erstellt worden.',
'shortcuts_updated' => 'Die Tastenkombinationen sind aktualisiert worden',
'updated' => 'Die Konfiguration ist aktualisiert worden',
),
'extensions' => array(
'already_enabled' => '%s ist bereits aktiviert',
'disable' => array(
'ko' => '%s kann nicht deaktiviert werden. Für Details <a href="%s">prüfen Sie die FressRSS-Protokolle</a>.',
'ok' => '%s ist jetzt deaktiviert',
),
'enable' => array(
'ko' => '%s kann nicht aktiviert werden. Für Details <a href="%s">prüfen Sie die FressRSS-Protokolle</a>.',
'ok' => '%s ist jetzt aktiviert',
),
'no_access' => 'Sie haben keinen Zugang zu %s',
'not_enabled' => '%s ist noch nicht aktiviert',
'not_found' => '%s existiert nicht',
),
'import_export' => array(
'export_no_zip_extension' => 'Die Zip-Erweiterung fehlt auf Ihrem Server. Bitte versuchen Sie die Dateien eine nach der anderen zu exportieren.',
'feeds_imported' => 'Ihre Feeds sind importiert worden und werden jetzt aktualisiert',
'feeds_imported_with_errors' => 'Ihre Feeds sind importiert worden, aber es traten einige Fehler auf',
'file_cannot_be_uploaded' => 'Die Datei kann nicht hochgeladen werden!',
'no_zip_extension' => 'Die Zip-Erweiterung ist auf Ihrem Server nicht vorhanden.',
'zip_error' => 'Ein Fehler trat während des Zip-Imports auf.',
),
'sub' => array(
'actualize' => 'Aktualisieren',
'category' => array(
'created' => 'Die Kategorie %s ist erstellt worden.',
'deleted' => 'Die Kategorie ist gelöscht worden.',
'emptied' => 'Die Kategorie ist geleert worden.',
'error' => 'Die Kategorie kann nicht aktualisiert werden',
'name_exists' => 'Der Kategorie-Name existiert bereits.',
'no_id' => 'Sie müssen die ID der Kategorie präzisieren.',
'no_name' => 'Der Kategorie-Name kann nicht leer sein.',
'not_delete_default' => 'Sie können die Vorgabe-Kategorie nicht löschen!',
'not_exist' => 'Die Kategorie existiert nicht!',
'over_max' => 'Sie haben Ihre Kategorien-Limite erreicht (%d)',
'updated' => 'Die Kategorie ist aktualisiert worden.',
),
'feed' => array(
'actualized' => '<em>%s</em> ist aktualisiert worden',
'actualizeds' => 'Die RSS-Feeds sind aktualisiert worden',
'added' => 'Der RSS-Feed <em>%s</em> ist hinzugefügt worden',
'already_subscribed' => 'Sie haben <em>%s</em> bereits abonniert',
'deleted' => 'Der Feed ist gelöscht worden',
'error' => 'Der Feed kann nicht aktualisiert werden',
'internal_problem' => 'Der RSS-Feed konnte nicht hinzugefügt werden. Für Details <a href="%s">prüfen Sie die FressRSS-Protokolle</a>.',
'invalid_url' => 'Die URL <em>%s</em> ist ungültig',
'marked_read' => 'Die Feeds sind als gelesen markiert worden',
'n_actualized' => 'Die %d Feeds sind aktualisiert worden',
'n_entries_deleted' => 'Die %d Artikel sind gelöscht worden',
'no_refresh' => 'Es gibt keinen Feed zum Aktualisieren…',
'not_added' => '<em>%s</em> konnte nicht hinzugefügt werden',
'over_max' => 'Sie haben Ihre Feeds-Limite erreicht (%d)',
'updated' => 'Der Feed ist aktualisiert worden',
),
'purge_completed' => 'Bereinigung abgeschlossen (%d Artikel gelöscht)',
),
'update' => array(
'can_apply' => 'FreshRSS wird nun auf die <strong>Version %s</strong> aktualisiert.',
'error' => 'Der Aktualisierungsvorgang stieß auf einen Fehler: %s',
'file_is_nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>%s</em>. Der HTTP-Server muss Schreibrechte besitzen',
'finished' => 'Aktualisierung abgeschlossen!',
'none' => 'Keine Aktualisierung zum Anwenden',
'server_not_found' => 'Der Aktualisierungs-Server kann nicht gefunden werden. [%s]',
),
'user' => array(
'created' => array(
'_' => 'Der Benutzer %s ist erstellt worden',
'error' => 'Der Benutzer %s kann nicht erstellt werden',
),
'deleted' => array(
'_' => 'Der Benutzer %s ist gelöscht worden',
'error' => 'Der Benutzer %s kann nicht gelöscht werden',
),
),
'profile' => array(
'error' => 'Ihr Profil kann nicht geändert werden',
'updated' => 'Ihr Profil ist geändert worden',
),
);

View file

@ -1,185 +0,0 @@
<?php
return array(
'action' => array(
'actualize' => 'Aktualisieren',
'back_to_rss_feeds' => '← Zurück zu Ihren RSS-Feeds gehen',
'cancel' => 'Abbrechen',
'create' => 'Erstellen',
'disable' => 'Deaktivieren',
'empty' => 'Leeren',
'enable' => 'Aktivieren',
'export' => 'Exportieren',
'filter' => 'Filtern',
'import' => 'Importieren',
'manage' => 'Verwalten',
'mark_read' => 'Als gelesen markieren',
'mark_favorite' => 'Als Favorit markieren',
'remove' => 'Entfernen',
'see_website' => 'Webseite ansehen',
'submit' => 'Abschicken',
'truncate' => 'Alle Artikel löschen',
),
'auth' => array(
'email' => 'E-Mail-Adresse',
'keep_logged_in' => 'Eingeloggt bleiben <small>(1 Monat)</small>',
'login' => 'Anmelden',
'login_persona' => 'Anmelden mit Persona',
'login_persona_problem' => 'Verbindungsproblem mit Persona?',
'logout' => 'Abmelden',
'password' => array(
'_' => 'Passwort',
'format' => '<small>mindestens 7 Zeichen</small>',
),
'registration' => array(
'_' => 'Neuer Account',
'ask' => 'Erstelle einen Account?',
'title' => 'Accounterstellung',
),
'reset' => 'Zurücksetzen der Authentifizierung',
'username' => array(
'_' => 'Nutzername',
'admin' => 'Administrator-Nutzername',
'format' => '<small>maximal 16 alphanumerische Zeichen</small>',
),
'will_reset' => 'Authentifikationssystem wird zurückgesetzt: ein Formular wird anstelle von Persona benutzt.',
),
'date' => array(
'Apr' => '\\A\\p\\r\\i\\l',
'Aug' => '\\A\\u\\g\\u\\s\\t',
'Dec' => '\\D\\e\\z\\e\\m\\b\\e\\r',
'Feb' => '\\F\\e\\b\\r\\u\\a\\r',
'Jan' => '\\J\\a\\n\\u\\a\\r',
'Jul' => '\\J\\u\\l\\i',
'Jun' => '\\J\\u\\n\\i',
'Mar' => '\\M\\ä\\r\\z',
'May' => '\\M\\a\\i',
'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r',
'Oct' => '\\O\\k\\t\\o\\b\\e\\r',
'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r',
'apr' => 'Apr',
'april' => 'April',
'aug' => 'Aug',
'august' => 'August',
'before_yesterday' => 'Vor vorgestern',
'dec' => 'Dez',
'december' => 'Dezember',
'feb' => 'Feb',
'february' => 'Februar',
'format_date' => 'd\\. %s Y',
'format_date_hour' => 'd\\. %s Y \\u\\m H\\:i',
'fri' => 'Fr',
'jan' => 'Jan',
'january' => 'Januar',
'jul' => 'Jul',
'july' => 'Juli',
'jun' => 'Jun',
'june' => 'Juni',
'last_3_month' => 'Letzte drei Monate',
'last_6_month' => 'Letzte sechs Monate',
'last_month' => 'Letzter Monat',
'last_week' => 'Letzte Woche',
'last_year' => 'Letztes Jahr',
'mar' => 'Mär',
'march' => 'März',
'may' => 'Mai',
'mon' => 'Mo',
'month' => 'Monat(en)',
'nov' => 'Nov',
'november' => 'November',
'oct' => 'Okt',
'october' => 'Oktober',
'sat' => 'Sa',
'sep' => 'Sep',
'september' => 'September',
'sun' => 'So',
'thu' => 'Do',
'today' => 'Heute',
'tue' => 'Di',
'wed' => 'Mi',
'yesterday' => 'Gestern',
),
'freshrss' => array(
'_' => 'FreshRSS',
'about' => 'Über FreshRSS',
),
'js' => array(
'category_empty' => 'Kategorie leeren',
'confirm_action' => 'Sind Sie sicher, dass Sie diese Aktion durchführen wollen? Diese Aktion kann nicht abgebrochen werden!',
'confirm_action_feed_cat' => 'Sind Sie sicher, dass Sie diese Aktion durchführen wollen? Sie werden zugehörige Favoriten und Benutzerabfragen verlieren. Dies kann nicht abgebrochen werden!',
'feedback' => array(
'body_new_articles' => 'Es gibt %%d neue Artikel zum Lesen auf FreshRSS.',
'request_failed' => 'Eine Anfrage ist fehlgeschlagen, dies könnte durch Probleme mit der Internetverbindung verursacht worden sein.',
'title_new_articles' => 'FreshRSS: neue Artikel!',
),
'new_article' => 'Es gibt neue verfügbare Artikel. Klicken Sie, um die Seite zu aktualisieren.',
'should_be_activated' => 'JavaScript muss aktiviert sein',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'Über',
'admin' => 'Administration',
'archiving' => 'Archivierung',
'authentication' => 'Authentifizierung',
'check_install' => 'Installationsüberprüfung',
'configuration' => 'Konfiguration',
'display' => 'Anzeige',
'extensions' => 'Erweiterungen',
'logs' => 'Protokolle',
'queries' => 'Benutzerabfragen',
'reading' => 'Lesen',
'search' => 'Suche Worte oder #Tags',
'sharing' => 'Teilen',
'shortcuts' => 'Tastaturkürzel',
'stats' => 'Statistiken',
'system' => 'System configuration',// @todo translate
'update' => 'Aktualisieren',
'user_management' => 'Benutzer verwalten',
'user_profile' => 'Profil',
),
'pagination' => array(
'first' => 'Erste',
'last' => 'Letzte',
'load_more' => 'Weitere Artikel laden',
'mark_all_read' => 'Alle als gelesen markieren',
'next' => 'Nächste',
'nothing_to_load' => 'Es gibt keine weiteren Artikel',
'previous' => 'Vorherige',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'E-Mail',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Drucken',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Achtung!',
'blank_to_disable' => 'Zum Deaktivieren frei lassen',
'by_author' => 'Von <em>%s</em>',
'by_default' => 'standardmäßig',
'damn' => 'Verdammt!',
'default_category' => 'Unkategorisiert',
'no' => 'Nein',
'not_applicable' => 'Nicht verfügbar',
'ok' => 'OK!',
'or' => 'oder',
'yes' => 'Ja',
),
);

View file

@ -1,61 +0,0 @@
<?php
return array(
'about' => array(
'_' => 'Über',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Fehlerberichte',
'credits' => 'Credits',
'credits_content' => 'Einige Designelemente stammen von <a href="http://twitter.github.io/bootstrap/">Bootstrap</a>, obwohl FreshRSS dieses Framework nicht nutzt. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> stammen vom <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> Font wurde von <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a> erstellt. Favicons werden mit <a href="https://getfavicon.appspot.com/">getFavicon API</a> gesammelt. FreshRSS basiert auf <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, einem PHP-Framework.',
'freshrss_description' => 'FreshRSS ist ein RSS-Feedsaggregator zum selbst hosten wie zum Beispiel <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> oder <a href="http://projet.idleman.fr/leed/">Leed</a>. Er ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstarkes und konfigurierbares Werkzeug.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'Lizenz',
'project_website' => 'Projekt-Webseite',
'title' => 'Über',
'version' => 'Version',
'website' => 'Webseite',
),
'feed' => array(
'add' => 'Sie können Feeds hinzufügen.',
'empty' => 'Es gibt keinen Artikel zum Anzeigen.',
'rss_of' => 'RSS-Feed von %s',
'title' => 'Ihre RSS-Feeds',
'title_global' => 'Globale Ansicht',
'title_fav' => 'Ihre Favoriten',
),
'log' => array(
'_' => 'Protokolle',
'clear' => 'Protokolle leeren',
'empty' => 'Protokolldatei ist leer.',
'title' => 'Protokolle',
),
'menu' => array(
'about' => 'Über FreshRSS',
'add_query' => 'Eine Abfrage hinzufügen',
'before_one_day' => 'Vor einem Tag',
'before_one_week' => 'Vor einer Woche',
'favorites' => 'Favoriten (%s)',
'global_view' => 'Globale Ansicht',
'main_stream' => 'Haupt-Feeds',
'mark_all_read' => 'Alle als gelesen markieren',
'mark_cat_read' => 'Kategorie als gelesen markieren',
'mark_feed_read' => 'Feed als gelesen markieren',
'newer_first' => 'Neuere zuerst',
'non-starred' => 'Alle außer Favoriten zeigen',
'normal_view' => 'Normale Ansicht',
'older_first' => 'Ältere zuerst',
'queries' => 'Benutzerabfragen',
'read' => 'Nur gelesene zeigen',
'reader_view' => 'Lese-Ansicht',
'rss_view' => 'RSS-Feed',
'search_short' => 'Suchen',
'starred' => 'Nur Favoriten zeigen',
'stats' => 'Statistiken',
'subscription' => 'Abonnementverwaltung',
'unread' => 'Nur ungelesene zeigen',
),
'share' => 'Teilen',
'tag' => array(
'related' => 'Verwandte Tags',
),
);

View file

@ -1,121 +0,0 @@
<?php
return array(
'action' => array(
'finish' => 'Installation fertigstellen',
'fix_errors_before' => 'Bitte Fehler korrigieren, bevor zum nächsten Schritt gesprungen wird.',
'keep_install' => 'Vorherige Installation beibehalten (Daten)',
'next_step' => 'Zum nächsten Schritt springen',
'reinstall' => 'Neuinstallation von FreshRSS',
),
'auth' => array(
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Webformular (traditionell, benötigt JavaScript)',
'http' => 'HTTP (HTTPS für erfahrene Benutzer)',
'none' => 'Keine (gefährlich)',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
'password_format' => 'mindestens 7 Zeichen',
'persona' => 'Mozilla Persona (modern, benötigt JavaScript)',
'type' => 'Authentifizierungsmethode',
),
'bdd' => array(
'_' => 'Datenbank',
'conf' => array(
'_' => 'Datenbank-Konfiguration',
'ko' => 'Überprüfen Sie Ihre Datenbank-Information.',
'ok' => 'Datenbank-Konfiguration ist gespeichert worden.',
),
'host' => 'Host',
'prefix' => 'Tabellen-Präfix',
'password' => 'SQL-Password',
'type' => 'Datenbank-Typ',
'username' => 'SQL-Nutzername',
),
'check' => array(
'_' => 'Überprüfungen',
'already_installed' => 'Wir haben festgestellt, dass FreshRSS bereits installiert wurde!',
'cache' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/cache</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/cache</em> sind in Ordnung.',
),
'ctype' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek für die Überprüfung von Zeichentypen (php-ctype).',
'ok' => 'Sie haben die benötigte Bibliothek für die Überprüfung von Zeichentypen (ctype).',
),
'curl' => array(
'nok' => 'Ihnen fehlt cURL (Paket php5-curl).',
'ok' => 'Sie haben die cURL-Erweiterung.',
),
'data' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data</em> sind in Ordnung.',
),
'dom' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek um DOM zu durchstöbern.',
'ok' => 'Sie haben die benötigte Bibliothek um DOM zu durchstöbern.',
),
'favicons' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/favicons</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/favicons</em> sind in Ordnung.',
),
'http_referer' => array(
'nok' => 'Bitte stellen Sie sicher, dass Sie Ihren HTTP REFERER nicht abändern.',
'ok' => 'Ihr HTTP REFERER ist bekannt und entspricht Ihrem Server.',
),
'json' => array(
'nok' => 'Ihnen fehlt eine empfohlene Bibliothek um JSON zu parsen.',
'ok' => 'Sie haben eine empfohlene Bibliothek um JSON zu parsen.',
),
'minz' => array(
'nok' => 'Ihnen fehlt das Minz-Framework.',
'ok' => 'Sie haben das Minz-Framework.',
),
'pcre' => array(
'nok' => 'Ihnen fehlt eine benötigte Bibliothek für reguläre Ausdrücke (php-pcre).',
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/persona</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/persona</em> sind in Ordnung.',
),
'php' => array(
'nok' => 'Ihre PHP-Version ist %s aber FreshRSS benötigt mindestens Version %s.',
'ok' => 'Ihre PHP-Version ist %s, welche kompatibel mit FreshRSS ist.',
),
'users' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/users</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/users</em> sind in Ordnung.',
),
'xml' => array(
'nok' => 'Ihnen fehlt die benötigte Bibliothek um XML zu parsen.',
'ok' => 'Sie haben die benötigte Bibliothek um XML zu parsen.',
),
),
'conf' => array(
'_' => 'Allgemeine Konfiguration',
'ok' => 'Die allgemeine Konfiguration ist gespeichert worden.',
),
'congratulations' => 'Glückwunsch!',
'default_user' => 'Nutzername des Standardbenutzers <small>(maximal 16 alphanumerische Zeichen)</small>',
'delete_articles_after' => 'Entferne Artikel nach',
'fix_errors_before' => 'Bitte den Fehler korrigieren, bevor zum nächsten Schritt gesprungen wird.',
'javascript_is_better' => 'FreshRSS ist ansprechender mit aktiviertem JavaScript',
'js' => array(
'confirm_reinstall' => 'Du wirst deine vorherige Konfiguration (Daten) verlieren FreshRSS. Bist du sicher, dass du fortfahren willst?',
),
'language' => array(
'_' => 'Sprache',
'choose' => 'Wählen Sie eine Sprache für FreshRSS',
'defined' => 'Die Sprache ist festgelegt worden.',
),
'not_deleted' => 'Etwas ist schiefgelaufen; Sie müssen die Datei <em>%s</em> manuell löschen.',
'ok' => 'Der Installationsvorgang war erfolgreich.',
'step' => 'Schritt %d',
'steps' => 'Schritte',
'title' => 'Installation · FreshRSS',
'this_is_the_end' => 'Das ist das Ende',
);

View file

@ -1,62 +0,0 @@
<?php
return array(
'category' => array(
'_' => 'Kategorie',
'add' => 'Eine Kategorie hinzufügen',
'empty' => 'Leere Kategorie',
'new' => 'Neue Kategorie',
),
'feed' => array(
'add' => 'Einen RSS-Feed hinzufügen',
'advanced' => 'Erweitert',
'archiving' => 'Archivierung',
'auth' => array(
'configuration' => 'Anmelden',
'help' => 'Die Verbindung erlaubt Zugriff auf HTTP-geschützte RSS-Feeds',
'http' => 'HTTP-Authentifizierung',
'password' => 'HTTP-Passwort',
'username' => 'HTTP-Nutzername',
),
'css_help' => 'Ruft gekürzte RSS-Feeds ab (Achtung, benötigt mehr Zeit!)',
'css_path' => 'Pfad zur CSS-Datei des Artikels auf der Original-Webseite',
'description' => 'Beschreibung',
'empty' => 'Dieser Feed ist leer. Bitte stellen Sie sicher, dass er noch gepflegt wird.',
'error' => 'Dieser Feed ist auf ein Problem gestoßen. Bitte stellen Sie sicher, dass er immer lesbar ist und aktualisieren Sie ihn dann.',
'in_main_stream' => 'In Haupt-Feeds zeigen',
'informations' => 'Information',
'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird',
'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie <em>%s</em> eingefügt.',
'no_selected' => 'Kein Feed ausgewählt.',
'number_entries' => '%d Artikel',
'stats' => 'Statistiken',
'think_to_add' => 'Sie können Feeds hinzufügen.',
'title' => 'Titel',
'title_add' => 'Einen RSS-Feed hinzufügen',
'ttl' => 'Aktualisiere automatisch nicht öfter als',
'url' => 'Feed-URL',
'validator' => 'Überprüfen Sie die Gültigkeit des Feeds',
'website' => 'Webseiten-URL',
'pubsubhubbub' => 'Sofortbenachrichtigung mit PubSubHubbub',
),
'import_export' => array(
'export' => 'Exportieren',
'export_opml' => 'Liste der Feeds exportieren (OPML)',
'export_starred' => 'Ihre Favoriten exportieren',
'feed_list' => 'Liste von %s Artikeln',
'file_to_import' => 'Zu importierende Datei<br />(OPML, JSON oder Zip)',
'file_to_import_no_zip' => 'Zu importierende Datei<br />(OPML oder JSON)',
'import' => 'Importieren',
'starred_list' => 'Liste der Lieblingsartikel',
'title' => 'Importieren / Exportieren',
),
'menu' => array(
'bookmark' => 'Abonnieren (FreshRSS-Lesezeichen)',
'import_export' => 'Importieren / Exportieren',
'subscription_management' => 'Abonnementverwaltung',
),
'title' => array(
'_' => 'Abonnementverwaltung',
'feed_management' => 'Verwaltung der RSS-Feeds',
),
);

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)',
'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles',
'api_enabled' => 'Allow <abbr>API</abbr> access <small>(required for mobile apps)</small>',
'form' => 'Web form (traditional, requires JavaScript)',
'http' => 'HTTP (for advanced users with HTTPS)',
'none' => 'None (dangerous)',
'persona' => 'Mozilla Persona (modern, requires JavaScript)',
'title' => 'Authentication',
'title_reset' => 'Authentication reset',
'token' => 'Authentication token',
'token_help' => 'Allows access to RSS output of the default user without authentication:',
'type' => 'Authentication method',
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Check permissions on <em>./data/cache</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on cache directory are good.',
),
'categories' => array(
'nok' => 'Category table is bad configured.',
'ok' => 'Category table is ok.',
),
'connection' => array(
'nok' => 'Connection to the database cannot being established.',
'ok' => 'Connection to the database is ok.',
),
'ctype' => array(
'nok' => 'You lack a required library for character type checking (php-ctype).',
'ok' => 'You have the required library for character type checking (ctype).',
),
'curl' => array(
'nok' => 'You lack cURL (php5-curl package).',
'ok' => 'You have cURL extension.',
),
'data' => array(
'nok' => 'Check permissions on <em>./data</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on data directory are good.',
),
'database' => 'Database installation',
'dom' => array(
'nok' => 'You lack a required library to browse the DOM (php-xml package).',
'ok' => 'You have the required library to browse the DOM.',
),
'entries' => array(
'nok' => 'Entry table is bad configured.',
'ok' => 'Entry table is ok.',
),
'favicons' => array(
'nok' => 'Check permissions on <em>./data/favicons</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on favicons directory are good.',
),
'feeds' => array(
'nok' => 'Feed table is bad configured.',
'ok' => 'Feed table is ok.',
),
'files' => 'File installation',
'json' => array(
'nok' => 'You lack JSON (php5-json package).',
'ok' => 'You have JSON extension.',
),
'minz' => array(
'nok' => 'You lack the Minz framework.',
'ok' => 'You have the Minz framework.',
),
'pcre' => array(
'nok' => 'You lack a required library for regular expressions (php-pcre).',
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Check permissions on <em>./data/persona</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on Mozilla Persona directory are good.',
),
'php' => array(
'_' => 'PHP installation',
'nok' => 'Your PHP version is %s but FreshRSS requires at least version %s.',
'ok' => 'Your PHP version is %s, which is compatible with FreshRSS.',
),
'tables' => array(
'nok' => 'There is one or more lacking tables in the database.',
'ok' => 'Tables are existing in the database.',
),
'title' => 'Installation checking',
'tokens' => array(
'nok' => 'Check permissions on <em>./data/tokens</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on tokens directory are good.',
),
'users' => array(
'nok' => 'Check permissions on <em>./data/users</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on users directory are good.',
),
'zip' => array(
'nok' => 'You lack ZIP extension (php5-zip package).',
'ok' => 'You have ZIP extension.',
),
),
'extensions' => array(
'disabled' => 'Disabled',
'empty_list' => 'There is no installed extension',
'enabled' => 'Enabled',
'no_configure_view' => 'This extension cannot be configured.',
'system' => array(
'_' => 'System extensions',
'no_rights' => 'System extension (you have no rights on it)',
),
'title' => 'Extensions',
'user' => 'User extensions',
),
'stats' => array(
'_' => 'Statistics',
'all_feeds' => 'All feeds',
'category' => 'Category',
'entry_count' => 'Entry count',
'entry_per_category' => 'Entries per category',
'entry_per_day' => 'Entries per day (last 30 days)',
'entry_per_day_of_week' => 'Per day of week (average: %.2f messages)',
'entry_per_hour' => 'Per hour (average: %.2f messages)',
'entry_per_month' => 'Per month (average: %.2f messages)',
'entry_repartition' => 'Entries repartition',
'feed' => 'Feed',
'feed_per_category' => 'Feeds per category',
'idle' => 'Idle feeds',
'main' => 'Main statistics',
'main_stream' => 'Main stream',
'menu' => array(
'idle' => 'Idle feeds',
'main' => 'Main statistics',
'repartition' => 'Articles repartition',
),
'no_idle' => 'There is no idle feed!',
'number_entries' => '%d articles',
'percent_of_total' => '%% of total',
'repartition' => 'Articles repartition',
'status_favorites' => 'Favourites',
'status_read' => 'Read',
'status_total' => 'Total',
'status_unread' => 'Unread',
'title' => 'Statistics',
'top_feed' => 'Top ten feeds',
),
'system' => array(
'_' => 'System configuration',
'auto-update-url' => 'Auto-update server URL',
'instance-name' => 'Instance name',
'max-categories' => 'Categories per user limit',
'max-feeds' => 'Feeds per user limit',
'registration' => array(
'help' => '0 means that there is no account limit',
'number' => 'Max number of accounts',
),
),
'update' => array(
'_' => 'Update system',
'apply' => 'Apply',
'check' => 'Check for new updates',
'current_version' => 'Your current version of FreshRSS is the %s.',
'last' => 'Last verification: %s',
'none' => 'No update to apply',
'title' => 'Update system',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
'create' => 'Create new user',
'email_persona' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Language',
'number' => 'There is %d account created yet',
'numbers' => 'There are %d accounts created yet',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
'title' => 'Manage users',
'user_list' => 'List of users',
'username' => 'Username',
'users' => 'Users',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Archiving',
'advanced' => 'Advanced',
'delete_after' => 'Remove articles after',
'help' => 'More options are available in the individual feed settings',
'keep_history_by_feed' => 'Minimum number of articles to keep by feed',
'optimize' => 'Optimise database',
'optimize_help' => 'To do occasionally to reduce the size of the database',
'purge_now' => 'Purge now',
'title' => 'Archiving',
'ttl' => 'Do not automatically refresh more often than',
),
'display' => array(
'_' => 'Display',
'icon' => array(
'bottom_line' => 'Bottom line',
'entry' => 'Article icons',
'publication_date' => 'Date of publication',
'related_tags' => 'Related tags',
'sharing' => 'Sharing',
'top_line' => 'Top line',
),
'language' => 'Language',
'notif_html5' => array(
'seconds' => 'seconds (0 means no timeout)',
'timeout' => 'HTML5 notification timeout',
),
'theme' => 'Theme',
'title' => 'Display',
'width' => array(
'content' => 'Content width',
'large' => 'Large',
'medium' => 'Medium',
'no_limit' => 'No limit',
'thin' => 'Thin',
),
),
'query' => array(
'_' => 'User queries',
'deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.',
'filter' => 'Filter applied:',
'get_all' => 'Display all articles',
'get_category' => 'Display "%s" category',
'get_favorite' => 'Display favorite articles',
'get_feed' => 'Display "%s" feed',
'no_filter' => 'No filter',
'none' => 'You havent created any user query yet.',
'number' => 'Query n°%d',
'order_asc' => 'Display oldest articles first',
'order_desc' => 'Display newest articles first',
'search' => 'Search for "%s"',
'state_0' => 'Display all articles',
'state_1' => 'Display read articles',
'state_2' => 'Display unread articles',
'state_3' => 'Display all articles',
'state_4' => 'Display favorite articles',
'state_5' => 'Display read favorite articles',
'state_6' => 'Display unread favorite articles',
'state_7' => 'Display favorite articles',
'state_8' => 'Display not favorite articles',
'state_9' => 'Display read not favorite articles',
'state_10' => 'Display unread not favorite articles',
'state_11' => 'Display not favorite articles',
'state_12' => 'Display all articles',
'state_13' => 'Display read articles',
'state_14' => 'Display unread articles',
'state_15' => 'Display all articles',
'title' => 'User queries',
),
'profile' => array(
'_' => 'Profile management',
'delete' => array(
'_' => 'Account deletion',
'warn' => 'Your account and all the related data will be deleted.',
),
'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
'title' => 'Profile',
),
'reading' => array(
'_' => 'Reading',
'after_onread' => 'After “mark all as read”,',
'articles_per_page' => 'Number of articles per page',
'auto_load_more' => 'Load next articles at the page bottom',
'auto_remove_article' => 'Hide articles after reading',
'mark_updated_article_unread' => 'Mark updated articles as unread',
'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions',
'display_articles_unfolded' => 'Show articles unfolded by default',
'display_categories_unfolded' => 'Show categories folded by default',
'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
'jump_next' => 'jump to next unread sibling (feed or category)',
'number_divided_when_reader' => 'Divided by 2 in the reading view.',
'read' => array(
'article_open_on_website' => 'when article is opened on its original website',
'article_viewed' => 'when article is viewed',
'scroll' => 'while scrolling',
'upon_reception' => 'upon reception of the article',
'when' => 'Mark article as read…',
),
'show' => array(
'_' => 'Articles to display',
'adaptive' => 'Adjust showing',
'all_articles' => 'Show all articles',
'unread' => 'Show only unread',
),
'sort' => array(
'_' => 'Sort order',
'newer_first' => 'Newer first',
'older_first' => 'Oldest first',
),
'sticky_post' => 'Stick the article to the top when opened',
'title' => 'Reading',
'view' => array(
'default' => 'Default view',
'global' => 'Global view',
'normal' => 'Normal view',
'reader' => 'Reading view',
),
),
'sharing' => array(
'_' => 'Sharing',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'More information',
'print' => 'Print',
'shaarli' => 'Shaarli',
'share_name' => 'Share name to display',
'share_url' => 'Share URL to use',
'title' => 'Sharing',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Shortcuts',
'article_action' => 'Article actions',
'auto_share' => 'Share',
'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
'close_dropdown' => 'Close menus',
'collapse_article' => 'Collapse',
'first_article' => 'Skip to the first article',
'focus_search' => 'Access search box',
'help' => 'Display documentation',
'javascript' => 'JavaScript must be enabled in order to use shortcuts',
'last_article' => 'Skip to the last article',
'load_more' => 'Load more articles',
'mark_read' => 'Mark as read',
'mark_favorite' => 'Mark as favourite',
'navigation' => 'Navigation',
'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
'next_article' => 'Skip to the next article',
'other_action' => 'Other actions',
'previous_article' => 'Skip to the previous article',
'see_on_website' => 'See on original website',
'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
'title' => 'Shortcuts',
'user_filter' => 'Access user filters',
'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
'current' => 'Current user',
'is_admin' => 'is administrator',
'users' => 'Users',
),
);

View file

@ -1,110 +0,0 @@
<?php
return array(
'admin' => array(
'optimization_complete' => 'Optimisation complete',
),
'access' => array(
'denied' => 'You dont have permission to access this page',
'not_found' => 'You are looking for a page which doesnt exist',
),
'auth' => array(
'form' => array(
'not_set' => 'A problem occured during authentication system configuration. Please retry later.',
'set' => 'Form is now your default authentication system.',
),
'login' => array(
'invalid' => 'Login is invalid',
'success' => 'You are connected',
),
'logout' => array(
'success' => 'You are disconnected',
),
'no_password_set' => 'Administrator password hasnt been set. This feature isnt available.',
'not_persona' => 'Only Persona system can be reset.',
),
'conf' => array(
'error' => 'An error occurred during configuration saving',
'query_created' => 'Query "%s" has been created.',
'shortcuts_updated' => 'Shortcuts have been updated',
'updated' => 'Configuration has been updated',
),
'extensions' => array(
'already_enabled' => '%s is already enabled',
'disable' => array(
'ko' => '%s cannot be disabled. <a href="%s">Check FressRSS logs</a> for details.',
'ok' => '%s is now disabled',
),
'enable' => array(
'ko' => '%s cannot be enabled. <a href="%s">Check FressRSS logs</a> for details.',
'ok' => '%s is now enabled',
),
'no_access' => 'You have no access on %s',
'not_enabled' => '%s is not enabled yet',
'not_found' => '%s does not exist',
),
'import_export' => array(
'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.',
'feeds_imported' => 'Your feeds have been imported and will now be updated',
'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
'file_cannot_be_uploaded' => 'File cannot be uploaded!',
'no_zip_extension' => 'Zip extension is not present on your server.',
'zip_error' => 'An error occured during Zip import.',
),
'sub' => array(
'actualize' => 'Actualise',
'category' => array(
'created' => 'Category %s has been created.',
'deleted' => 'Category has been deleted.',
'emptied' => 'Category has been emptied',
'error' => 'Category cannot be updated',
'name_exists' => 'Category name already exists.',
'no_id' => 'You must precise the id of the category.',
'no_name' => 'Category name cannot be empty.',
'not_delete_default' => 'You cannot delete the default category!',
'not_exist' => 'The category does not exist!',
'over_max' => 'You have reached your limit of categories (%d)',
'updated' => 'Category has been updated.',
),
'feed' => array(
'actualized' => '<em>%s</em> has been updated',
'actualizeds' => 'RSS feeds have been updated',
'added' => 'RSS feed <em>%s</em> has been added',
'already_subscribed' => 'You have already subscribed to <em>%s</em>',
'deleted' => 'Feed has been deleted',
'error' => 'Feed cannot be updated',
'internal_problem' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.',
'invalid_url' => 'URL <em>%s</em> is invalid',
'marked_read' => 'Feeds have been marked as read',
'n_actualized' => '%d feeds have been updated',
'n_entries_deleted' => '%d articles have been deleted',
'no_refresh' => 'There is no feed to refresh…',
'not_added' => '<em>%s</em> could not be added',
'over_max' => 'You have reached your limit of feeds (%d)',
'updated' => 'Feed has been updated',
),
'purge_completed' => 'Purge completed (%d articles deleted)',
),
'update' => array(
'can_apply' => 'FreshRSS will now be updated to the <strong>version %s</strong>.',
'error' => 'The update process has encountered an error: %s',
'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
'finished' => 'Update completed!',
'none' => 'No update to apply',
'server_not_found' => 'Update server cannot be found. [%s]',
),
'user' => array(
'created' => array(
'_' => 'User %s has been created',
'error' => 'User %s cannot be created',
),
'deleted' => array(
'_' => 'User %s has been deleted',
'error' => 'User %s cannot be deleted',
),
),
'profile' => array(
'error' => 'Your profile cannot be modified',
'updated' => 'Your profile has been modified',
),
);

View file

@ -1,185 +0,0 @@
<?php
return array(
'action' => array(
'actualize' => 'Actualize',
'back_to_rss_feeds' => '← Go back to your RSS feeds',
'cancel' => 'Cancel',
'create' => 'Create',
'disable' => 'Disable',
'empty' => 'Empty',
'enable' => 'Enable',
'export' => 'Export',
'filter' => 'Filter',
'import' => 'Import',
'manage' => 'Manage',
'mark_read' => 'Mark as read',
'mark_favorite' => 'Mark as favourite',
'remove' => 'Remove',
'see_website' => 'See website',
'submit' => 'Submit',
'truncate' => 'Delete all articles',
),
'auth' => array(
'email' => 'Email address',
'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
'login' => 'Login',
'login_persona' => 'Login with Persona',
'login_persona_problem' => 'Connection problem with Persona?',
'logout' => 'Logout',
'password' => array(
'_' => 'Password',
'format' => '<small>At least 7 characters</small>',
),
'registration' => array(
'_' => 'New account',
'ask' => 'Create an account?',
'title' => 'Account creation',
),
'reset' => 'Authentication reset',
'username' => array(
'_' => 'Username',
'admin' => 'Administrator username',
'format' => '<small>maximum 16 alphanumeric characters</small>',
),
'will_reset' => 'Authentication system will be reset: a form will be used instead of Persona.',
),
'date' => array(
'Apr' => '\\A\\p\\r\\i\\l',
'Aug' => '\\A\\u\\g\\u\\s\\t',
'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r',
'Feb' => '\\F\\e\\b\\r\\u\\a\\r\\y',
'Jan' => '\\J\\a\\n\\u\\a\\r\\y',
'Jul' => '\\J\\u\\l\\y',
'Jun' => '\\J\\u\\n\\e',
'Mar' => '\\M\\a\\r\\c\\h',
'May' => '\\M\\a\\y',
'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r',
'Oct' => '\\O\\c\\t\\o\\b\\e\\r',
'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r',
'apr' => 'apr',
'april' => 'Apr',
'aug' => 'aug',
'august' => 'Aug',
'before_yesterday' => 'Before yesterday',
'dec' => 'dec',
'december' => 'Dec',
'feb' => 'feb',
'february' => 'Feb',
'format_date' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y',
'format_date_hour' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y \\a\\t H\\:i',
'fri' => 'Fri',
'jan' => 'jan',
'january' => 'Jan',
'jul' => 'jul',
'july' => 'Jul',
'jun' => 'jun',
'june' => 'Jun',
'last_3_month' => 'Last three months',
'last_6_month' => 'Last six months',
'last_month' => 'Last month',
'last_week' => 'Last week',
'last_year' => 'Last year',
'mar' => 'mar',
'march' => 'Mar',
'may' => 'May',
'mon' => 'Mon',
'month' => 'months',
'nov' => 'nov',
'november' => 'Nov',
'oct' => 'oct',
'october' => 'Oct',
'sat' => 'Sat',
'sep' => 'sep',
'september' => 'Sep',
'sun' => 'Sun',
'thu' => 'Thu',
'today' => 'Today',
'tue' => 'Tue',
'wed' => 'Wed',
'yesterday' => 'Yesterday',
),
'freshrss' => array(
'_' => 'FreshRSS',
'about' => 'About FreshRSS',
),
'js' => array(
'category_empty' => 'Empty category',
'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!',
'confirm_action_feed_cat' => 'Are you sure you want to perform this action? You will lose related favorites and user queries. It cannot be cancelled!',
'feedback' => array(
'body_new_articles' => 'There are %%d new articles to read on FreshRSS.',
'request_failed' => 'A request has failed, it may have been caused by Internet connection problems.',
'title_new_articles' => 'FreshRSS: new articles!',
),
'new_article' => 'There are new available articles, click to refresh the page.',
'should_be_activated' => 'JavaScript must be enabled',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'About',
'admin' => 'Administration',
'archiving' => 'Archiving',
'authentication' => 'Authentication',
'check_install' => 'Installation checking',
'configuration' => 'Configuration',
'display' => 'Display',
'extensions' => 'Extensions',
'logs' => 'Logs',
'queries' => 'User queries',
'reading' => 'Reading',
'search' => 'Search words or #tags',
'sharing' => 'Sharing',
'shortcuts' => 'Shortcuts',
'stats' => 'Statistics',
'system' => 'System configuration',
'update' => 'Update',
'user_management' => 'Manage users',
'user_profile' => 'Profile',
),
'pagination' => array(
'first' => 'First',
'last' => 'Last',
'load_more' => 'Load more articles',
'mark_all_read' => 'Mark all as read',
'next' => 'Next',
'nothing_to_load' => 'There are no more articles',
'previous' => 'Previous',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Print',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Warning!',
'blank_to_disable' => 'Leave blank to disable',
'by_author' => 'By <em>%s</em>',
'by_default' => 'By default',
'damn' => 'Damn!',
'default_category' => 'Uncategorized',
'no' => 'No',
'not_applicable' => 'Not available',
'ok' => 'Ok!',
'or' => 'or',
'yes' => 'Yes',
),
);

View file

@ -1,61 +0,0 @@
<?php
return array(
'about' => array(
'_' => 'About',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Bugs reports',
'credits' => 'Credits',
'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesnt use this framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> come from <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police has been created by <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicons are collected with <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS is based on <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, a PHP framework.',
'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://projet.idleman.fr/leed/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">on Github</a>',
'license' => 'License',
'project_website' => 'Project website',
'title' => 'About',
'version' => 'Version',
'website' => 'Website',
),
'feed' => array(
'add' => 'You may add some feeds.',
'empty' => 'There is no article to show.',
'rss_of' => 'RSS feed of %s',
'title' => 'Your RSS feeds',
'title_global' => 'Global view',
'title_fav' => 'Your favourites',
),
'log' => array(
'_' => 'Logs',
'clear' => 'Clear the logs',
'empty' => 'Log file is empty',
'title' => 'Logs',
),
'menu' => array(
'about' => 'About FreshRSS',
'add_query' => 'Add a query',
'before_one_day' => 'Before one day',
'before_one_week' => 'Before one week',
'favorites' => 'Favourites (%s)',
'global_view' => 'Global view',
'main_stream' => 'Main stream',
'mark_all_read' => 'Mark all as read',
'mark_cat_read' => 'Mark category as read',
'mark_feed_read' => 'Mark feed as read',
'newer_first' => 'Newer first',
'non-starred' => 'Show all but favorites',
'normal_view' => 'Normal view',
'older_first' => 'Oldest first',
'queries' => 'User queries',
'read' => 'Show only read',
'reader_view' => 'Reading view',
'rss_view' => 'RSS feed',
'search_short' => 'Search',
'starred' => 'Show only favorites',
'stats' => 'Statistics',
'subscription' => 'Subscriptions management',
'unread' => 'Show only unread',
),
'share' => 'Share',
'tag' => array(
'related' => 'Related tags',
),
);

View file

@ -1,121 +0,0 @@
<?php
return array(
'action' => array(
'finish' => 'Complete installation',
'fix_errors_before' => 'Please fix errors before skipping to the next step.',
'keep_install' => 'Keep previous installation',
'next_step' => 'Go to the next step',
'reinstall' => 'Reinstall FreshRSS',
),
'auth' => array(
'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Web form (traditional, requires JavaScript)',
'http' => 'HTTP (for advanced users with HTTPS)',
'none' => 'None (dangerous)',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
'persona' => 'Mozilla Persona (modern, requires JavaScript)',
'type' => 'Authentication method',
),
'bdd' => array(
'_' => 'Database',
'conf' => array(
'_' => 'Database configuration',
'ko' => 'Verify your database information.',
'ok' => 'Database configuration has been saved.',
),
'host' => 'Host',
'prefix' => 'Table prefix',
'password' => 'HTTP password',
'type' => 'Type of database',
'username' => 'HTTP username',
),
'check' => array(
'_' => 'Checks',
'already_installed' => 'We have detected that FreshRSS is already installed!',
'cache' => array(
'nok' => 'Check permissions on <em>./data/cache</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on cache directory are good.',
),
'ctype' => array(
'nok' => 'You lack a required library for character type checking (php-ctype).',
'ok' => 'You have the required library for character type checking (ctype).',
),
'curl' => array(
'nok' => 'You lack cURL (php5-curl package).',
'ok' => 'You have cURL extension.',
),
'data' => array(
'nok' => 'Check permissions on <em>./data</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on data directory are good.',
),
'dom' => array(
'nok' => 'You lack a required library to browse the DOM.',
'ok' => 'You have the required library to browse the DOM.',
),
'favicons' => array(
'nok' => 'Check permissions on <em>./data/favicons</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on favicons directory are good.',
),
'http_referer' => array(
'nok' => 'Please check that you are not altering your HTTP REFERER.',
'ok' => 'Your HTTP REFERER is known and corresponds to your server.',
),
'json' => array(
'nok' => 'You lack a recommended library to parse JSON.',
'ok' => 'You have a recommended library to parse JSON.',
),
'minz' => array(
'nok' => 'You lack the Minz framework.',
'ok' => 'You have the Minz framework.',
),
'pcre' => array(
'nok' => 'You lack a required library for regular expressions (php-pcre).',
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Check permissions on <em>./data/persona</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on Mozilla Persona directory are good.',
),
'php' => array(
'nok' => 'Your PHP version is %s but FreshRSS requires at least version %s.',
'ok' => 'Your PHP version is %s, which is compatible with FreshRSS.',
),
'users' => array(
'nok' => 'Check permissions on <em>./data/users</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on users directory are good.',
),
'xml' => array(
'nok' => 'You lack the required library to parse XML.',
'ok' => 'You have the required library to parse XML.',
),
),
'conf' => array(
'_' => 'General configuration',
'ok' => 'General configuration has been saved.',
),
'congratulations' => 'Congratulations!',
'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>',
'delete_articles_after' => 'Remove articles after',
'fix_errors_before' => 'Please fix errors before skipping to the next step.',
'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled',
'js' => array(
'confirm_reinstall' => 'You will lose your previous configuration by reinstalling FreshRSS. Are you sure you want to continue?',
),
'language' => array(
'_' => 'Language',
'choose' => 'Choose a language for FreshRSS',
'defined' => 'Language has been defined.',
),
'not_deleted' => 'Something went wrong; you must delete the file <em>%s</em> manually.',
'ok' => 'The installation process was successful.',
'step' => 'step %d',
'steps' => 'Steps',
'title' => 'Installation · FreshRSS',
'this_is_the_end' => 'This is the end',
);

View file

@ -1,62 +0,0 @@
<?php
return array(
'category' => array(
'_' => 'Category',
'add' => 'Add a category',
'empty' => 'Empty category',
'new' => 'New category',
),
'feed' => array(
'add' => 'Add a RSS feed',
'advanced' => 'Advanced',
'archiving' => 'Archivage',
'auth' => array(
'configuration' => 'Login',
'help' => 'Connection allows to access HTTP protected RSS feeds',
'http' => 'HTTP Authentication',
'password' => 'HTTP password',
'username' => 'HTTP username',
),
'css_help' => 'Retrieves truncated RSS feeds (caution, requires more time!)',
'css_path' => 'Articles CSS path on original website',
'description' => 'Description',
'empty' => 'This feed is empty. Please verify that it is still maintained.',
'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
'in_main_stream' => 'Show in main stream',
'informations' => 'Information',
'keep_history' => 'Minimum number of articles to keep',
'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',
'no_selected' => 'No feed selected.',
'number_entries' => '%d articles',
'stats' => 'Statistics',
'think_to_add' => 'You may add some feeds.',
'title' => 'Title',
'title_add' => 'Add a RSS feed',
'ttl' => 'Do not automatically refresh more often than',
'url' => 'Feed URL',
'validator' => 'Check the validity of the feed',
'website' => 'Website URL',
'pubsubhubbub' => 'Instant notification with PubSubHubbub',
),
'import_export' => array(
'export' => 'Export',
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
'feed_list' => 'List of %s articles',
'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
'file_to_import_no_zip' => 'File to import<br />(OPML or Json)',
'import' => 'Import',
'starred_list' => 'List of favourite articles',
'title' => 'Import / export',
),
'menu' => array(
'bookmark' => 'Subscribe (FreshRSS bookmark)',
'import_export' => 'Import / export',
'subscription_management' => 'Subscriptions management',
),
'title' => array(
'_' => 'Subscriptions management',
'feed_management' => 'RSS feeds management',
),
);

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Autoriser la lecture anonyme des articles de lutilisateur par défaut (%s)',
'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux',
'api_enabled' => 'Autoriser laccès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>',
'form' => 'Formulaire (traditionnel, requiert JavaScript)',
'http' => 'HTTP (pour utilisateurs avancés avec HTTPS)',
'none' => 'Aucune (dangereux)',
'persona' => 'Mozilla Persona (moderne, requiert JavaScript)',
'title' => 'Authentification',
'title_reset' => 'Réinitialisation de lauthentification',
'token' => 'Jeton didentification',
'token_help' => 'Permet daccéder à la sortie RSS de lutilisateur par défaut sans besoin de sauthentifier :',
'type' => 'Méthode dauthentification',
'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/cache</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de cache sont bons.',
),
'categories' => array(
'nok' => 'La table category est mal configurée.',
'ok' => 'La table category est bien configurée.',
),
'connection' => array(
'nok' => 'La connexion à la base de données est impossible.',
'ok' => 'La connexion à la base de données est bonne.',
),
'ctype' => array(
'nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype).',
'ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype).',
),
'curl' => array(
'nok' => 'Vous ne disposez pas de cURL (paquet php5-curl).',
'ok' => 'Vous disposez de cURL.',
),
'data' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de data sont bons.',
),
'database' => 'Installation de la base de données',
'dom' => array(
'nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml).',
'ok' => 'Vous disposez du nécessaire pour parcourir le DOM.',
),
'entries' => array(
'nok' => 'La table entry est mal configurée.',
'ok' => 'La table entry est bien configurée.',
),
'favicons' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire des favicons sont bons.',
),
'feeds' => array(
'nok' => 'La table feed est mal configurée.',
'ok' => 'La table feed est bien configurée.',
),
'files' => 'Installation des fichiers',
'json' => array(
'nok' => 'Vous ne disposez pas de JSON (paquet php5-json).',
'ok' => 'Vous disposez de l\'extension JSON.',
),
'minz' => array(
'nok' => 'Vous ne disposez pas de la librairie Minz.',
'ok' => 'Vous disposez du framework Minz',
),
'pcre' => array(
'nok' => 'Il manque une librairie pour les expressions régulières (php-pcre).',
'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
),
'pdo' => array(
'nok' => 'Vous ne disposez pas de PDO ou dun des drivers supportés (pdo_mysql, pdo_sqlite).',
'ok' => 'Vous disposez de PDO et dau moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/persona</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons.',
),
'php' => array(
'_' => 'Installation de PHP',
'nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s.',
'ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS.',
),
'tables' => array(
'nok' => 'Il manque une ou plusieurs tables en base de données.',
'ok' => 'Les tables sont bien présentes en base de données.',
),
'title' => 'Vérification de linstallation',
'tokens' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/tokens</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire des tokens sont bons.',
),
'users' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/users</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire des utilisateurs sont bons.',
),
'zip' => array(
'nok' => 'Vous ne disposez pas de l\'extension ZIP (paquet php5-zip).',
'ok' => 'Vous disposez de l\'extension ZIP.',
),
),
'extensions' => array(
'disabled' => 'Désactivée',
'empty_list' => 'Il ny a aucune extension installée.',
'enabled' => 'Activée',
'no_configure_view' => 'Cette extension ne peut pas être configurée.',
'system' => array(
'_' => 'Extensions système',
'no_rights' => 'Extension système (vous navez aucun droit dessus)',
),
'title' => 'Extensions',
'user' => 'Extensions utilisateur',
),
'stats' => array(
'_' => 'Statistiques',
'all_feeds' => 'Tous les flux',
'category' => 'Catégorie',
'entry_count' => 'Nombre darticles',
'entry_per_category' => 'Articles par catégorie',
'entry_per_day' => 'Nombre darticles par jour (30 derniers jours)',
'entry_per_day_of_week' => 'Par jour de la semaine (moyenne : %.2f messages)',
'entry_per_hour' => 'Par heure (moyenne : %.2f messages)',
'entry_per_month' => 'Par mois (moyenne : %.2f messages)',
'entry_repartition' => 'Répartition des articles',
'feed' => 'Flux',
'feed_per_category' => 'Flux par catégorie',
'idle' => 'Flux inactifs',
'main' => 'Statistiques principales',
'main_stream' => 'Flux principal',
'menu' => array(
'idle' => 'Flux inactifs',
'main' => 'Statistiques principales',
'repartition' => 'Répartition des articles',
),
'no_idle' => 'Il ny a aucun flux inactif !',
'number_entries' => '%d articles',
'percent_of_total' => '%% du total',
'repartition' => 'Répartition des articles',
'status_favorites' => 'favoris',
'status_read' => 'lus',
'status_total' => 'total',
'status_unread' => 'non lus',
'title' => 'Statistiques',
'top_feed' => 'Les dix plus gros flux',
),
'system' => array(
'_' => 'Configuration du système',
'auto-update-url' => 'URL du service de mise à jour',
'instance-name' => 'Nom de linstance',
'max-categories' => 'Limite de catégories par utilisateur',
'max-feeds' => 'Limite de flux par utilisateur',
'registration' => array(
'help' => 'Un chiffre de 0 signifie que lon peut créer un nombre infini de comptes',
'number' => 'Nombre max de comptes',
),
),
'update' => array(
'_' => 'Système de mise à jour',
'apply' => 'Appliquer la mise à jour',
'check' => 'Vérifier les mises à jour',
'current_version' => 'Votre version actuelle de FreshRSS est la %s.',
'last' => 'Dernière vérification : %s',
'none' => 'Aucune mise à jour à appliquer',
'title' => 'Système de mise à jour',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
'create' => 'Créer un nouvel utilisateur',
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Langue',
'number' => '%d compte a déjà été créé',
'numbers' => '%d comptes ont déjà été créés',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_format' => '7 caractères minimum',
'title' => 'Gestion des utilisateurs',
'user_list' => 'Liste des utilisateurs',
'username' => 'Nom dutilisateur',
'users' => 'Utilisateurs',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Archivage',
'advanced' => 'Avancé',
'delete_after' => 'Supprimer les articles après',
'help' => 'Dautres options sont disponibles dans la configuration individuelle des flux.',
'keep_history_by_feed' => 'Nombre minimum darticles à conserver par flux',
'optimize' => 'Optimiser la base de données',
'optimize_help' => 'À faire de temps en temps pour réduire la taille de la BDD',
'purge_now' => 'Purger maintenant',
'title' => 'Archivage',
'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que',
),
'display' => array(
'_' => 'Affichage',
'icon' => array(
'bottom_line' => 'Ligne du bas',
'entry' => 'Icônes darticle',
'publication_date' => 'Date de publication',
'related_tags' => 'Tags associés',
'sharing' => 'Partage',
'top_line' => 'Ligne du haut',
),
'language' => 'Langue',
'notif_html5' => array(
'seconds' => 'secondes (0 signifie aucun timeout)',
'timeout' => 'Temps daffichage de la notification HTML5',
),
'theme' => 'Thème',
'title' => 'Affichage',
'width' => array(
'content' => 'Largeur du contenu',
'large' => 'Large',
'medium' => 'Moyenne',
'no_limit' => 'Pas de limite',
'thin' => 'Fine',
),
),
'query' => array(
'_' => 'Filtres utilisateurs',
'deprecated' => 'Ce filtre nest plus valide. La catégorie ou le flux concerné a été supprimé.',
'filter' => 'Filtres appliqués :',
'get_all' => 'Afficher tous les articles',
'get_category' => 'Afficher la catégorie "%s"',
'get_favorite' => 'Afficher les articles favoris',
'get_feed' => 'Afficher le flux "%s"',
'no_filter' => 'Aucun filtre appliqué',
'none' => 'Vous navez pas encore créé de filtre.',
'number' => 'Filtre n°%d',
'order_asc' => 'Afficher les articles les plus anciens en premier',
'order_desc' => 'Afficher les articles les plus récents en premier',
'search' => 'Recherche de "%s"',
'state_0' => 'Afficher tous les articles',
'state_1' => 'Afficher les articles lus',
'state_2' => 'Afficher les articles non lus',
'state_3' => 'Afficher tous les articles',
'state_4' => 'Afficher les articles favoris',
'state_5' => 'Afficher les articles lus et favoris',
'state_6' => 'Afficher les articles non lus et favoris',
'state_7' => 'Afficher les articles favoris',
'state_8' => 'Afficher les articles non favoris',
'state_9' => 'Afficher les articles lus et non favoris',
'state_10' => 'Afficher les articles non lus et non favoris',
'state_11' => 'Afficher les articles non favoris',
'state_12' => 'Afficher tous les articles',
'state_13' => 'Afficher les articles lus',
'state_14' => 'Afficher les articles non lus',
'state_15' => 'Afficher tous les articles',
'title' => 'Filtres utilisateurs',
),
'profile' => array(
'_' => 'Gestion du profil',
'delete' => array(
'_' => 'Suppression du compte',
'warn' => 'Le compte et toutes les données associées vont être supprimées.',
),
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_format' => '7 caractères minimum',
'title' => 'Profil',
),
'reading' => array(
'_' => 'Lecture',
'after_onread' => 'Après “marquer tout comme lu”,',
'articles_per_page' => 'Nombre darticles par page',
'auto_load_more' => 'Charger les articles suivants en bas de page',
'auto_remove_article' => 'Cacher les articles après lecture',
'mark_updated_article_unread' => 'Marquer les articles mis à jour comme non-lus',
'confirm_enabled' => 'Afficher une confirmation lors des actions “marquer tout comme lu”',
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
'display_categories_unfolded' => 'Afficher les catégories pliées par défaut',
'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (ne fonctionne pas avec la configuration “Afficher tous les articles”)',
'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images',
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
'number_divided_when_reader' => 'Divisé par 2 dans la vue de lecture.',
'read' => array(
'article_open_on_website' => 'lorsque larticle est ouvert sur le site dorigine',
'article_viewed' => 'lorsque larticle est affiché',
'scroll' => 'au défilement de la page',
'upon_reception' => 'dès la réception du nouvel article',
'when' => 'Marquer un article comme lu…',
),
'show' => array(
'_' => 'Articles à afficher',
'adaptive' => 'Adapter laffichage',
'all_articles' => 'Afficher tous les articles',
'unread' => 'Afficher les non lus',
),
'sort' => array(
'_' => 'Ordre de tri',
'newer_first' => 'Plus récents en premier',
'older_first' => 'Plus anciens en premier',
),
'sticky_post' => 'Aligner larticle en haut quand il est ouvert',
'title' => 'Lecture',
'view' => array(
'default' => 'Vue par défaut',
'global' => 'Vue globale',
'normal' => 'Vue normale',
'reader' => 'Vue lecture',
),
),
'sharing' => array(
'_' => 'Partage',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Courriel',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'Plus dinformations',
'print' => 'Print',
'shaarli' => 'Shaarli',
'share_name' => 'Nom du partage à afficher',
'share_url' => 'URL du partage à utiliser',
'title' => 'Partage',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Raccourcis',
'article_action' => 'Actions associées à larticle courant',
'auto_share' => 'Partager',
'auto_share_help' => 'Sil ny a quun mode de partage, celui-ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
'close_dropdown' => 'Fermer les menus',
'collapse_article' => 'Refermer',
'first_article' => 'Passer au premier article',
'focus_search' => 'Accéder à la recherche',
'help' => 'Afficher la documentation',
'javascript' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.',
'last_article' => 'Passer au dernier article',
'load_more' => 'Charger plus darticles',
'mark_read' => 'Marquer comme lu',
'mark_favorite' => 'Mettre en favori',
'navigation' => 'Navigation',
'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.',
'next_article' => 'Passer à larticle suivant',
'other_action' => 'Autres actions',
'previous_article' => 'Passer à larticle précédent',
'see_on_website' => 'Voir sur le site dorigine',
'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
'title' => 'Raccourcis',
'user_filter' => 'Accéder aux filtres utilisateur',
'user_filter_help' => 'Sil ny a quun filtre utilisateur, celui-ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
'current' => 'Utilisateur actuel',
'is_admin' => 'est administrateur',
'users' => 'Utilisateurs',
),
);

View file

@ -1,110 +0,0 @@
<?php
return array(
'admin' => array(
'optimization_complete' => 'Optimisation terminée.',
),
'access' => array(
'denied' => 'Vous navez pas le droit daccéder à cette page !',
'not_found' => 'La page que vous cherchez nexiste pas !',
),
'auth' => array(
'form' => array(
'not_set' => 'Un problème est survenu lors de la configuration de votre système dauthentification. Veuillez réessayer plus tard.',
'set' => 'Le formulaire est désormais votre système dauthentification.',
),
'login' => array(
'invalid' => 'Lidentifiant est invalide',
'success' => 'Vous êtes désormais connecté',
),
'logout' => array(
'success' => 'Vous avez été déconnecté',
),
'no_password_set' => 'Aucun mot de passe administrateur na été précisé. Cette fonctionnalité nest pas disponible.',
'not_persona' => 'Seul le système dauthentification Persona peut être réinitialisé.',
),
'conf' => array(
'error' => 'Une erreur est survenue durant la sauvegarde de la configuration',
'query_created' => 'Le filtre "%s" a bien été créé.',
'shortcuts_updated' => 'Les raccourcis ont été mis à jour.',
'updated' => 'La configuration a été mise à jour',
),
'extensions' => array(
'already_enabled' => '%s est déjà activée',
'disable' => array(
'ko' => '%s ne peut pas être désactivée. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.',
'ok' => '%s est désormais désactivée',
),
'enable' => array(
'ko' => '%s ne peut pas être activée. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.',
'ok' => '%s est désormais activée',
),
'no_access' => 'Vous navez aucun accès sur %s',
'not_enabled' => '%s nest pas encore activée',
'not_found' => '%s nexiste pas',
),
'import_export' => array(
'export_no_zip_extension' => 'Lextension Zip nest pas présente sur votre serveur. Veuillez essayer dexporter les fichiers un par un.',
'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.',
'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.',
'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé !',
'no_zip_extension' => 'Lextension Zip nest pas présente sur votre serveur.',
'zip_error' => 'Une erreur est survenue durant limport du fichier Zip.',
),
'sub' => array(
'actualize' => 'Actualiser',
'category' => array(
'created' => 'La catégorie %s a été créée.',
'deleted' => 'La catégorie a été supprimée.',
'emptied' => 'La catégorie a été vidée.',
'error' => 'La catégorie na pas pu être modifiée',
'name_exists' => 'Une catégorie possède déjà ce nom.',
'no_id' => 'Vous devez préciser lid de la catégorie.',
'no_name' => 'Vous devez préciser un nom pour la catégorie.',
'not_delete_default' => 'Vous ne pouvez pas supprimer la catégorie par défaut !',
'not_exist' => 'Cette catégorie nexiste pas !',
'over_max' => 'Vous avez atteint votre limite de catégories (%d)',
'updated' => 'La catégorie a été mise à jour.',
),
'feed' => array(
'actualized' => '<em>%s</em> a été mis à jour.',
'actualizeds' => 'Les flux ont été mis à jour.',
'added' => 'Le flux <em>%s</em> a bien été ajouté.',
'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>',
'deleted' => 'Le flux a été supprimé.',
'error' => 'Une erreur est survenue',
'internal_problem' => '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.',
'marked_read' => 'Les flux ont été marqués comme lus.',
'n_actualized' => '%d flux ont été mis à jour.',
'n_entries_deleted' => '%d articles ont été supprimés.',
'no_refresh' => 'Il ny a aucun flux à actualiser…',
'not_added' => '<em>%s</em> na pas pu être ajouté.',
'over_max' => 'Vous avez atteint votre limite de flux (%d)',
'updated' => 'Le flux a été mis à jour',
),
'purge_completed' => 'Purge effectuée (%d articles supprimés).',
),
'update' => array(
'can_apply' => 'FreshRSS va maintenant être mis à jour vers la <strong>version %s</strong>.',
'error' => 'La mise à jour a rencontré un problème : %s',
'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable décrire dedans',
'finished' => 'La mise à jour est terminée !',
'none' => 'Aucune mise à jour à appliquer',
'server_not_found' => 'Le serveur de mise à jour na pas été trouvé. [%s]',
),
'user' => array(
'created' => array(
'_' => 'Lutilisateur %s a été créé.',
'error' => 'Lutilisateur %s ne peut pas être créé.',
),
'deleted' => array(
'_' => 'Lutilisateur %s a été supprimé.',
'error' => 'Lutilisateur %s ne peut pas être supprimé.',
),
),
'profile' => array(
'error' => 'Votre profil na pas pu être mis à jour',
'updated' => 'Votre profil a été mis à jour',
),
);

View file

@ -1,185 +0,0 @@
<?php
return array(
'action' => array(
'actualize' => 'Actualiser',
'back_to_rss_feeds' => '← Retour à vos flux RSS',
'cancel' => 'Annuler',
'create' => 'Créer',
'disable' => 'Désactiver',
'empty' => 'Vider',
'enable' => 'Activer',
'export' => 'Exporter',
'filter' => 'Filtrer',
'import' => 'Importer',
'manage' => 'Gérer',
'mark_read' => 'Marquer comme lu',
'mark_favorite' => 'Mettre en favori',
'remove' => 'Supprimer',
'see_website' => 'Voir le site',
'submit' => 'Valider',
'truncate' => 'Supprimer tous les articles',
),
'auth' => array(
'email' => 'Adresse courriel',
'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>',
'login' => 'Connexion',
'login_persona' => 'Connexion avec Persona',
'login_persona_problem' => 'Problème de connexion à Persona ?',
'logout' => 'Déconnexion',
'password' => array(
'_' => 'Mot de passe',
'format' => '<small>7 caractères minimum</small>',
),
'registration' => array(
'_' => 'Nouveau compte',
'ask' => 'Créer un compte ?',
'title' => 'Création de compte',
),
'reset' => 'Réinitialisation de lauthentification',
'username' => array(
'_' => 'Nom dutilisateur',
'admin' => 'Nom dutilisateur administrateur',
'format' => '<small>16 caractères alphanumériques maximum</small>',
),
'will_reset' => 'Le système dauthentification va être réinitialisé : un formulaire sera utilisé à la place de Persona.',
),
'date' => array(
'Apr' => '\\a\\v\\r\\i\\l',
'Aug' => '\\a\\o\\û\\t',
'Dec' => '\\d\\é\\c\\e\\m\\b\\r\\e',
'Feb' => '\\f\\é\\v\\r\\i\\e\\r',
'Jan' => '\\j\\a\\n\\v\\i\\e\\r',
'Jul' => '\\j\\u\\i\\l\\l\\e\\t',
'Jun' => '\\j\\u\\i\\n',
'Mar' => '\\m\\a\\r\\s',
'May' => '\\m\\a\\i',
'Nov' => '\\n\\o\\v\\e\\m\\b\\r\\e',
'Oct' => '\\o\\c\\t\\o\\b\\r\\e',
'Sep' => '\\s\\e\\p\\t\\e\\m\\b\\r\\e',
'apr' => 'avr.',
'april' => 'avril',
'aug' => 'août',
'august' => 'août',
'before_yesterday' => 'À partir davant-hier',
'dec' => 'déc.',
'december' => 'décembre',
'feb' => 'fév.',
'february' => 'février',
'format_date' => 'j %s Y',
'format_date_hour' => 'j %s Y \\à H\\:i',
'fri' => 'ven.',
'jan' => 'jan.',
'january' => 'janvier',
'jul' => 'jui.',
'july' => 'juillet',
'jun' => 'juin',
'june' => 'juin',
'last_3_month' => 'Depuis les trois derniers mois',
'last_6_month' => 'Depuis les six derniers mois',
'last_month' => 'Depuis le mois dernier',
'last_week' => 'Depuis la semaine dernière',
'last_year' => 'Depuis lannée dernière',
'mar' => 'mar.',
'march' => 'mars',
'may' => 'mai.',
'mon' => 'lun.',
'month' => 'mois',
'nov' => 'nov.',
'november' => 'novembre',
'oct' => 'oct.',
'october' => 'octobre',
'sat' => 'sam.',
'sep' => 'sep.',
'september' => 'septembre',
'sun' => 'dim.',
'thu' => 'jeu.',
'today' => 'Aujourdhui',
'tue' => 'mar.',
'wed' => 'mer.',
'yesterday' => 'Hier',
),
'freshrss' => array(
'_' => 'FreshRSS',
'about' => 'À propos de FreshRSS',
),
'js' => array(
'category_empty' => 'Catégorie vide',
'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !',
'confirm_action_feed_cat' => 'Êtes-vous sûr(e) de vouloir continuer ? Vous perdrez les favoris et les filtres associés. Cette action ne peut être annulée !',
'feedback' => array(
'body_new_articles' => 'Il y a %%d nouveaux articles à lire sur FreshRSS.',
'request_failed' => 'Une requête a échoué, cela peut être dû à des problèmes de connexion à Internet.',
'title_new_articles' => 'FreshRSS : nouveaux articles !',
),
'new_article' => 'Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.',
'should_be_activated' => 'Le JavaScript doit être activé.',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'À propos',
'admin' => 'Administration',
'archiving' => 'Archivage',
'authentication' => 'Authentification',
'check_install' => 'Vérification de linstallation',
'configuration' => 'Configuration',
'display' => 'Affichage',
'extensions' => 'Extensions',
'logs' => 'Logs',
'queries' => 'Filtres utilisateurs',
'reading' => 'Lecture',
'search' => 'Rechercher des mots ou des #tags',
'sharing' => 'Partage',
'shortcuts' => 'Raccourcis',
'stats' => 'Statistiques',
'system' => 'Configuration du système',
'update' => 'Mise à jour',
'user_management' => 'Gestion des utilisateurs',
'user_profile' => 'Profil',
),
'pagination' => array(
'first' => 'Début',
'last' => 'Fin',
'load_more' => 'Charger plus darticles',
'mark_all_read' => 'Tout marquer comme lu',
'next' => 'Suivant',
'nothing_to_load' => 'Fin des articles',
'previous' => 'Précédent',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Courriel',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Imprimer',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Attention !',
'blank_to_disable' => 'Laissez vide pour désactiver',
'by_author' => 'Par <em>%s</em>',
'by_default' => 'Par défaut',
'damn' => 'Arf !',
'default_category' => 'Sans catégorie',
'no' => 'Non',
'not_applicable' => 'Non disponible',
'ok' => 'Ok !',
'or' => 'ou',
'yes' => 'Oui',
),
);

View file

@ -1,61 +0,0 @@
<?php
return array(
'about' => array(
'_' => 'À propos',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Rapports de bugs',
'credits' => 'Crédits',
'credits_content' => 'Des éléments de design sont issus du <a href="http://twitter.github.io/bootstrap/">projet Bootstrap</a> bien que FreshRSS nutilise pas ce framework. Les <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icônes</a> sont issues du <a href="https://www.gnome.org/">projet GNOME</a>. La police <em>Open Sans</em> utilisée a été créée par <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Les favicons sont récupérés grâce au site <a href="https://getfavicon.appspot.com/">getFavicon</a>. FreshRSS repose sur <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à limage de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://projet.idleman.fr/leed/">Leed</a>. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">sur Github</a>',
'license' => 'Licence',
'project_website' => 'Site du projet',
'title' => 'À propos',
'version' => 'Version',
'website' => 'Site Internet',
),
'feed' => array(
'add' => 'Vous pouvez ajouter des flux.',
'empty' => 'Il ny a aucun article à afficher.',
'rss_of' => 'Flux RSS de %s',
'title' => 'Vos flux RSS',
'title_global' => 'Vue globale',
'title_fav' => 'Vos favoris',
),
'log' => array(
'_' => 'Logs',
'clear' => 'Effacer les logs',
'empty' => 'Les logs sont vides.',
'title' => 'Logs',
),
'menu' => array(
'about' => 'À propos de FreshRSS',
'add_query' => 'Créer un filtre',
'before_one_day' => 'Antérieurs à 1 jour',
'before_one_week' => 'Antérieurs à 1 semaine',
'favorites' => 'Favoris (%s)',
'global_view' => 'Vue globale',
'main_stream' => 'Flux principal',
'mark_all_read' => 'Tout marquer comme lu',
'mark_cat_read' => 'Marquer la catégorie comme lue',
'mark_feed_read' => 'Marquer le flux comme lu',
'newer_first' => 'Plus récents en premier',
'non-starred' => 'Afficher tout sauf les favoris',
'normal_view' => 'Vue normale',
'older_first' => 'Plus anciens en premier',
'queries' => 'Filtres utilisateurs',
'read' => 'Afficher les lus',
'reader_view' => 'Vue lecture',
'rss_view' => 'Flux RSS',
'search_short' => 'Rechercher',
'starred' => 'Afficher les favoris',
'stats' => 'Statistiques',
'subscription' => 'Gestion des abonnements',
'unread' => 'Afficher les non lus',
),
'share' => 'Partager',
'tag' => array(
'related' => 'Tags associés',
),
);

View file

@ -1,121 +0,0 @@
<?php
return array(
'action' => array(
'finish' => 'Terminer linstallation',
'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à létape suivante.',
'keep_install' => 'Garder lancienne configuration',
'next_step' => 'Passer à létape suivante',
'reinstall' => 'Réinstaller FreshRSS',
),
'auth' => array(
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Formulaire (traditionnel, requiert JavaScript)',
'http' => 'HTTP (pour utilisateurs avancés avec HTTPS)',
'none' => 'Aucune (dangereux)',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_format' => '7 caractères minimum',
'persona' => 'Mozilla Persona (moderne, requiert JavaScript)',
'type' => 'Méthode dauthentification',
),
'bdd' => array(
'_' => 'Base de données',
'conf' => array(
'_' => 'Configuration de la base de données',
'ko' => 'Vérifiez les informations daccès à la base de données.',
'ok' => 'La configuration de la base de données a été enregistrée.',
),
'host' => 'Hôte',
'password' => 'Mot de passe',
'prefix' => 'Préfixe des tables',
'type' => 'Type de base de données',
'username' => 'Nom dutilisateur',
),
'check' => array(
'_' => 'Vérifications',
'already_installed' => 'FreshRSS semble avoir déjà été installé !',
'cache' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/cache</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de cache sont bons.',
),
'ctype' => array(
'nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype).',
'ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype).',
),
'curl' => array(
'nok' => 'Vous ne disposez pas de cURL (paquet php5-curl).',
'ok' => 'Vous disposez de cURL.',
),
'data' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de data sont bons.',
),
'dom' => array(
'nok' => 'Il manque une librairie pour parcourir le DOM.',
'ok' => 'Vous disposez du nécessaire pour parcourir le DOM.',
),
'favicons' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire des favicons sont bons.',
),
'http_referer' => array(
'nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.',
'ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.',
),
'json' => array(
'nok' => 'Il manque une librairie recommandée pour JSON.',
'ok' => 'Vouz disposez de la librairie recommandée pour JSON.',
),
'minz' => array(
'nok' => 'Vous ne disposez pas de la librairie Minz.',
'ok' => 'Vous disposez du framework Minz',
),
'pcre' => array(
'nok' => 'Il manque une librairie pour les expressions régulières (php-pcre).',
'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
),
'pdo' => array(
'nok' => 'Vous ne disposez pas de PDO ou dun des drivers supportés (pdo_mysql, pdo_sqlite).',
'ok' => 'Vous disposez de PDO et dau moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/persona</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons.',
),
'php' => array(
'nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s.',
'ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS.',
),
'users' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/users</em>. Le serveur HTTP doit être capable décrire dedans',
'ok' => 'Les droits sur le répertoire des utilisateurs sont bons.',
),
'xml' => array(
'nok' => 'Il manque une librairie requise pour XML.',
'ok' => 'Vouz disposez de la librairie requise pour XML.',
),
),
'conf' => array(
'_' => 'Configuration générale',
'ok' => 'La configuration générale a été enregistrée.',
),
'congratulations' => 'Félicitations !',
'default_user' => 'Nom de lutilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>',
'delete_articles_after' => 'Supprimer les articles après',
'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à létape suivante.',
'javascript_is_better' => 'FreshRSS est plus agréable à utiliser avec JavaScript activé',
'js' => array(
'confirm_reinstall' => 'Réinstaller FreshRSS vous fera perdre la configuration précédente. Êtes-vous sûr de vouloir continuer ?',
),
'language' => array(
'_' => 'Langue',
'choose' => 'Choisissez la langue pour FreshRSS',
'defined' => 'La langue a bien été définie.',
),
'not_deleted' => 'Quelque chose sest mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
'ok' => 'Linstallation sest bien passée.',
'step' => 'étape %d',
'steps' => 'Étapes',
'title' => 'Installation · FreshRSS',
'this_is_the_end' => 'This is the end',
);

View file

@ -1,62 +0,0 @@
<?php
return array(
'category' => array(
'_' => 'Catégorie',
'add' => 'Ajouter une catégorie',
'empty' => 'Catégorie vide',
'new' => 'Nouvelle catégorie',
),
'feed' => array(
'add' => 'Ajouter un flux RSS',
'advanced' => 'Avancé',
'archiving' => 'Archivage',
'auth' => array(
'configuration' => 'Identification',
'help' => 'La connexion permet daccéder aux flux protégés par une authentification HTTP.',
'http' => 'Authentification HTTP',
'password' => 'Mot de passe HTTP',
'username' => 'Identifiant HTTP',
),
'css_help' => 'Permet de récupérer les flux tronqués (attention, demande plus de temps !)',
'css_path' => 'Sélecteur CSS des articles sur le site dorigine',
'description' => 'Description',
'empty' => 'Ce flux est vide. Veuillez vérifier quil est toujours maintenu.',
'error' => 'Ce flux a rencontré un problème. Veuillez vérifier quil est toujours accessible puis actualisez-le.',
'in_main_stream' => 'Afficher dans le flux principal',
'informations' => 'Informations',
'keep_history' => 'Nombre minimum darticles à conserver',
'moved_category_deleted' => 'Lors de la suppression dune catégorie, ses flux seront automatiquement classés dans <em>%s</em>.',
'no_selected' => 'Aucun flux sélectionné.',
'number_entries' => '%d articles',
'stats' => 'Statistiques',
'think_to_add' => 'Vous pouvez ajouter des flux.',
'title' => 'Titre',
'title_add' => 'Ajouter un flux RSS',
'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que',
'url' => 'URL du flux',
'validator' => 'Vérifier la validité du flux',
'website' => 'URL du site',
'pubsubhubbub' => 'Notification instantanée par PubSubHubbub',
),
'import_export' => array(
'export' => 'Exporter',
'export_opml' => 'Exporter la liste des flux (OPML)',
'export_starred' => 'Exporter les favoris',
'feed_list' => 'Liste des articles de %s',
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)',
'import' => 'Importer',
'starred_list' => 'Liste des articles favoris',
'title' => 'Importer / exporter',
),
'menu' => array(
'bookmark' => 'Sabonner (bookmark FreshRSS)',
'import_export' => 'Importer / exporter',
'subscription_management' => 'Gestion des abonnements',
),
'title' => array(
'_' => 'Gestion des abonnements',
'feed_management' => 'Gestion des flux RSS',
),
);

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Consenti la lettura agli utenti anonimi degli articoli dell utente predefinito (%s)',
'allow_anonymous_refresh' => 'Consenti agli utenti anonimi di aggiornare gli articoli',
'api_enabled' => 'Consenti le <abbr>API</abbr> di accesso <small>(richiesto per le app mobili)</small>',
'form' => 'Web form (tradizionale, richiede JavaScript)',
'http' => 'HTTP (per gli utenti avanzati con HTTPS)',
'none' => 'Nessuno (pericoloso)',
'persona' => 'Mozilla Persona (moderno, richiede JavaScript)',
'title' => 'Autenticazione',
'title_reset' => 'Reset autenticazione',
'token' => 'Token di autenticazione',
'token_help' => 'Consenti accesso agli RSS dell utente predefinito senza autenticazione:',
'type' => 'Metodo di autenticazione',
'unsafe_autologin' => 'Consenti accesso automatico non sicuro usando il formato: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/cache</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella della cache sono corretti.',
),
'categories' => array(
'nok' => 'La tabella delle categorie ha una configurazione errata.',
'ok' => 'Tabella delle categorie OK.',
),
'connection' => array(
'nok' => 'La connessione al database non può essere stabilita.',
'ok' => 'Connessione al database OK',
),
'ctype' => array(
'nok' => 'Manca una libreria richiesta per il controllo dei caratteri (php-ctype).',
'ok' => 'Libreria richiesta per il controllo dei caratteri presente (ctype).',
),
'curl' => array(
'nok' => 'Manca il supporto per cURL (pacchetto php5-curl).',
'ok' => 'Estensione cURL presente.',
),
'data' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella data sono corretti.',
),
'database' => 'Installazione database',
'dom' => array(
'nok' => 'Manca una libreria richiesta per leggere DOM (pacchetto php-xml).',
'ok' => 'Libreria richiesta per leggere DOM presente.',
),
'entries' => array(
'nok' => 'La tabella Entry ha una configurazione errata.',
'ok' => 'Tabella Entry OK.',
),
'favicons' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/favicons</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella favicons sono corretti.',
),
'feeds' => array(
'nok' => 'La tabella Feed ha una configurazione errata.',
'ok' => 'Tabella Feed OK.',
),
'files' => 'Installazione files',
'json' => array(
'nok' => 'Manca il supoorto a JSON (pacchetto php5-json).',
'ok' => 'Estensione JSON presente.',
),
'minz' => array(
'nok' => 'Manca il framework Minz.',
'ok' => 'Framework Minz presente.',
),
'pcre' => array(
'nok' => 'Manca una libreria richiesta per le regular expressions (php-pcre).',
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/persona</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella Mozilla Persona sono corretti.',
),
'php' => array(
'_' => 'Installazione PHP',
'nok' => 'Versione PHP %s FreshRSS richiede almeno la versione %s.',
'ok' => 'Versione PHP %s, compatibile con FreshRSS.',
),
'tables' => array(
'nok' => 'Rilevate tabelle mancanti nel database.',
'ok' => 'Tutte le tabelle sono presenti nel database.',
),
'title' => 'Verifica installazione',
'tokens' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/tokens</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella tokens sono corretti.',
),
'users' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/users</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella users sono corretti.',
),
'zip' => array(
'nok' => 'Manca estensione ZIP (pacchetto php5-zip).',
'ok' => 'Estensione ZIP presente.',
),
),
'extensions' => array(
'disabled' => 'Disabilitata',
'empty_list' => 'Non ci sono estensioni installate',
'enabled' => 'Abilitata',
'no_configure_view' => 'Questa estensioni non può essere configurata.',
'system' => array(
'_' => 'Estensioni di sistema',
'no_rights' => 'Estensione di sistema (non hai i permessi su questo tipo)',
),
'title' => 'Estensioni',
'user' => 'Estensioni utente',
),
'stats' => array(
'_' => 'Statistiche',
'all_feeds' => 'Tutti i feeds',
'category' => 'Categoria',
'entry_count' => 'Articoli',
'entry_per_category' => 'Articoli per categoria',
'entry_per_day' => 'Articoli per giorno (ultimi 30 giorni)',
'entry_per_day_of_week' => 'Per giorno della settimana (media: %.2f articoli)',
'entry_per_hour' => 'Per ora (media: %.2f articoli)',
'entry_per_month' => 'Per mese (media: %.2f articoli)',
'entry_repartition' => 'Ripartizione contenuti',
'feed' => 'Feed',
'feed_per_category' => 'Feeds per categoria',
'idle' => 'Feeds non aggiornati',
'main' => 'Statistiche principali',
'main_stream' => 'Flusso principale',
'menu' => array(
'idle' => 'Feeds non aggiornati',
'main' => 'Statistiche principali',
'repartition' => 'Ripartizione articoli',
),
'no_idle' => 'Non ci sono feed non aggiornati',
'number_entries' => '%d articoli',
'percent_of_total' => '%% del totale',
'repartition' => 'Ripartizione articoli',
'status_favorites' => 'Preferiti',
'status_read' => 'Letti',
'status_total' => 'Totale',
'status_unread' => 'Non letti',
'title' => 'Statistiche',
'top_feed' => 'I migliori 10 feeds',
),
'system' => array(
'_' => 'Configurazione di sistema',
'auto-update-url' => 'Auto-update server URL', // @todo translate
'instance-name' => 'Nome istanza',
'max-categories' => 'Limite categorie per utente',
'max-feeds' => 'Limite feeds per utente',
'registration' => array(
'help' => '0 significa che non esiste limite sui profili',
'number' => 'Numero massimo di profili',
),
),
'update' => array(
'_' => 'Aggiornamento sistema',
'apply' => 'Applica',
'check' => 'Controlla la presenza di nuovi aggiornamenti',
'current_version' => 'FreshRSS versione %s.',
'last' => 'Ultima verifica: %s',
'none' => 'Nessun aggiornamento da applicare',
'title' => 'Aggiorna sistema',
),
'user' => array(
'articles_and_size' => '%s articoli (%s)',
'create' => 'Crea nuovo utente',
'email_persona' => 'Indirizzo mail<br /><small>(Login <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Lingua',
'number' => ' %d profilo utente creato',
'numbers' => 'Sono presenti %d profili utente',
'password_form' => 'Password<br /><small>(per il login classico)</small>',
'password_format' => 'Almeno 7 caratteri',
'title' => 'Gestione utenti',
'user_list' => 'Lista utenti',
'username' => 'Nome utente',
'users' => 'Utenti',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Archiviazione',
'advanced' => 'Avanzate',
'delete_after' => 'Rimuovi articoli dopo',
'help' => 'Altre opzioni sono disponibili nelle impostazioni dei singoli feed',
'keep_history_by_feed' => 'Numero minimo di articoli da mantenere per feed',
'optimize' => 'Ottimizza database',
'optimize_help' => 'Da fare occasionalmente per ridurre le dimensioni del database',
'purge_now' => 'Cancella ora',
'title' => 'Archiviazione',
'ttl' => 'Non effettuare aggiornamenti per più di',
),
'display' => array(
'_' => 'Visualizzazione',
'icon' => array(
'bottom_line' => 'Barra in fondo',
'entry' => 'Icone degli articoli',
'publication_date' => 'Data di pubblicazione',
'related_tags' => 'Tags correlati',
'sharing' => 'Condivisione',
'top_line' => 'Barra in alto',
),
'language' => 'Lingua',
'notif_html5' => array(
'seconds' => 'secondi (0 significa nessun timeout)',
'timeout' => 'Notifica timeout HTML5',
),
'theme' => 'Tema',
'title' => 'Visualizzazione',
'width' => array(
'content' => 'Larghezza contenuto',
'large' => 'Largo',
'medium' => 'Medio',
'no_limit' => 'Nessun limite',
'thin' => 'Stretto',
),
),
'query' => array(
'_' => 'Ricerche personali',
'deprecated' => 'Questa query non è più valida. La categoria o il feed di riferimento non stati cancellati.',
'filter' => 'Filtro applicato:',
'get_all' => 'Mostra tutti gli articoli',
'get_category' => 'Mostra la categoria "%s" ',
'get_favorite' => 'Mostra articoli preferiti',
'get_feed' => 'Mostra feed "%s" ',
'no_filter' => 'Nessun filtro',
'none' => 'Non hai creato nessuna ricerca personale.',
'number' => 'Ricerca n°%d',
'order_asc' => 'Mostra prima gli articoli più vecchi',
'order_desc' => 'Mostra prima gli articoli più nuovi',
'search' => 'Cerca per "%s"',
'state_0' => 'Mostra tutti gli articoli',
'state_1' => 'Mostra gli articoli letti',
'state_2' => 'Mostra gli articoli non letti',
'state_3' => 'Mostra tutti gli articoli',
'state_4' => 'Mostra gli articoli preferiti',
'state_5' => 'Mostra gli articoli preferiti letti',
'state_6' => 'Mostra gli articoli preferiti non letti',
'state_7' => 'Mostra gli articoli preferiti',
'state_8' => 'Non mostrare gli articoli preferiti',
'state_9' => 'Mostra gli articoli letti non preferiti',
'state_10' => 'Mostra gli articoli non letti e non preferiti',
'state_11' => 'Non mostrare gli articoli preferiti',
'state_12' => 'Mostra tutti gli articoli',
'state_13' => 'Mostra gli articoli letti',
'state_14' => 'Mostra gli articoli non letti',
'state_15' => 'Mostra tutti gli articoli',
'title' => 'Ricerche personali',
),
'profile' => array(
'_' => 'Gestione profili',
'delete' => array(
'_' => 'Cancellazione account',
'warn' => 'Il tuo account e tutti i dati associati saranno cancellati.',
),
'email_persona' => 'Indirizzo email<br /><small>(Login <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(e.g., per applicazioni mobili)</small>',
'password_form' => 'Password<br /><small>(per il login classico)</small>',
'password_format' => 'Almeno 7 caratteri',
'title' => 'Profilo',
),
'reading' => array(
'_' => 'Lettura',
'after_onread' => 'Dopo “segna tutto come letto”,',
'articles_per_page' => 'Numero di articoli per pagina',
'auto_load_more' => 'Carica articoli successivi a fondo pagina',
'auto_remove_article' => 'Nascondi articoli dopo la lettura',
'mark_updated_article_unread' => 'Segna articoli aggiornati come non letti',
'confirm_enabled' => 'Mostra una conferma per “segna tutto come letto”',
'display_articles_unfolded' => 'Mostra articoli aperti di predefinito',
'display_categories_unfolded' => 'Mostra categorie aperte di predefinito',
'hide_read_feeds' => 'Nascondi categorie e feeds con articoli già letti (non funziona se “Mostra tutti gli articoli” è selezionato)',
'img_with_lazyload' => 'Usa la modalità "caricamento ritardato" per le immagini',
'jump_next' => 'Salta al successivo feed o categoria non letto',
'number_divided_when_reader' => 'Diviso 2 nella modalità di lettura.',
'read' => array(
'article_open_on_website' => 'Quando un articolo è aperto nel suo sito di origine',
'article_viewed' => 'Quando un articolo viene letto',
'scroll' => 'Scorrendo la pagina',
'upon_reception' => 'Alla ricezione del contenuto',
'when' => 'Segna articoli come letti…',
),
'show' => array(
'_' => 'Articoli da visualizzare',
'adaptive' => 'Adatta visualizzazione',
'all_articles' => 'Mostra tutti gli articoli',
'unread' => 'Mostra solo non letti',
),
'sort' => array(
'_' => 'Ordinamento',
'newer_first' => 'Prima i più recenti',
'older_first' => 'Prima i più vecchi',
),
'sticky_post' => 'Blocca il contenuto a inizio pagina quando aperto',
'title' => 'Lettura',
'view' => array(
'default' => 'Visualizzazione predefinita',
'global' => 'Vista globale per categorie',
'normal' => 'Vista elenco',
'reader' => 'Modalità di lettura',
),
),
'sharing' => array(
'_' => 'Condivisione',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'Ulteriori informazioni',
'print' => 'Stampa',
'shaarli' => 'Shaarli',
'share_name' => 'Nome condivisione',
'share_url' => 'URL condivisione',
'title' => 'Condividi',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Comandi tastiera',
'article_action' => 'Azioni sugli articoli',
'auto_share' => 'Condividi',
'auto_share_help' => 'Se è presente un solo servizio di condivisione verrà usato quello, altrimenti usare anche il numero associato.',
'close_dropdown' => 'Chiudi menù',
'collapse_article' => 'Collassa articoli',
'first_article' => 'Salta al primo articolo',
'focus_search' => 'Modulo di ricerca',
'help' => 'Mostra documentazione',
'javascript' => 'JavaScript deve essere abilitato per poter usare i comandi da tastiera',
'last_article' => 'Salta all ultimo articolo',
'load_more' => 'Carica altri articoli',
'mark_read' => 'Segna come letto',
'mark_favorite' => 'Segna come preferito',
'navigation' => 'Navigazione',
'navigation_help' => 'Con il tasto "Shift" i comandi di navigazione verranno applicati ai feeds.<br/>Con il tasto "Alt" i comandi di navigazione verranno applicati alle categorie.',
'next_article' => 'Salta al contenuto successivo',
'other_action' => 'Altre azioni',
'previous_article' => 'Salta al contenuto precedente',
'see_on_website' => 'Vai al sito fonte',
'shift_for_all_read' => '+ <code>shift</code> per segnare tutti gli articoli come letti',
'title' => 'Comandi da tastiera',
'user_filter' => 'Accedi alle ricerche personali',
'user_filter_help' => 'Se è presente una sola ricerca personale verrà usata quella, altrimenti usare anche il numero associato.',
),
'user' => array(
'articles_and_size' => '%s articoli (%s)',
'current' => 'Utente connesso',
'is_admin' => 'è amministratore',
'users' => 'Utenti',
),
);

View file

@ -1,110 +0,0 @@
<?php
return array(
'admin' => array(
'optimization_complete' => 'Ottimizzazione completata',
),
'access' => array(
'denied' => 'Non hai i permessi per accedere a questa pagina',
'not_found' => 'Pagina non disponibile',
),
'auth' => array(
'form' => array(
'not_set' => 'Si è verificato un problema alla configurazione del sistema di autenticazione. Per favore riprova più tardi.',
'set' => 'Sistema di autenticazione tramite Form impostato come predefinito.',
),
'login' => array(
'invalid' => 'Autenticazione non valida',
'success' => 'Autenticazione effettuata',
),
'logout' => array(
'success' => 'Disconnessione effettuata',
),
'no_password_set' => 'Password di amministrazione non impostata. Opzione non disponibile.',
'not_persona' => 'Solo il sistema Mozilla Persona può essere resettato.',
),
'conf' => array(
'error' => 'Si è verificato un errore durante il salvataggio della configurazione',
'query_created' => 'Ricerca "%s" creata.',
'shortcuts_updated' => 'Collegamenti tastiera aggiornati',
'updated' => 'Configurazione aggiornata',
),
'extensions' => array(
'already_enabled' => '%s è già abilitata',
'disable' => array(
'ko' => '%s non può essere disabilitata. <a href="%s">Verifica i logs</a> per dettagli.',
'ok' => '%s è disabilitata',
),
'enable' => array(
'ko' => '%s non può essere abilitata. <a href="%s">Verifica i logs</a> per dettagli.',
'ok' => '%s è ora abilitata',
),
'no_access' => 'Accesso negato a %s',
'not_enabled' => '%s non abilitato',
'not_found' => '%s non disponibile',
),
'import_export' => array(
'export_no_zip_extension' => 'Estensione Zip non presente sul server. Per favore esporta i files singolarmente.',
'feeds_imported' => 'I tuoi feed sono stati importati e saranno aggiornati',
'feeds_imported_with_errors' => 'I tuoi feeds sono stati importati ma si sono verificati alcuni errori',
'file_cannot_be_uploaded' => 'Il file non può essere caricato!',
'no_zip_extension' => 'Estensione Zip non presente sul server.',
'zip_error' => 'Si è verificato un errore importando il file Zip',
),
'sub' => array(
'actualize' => 'Aggiorna',
'category' => array(
'created' => 'Categoria %s creata.',
'deleted' => 'Categoria cancellata',
'emptied' => 'Categoria svuotata',
'error' => 'Categoria non aggiornata',
'name_exists' => 'Categoria già esistente.',
'no_id' => 'Categoria senza ID.',
'no_name' => 'Il nome della categoria non può essere lasciato vuoto.',
'not_delete_default' => 'Non puoi cancellare la categoria predefinita!',
'not_exist' => 'La categoria non esite!',
'over_max' => 'Hai raggiunto il numero limite di categorie (%d)',
'updated' => 'Categoria aggiornata.',
),
'feed' => array(
'actualized' => '<em>%s</em> aggiornato',
'actualizeds' => 'RSS feeds aggiornati',
'added' => 'RSS feed <em>%s</em> aggiunti',
'already_subscribed' => 'Hai già sottoscritto <em>%s</em>',
'deleted' => 'Feed cancellato',
'error' => 'Feed non aggiornato',
'internal_problem' => 'RSS feed non aggiunto. <a href="%s">Verifica i logs</a> per dettagli.',
'invalid_url' => 'URL <em>%s</em> non valido',
'marked_read' => 'Feeds segnati come letti',
'n_actualized' => '%d feeds aggiornati',
'n_entries_deleted' => '%d articoli cancellati',
'no_refresh' => 'Nessun aggiornamento disponibile…',
'not_added' => '<em>%s</em> non può essere aggiunto',
'over_max' => 'Hai raggiunto il numero limite di feed (%d)',
'updated' => 'Feed aggiornato',
),
'purge_completed' => 'Svecchiamento completato (%d articoli cancellati)',
),
'update' => array(
'can_apply' => 'FreshRSS verrà aggiornato alla <strong>versione %s</strong>.',
'error' => 'Il processo di aggiornamento ha riscontrato il seguente errore: %s',
'file_is_nok' => 'Verifica i permessi della cartella <em>%s</em>. Il server HTTP deve avere i permessi per la scrittura ',
'finished' => 'Aggiornamento completato con successo!',
'none' => 'Nessun aggiornamento disponibile',
'server_not_found' => 'Server per aggiornamento non disponibile. [%s]',
),
'user' => array(
'created' => array(
'_' => 'Utente %s creato',
'error' => 'Errore nella creazione utente %s ',
),
'deleted' => array(
'_' => 'Utente %s cancellato',
'error' => 'Utente %s non cancellato',
),
),
'profile' => array(
'error' => 'Il tuo profilo non può essere modificato',
'updated' => 'Il tuo profilo è stato modificato',
),
);

View file

@ -1,185 +0,0 @@
<?php
return array(
'action' => array(
'actualize' => 'Aggiorna',
'back_to_rss_feeds' => '← Indietro',
'cancel' => 'Annulla',
'create' => 'Crea',
'disable' => 'Disabilita',
'empty' => 'Vuoto',
'enable' => 'Abilita',
'export' => 'Esporta',
'filter' => 'Filtra',
'import' => 'Importa',
'manage' => 'Gestisci',
'mark_read' => 'Segna come letto',
'mark_favorite' => 'Segna come preferito',
'remove' => 'Rimuovi',
'see_website' => 'Vai al sito',
'submit' => 'Conferma',
'truncate' => 'Cancella tutti gli articoli',
),
'auth' => array(
'email' => 'Indirizzo email',
'keep_logged_in' => 'Ricorda i dati <small>(1 mese)</small>',
'login' => 'Accedi',
'login_persona' => 'Accedi con Mozilla Persona',
'login_persona_problem' => 'Problemi di connessione con Mozilla Persona?',
'logout' => 'Esci',
'password' => array(
'_' => 'Password',
'format' => '<small>almeno 7 caratteri</small>',
),
'registration' => array(
'_' => 'Nuovo profilo',
'ask' => 'Vuoi creare un nuovo profilo?',
'title' => 'Creazione profilo',
),
'reset' => 'Reset autenticazione',
'username' => array(
'_' => 'Username',
'admin' => 'Username amministratore',
'format' => '<small>massimo 16 caratteri alfanumerici</small>',
),
'will_reset' => 'Il sistema di autenticazione verrà resettato: un form verrà usato per Mozilla Persona.',
),
'date' => array(
'Apr' => '\\A\\p\\r\\i\\l\\e',
'Aug' => '\\A\\g\\o\\s\\t\\o',
'Dec' => '\\D\\i\\c\\e\\m\\b\\r\\e',
'Feb' => '\\F\\e\\b\\b\\r\\a\\i\\o',
'Jan' => '\\G\\e\\n\\u\\a\\i\\o',
'Jul' => '\\L\\u\\g\\l\\i\\o',
'Jun' => '\\G\\i\\u\\g\\n\\o',
'Mar' => '\\M\\a\\r\\z\\o',
'May' => '\\M\\a\\g\\g\\i\\o',
'Nov' => '\\N\\o\\v\\e\\m\\b\\r\\e',
'Oct' => '\\O\\t\\t\\o\\b\\r\\e',
'Sep' => '\\S\\e\\t\\t\\e\\m\\b\\r\\e',
'apr' => 'apr',
'april' => 'Apr',
'aug' => 'aug',
'august' => 'Aug',
'before_yesterday' => 'Meno recenti',
'dec' => 'dec',
'december' => 'Dec',
'feb' => 'feb',
'february' => 'Feb',
'format_date' => 'j\\ %s Y',
'format_date_hour' => 'j\\ %s Y \\o\\r\\e H\\:i',
'fri' => 'Fri',
'jan' => 'jan',
'january' => 'Jan',
'jul' => 'jul',
'july' => 'Jul',
'jun' => 'jun',
'june' => 'Jun',
'last_3_month' => 'Ultimi 3 mesi',
'last_6_month' => 'Ultimi 6 mesi',
'last_month' => 'Ultimo mese',
'last_week' => 'Ultima settimana',
'last_year' => 'Ultimo anno',
'mar' => 'mar',
'march' => 'Mar',
'may' => 'May',
'mon' => 'Mon',
'month' => 'mesi',
'nov' => 'nov',
'november' => 'Nov',
'oct' => 'oct',
'october' => 'Oct',
'sat' => 'Sat',
'sep' => 'sep',
'september' => 'Sep',
'sun' => 'Sun',
'thu' => 'Thu',
'today' => 'Oggi',
'tue' => 'Tue',
'wed' => 'Wed',
'yesterday' => 'Ieri',
),
'freshrss' => array(
'_' => 'Feed RSS Reader',
'about' => 'Informazioni',
),
'js' => array(
'category_empty' => 'Categoria vuota',
'confirm_action' => 'Sei sicuro di voler continuare?',
'confirm_action_feed_cat' => 'Sei sicuro di voler continuare? Verranno persi i preferiti e le ricerche utente correlate!',
'feedback' => array(
'body_new_articles' => 'Ci sono %%d nuovi articoli da leggere.',
'request_failed' => 'Richiesta fallita, probabilmente a causa di problemi di connessione',
'title_new_articles' => 'Feed RSS Reader: nuovi articoli!',
),
'new_article' => 'Sono disponibili nuovi articoli, clicca qui per caricarli.',
'should_be_activated' => 'JavaScript deve essere abilitato',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'Informazioni',
'admin' => 'Amministrazione',
'archiving' => 'Archiviazione',
'authentication' => 'Autenticazione',
'check_install' => 'Installazione',
'configuration' => 'Configurazione',
'display' => 'Visualizzazione',
'extensions' => 'Estensioni',
'logs' => 'Logs',
'queries' => 'Ricerche personali',
'reading' => 'Lettura',
'search' => 'Ricerca parole o #tags',
'sharing' => 'Condivisione',
'shortcuts' => 'Comandi tastiera',
'stats' => 'Statistiche',
'system' => 'Configurazione sistema',
'update' => 'Aggiornamento',
'user_management' => 'Gestione utenti',
'user_profile' => 'Profilo',
),
'pagination' => array(
'first' => 'Prima',
'last' => 'Ultima',
'load_more' => 'Carica altri articoli',
'mark_all_read' => 'Segna tutto come letto',
'next' => 'Successiva',
'nothing_to_load' => 'Non ci sono altri articoli',
'previous' => 'Precedente',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Stampa',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Attenzione!',
'blank_to_disable' => 'Lascia vuoto per disabilitare',
'by_author' => 'di <em>%s</em>',
'by_default' => 'predefinito',
'damn' => 'Ops!',
'default_category' => 'Senza categoria',
'no' => 'No',
'not_applicable' => 'Non disponibile',
'ok' => 'OK!',
'or' => 'o',
'yes' => 'Si',
),
);

View file

@ -1,61 +0,0 @@
<?php
return array(
'about' => array(
'_' => 'Informazioni',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Bugs',
'credits' => 'Crediti',
'credits_content' => 'Alcuni elementi di design provengono da <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> sebbene FreshRSS non usi questo framework. Le <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icone</a> provengono dal progetto <a href="https://www.gnome.org/">GNOME</a>. Il carattere <em>Open Sans</em> è stato creato da <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Le Favicons vengono estratte con le API <a href="https://getfavicon.appspot.com/">getFavicon</a>. FreshRSS è basato su <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.',
'freshrss_description' => 'FreshRSS è un aggregatore di feeds RSS da installare sul proprio host come <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> o <a href="http://projet.idleman.fr/leed/">Leed</a>. Leggero e facile da mantenere pur essendo molto configurabile e potente.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">su Github</a>',
'license' => 'Licenza',
'project_website' => 'Sito del progetto',
'title' => 'Informazioni',
'version' => 'Versione',
'website' => 'Sito',
),
'feed' => array(
'add' => 'Aggiungi un Feed RSS',
'empty' => 'Non ci sono articoli da mostrare.',
'rss_of' => 'RSS feed di %s',
'title' => 'RSS feeds',
'title_global' => 'Vista globale per categorie',
'title_fav' => 'Preferiti',
),
'log' => array(
'_' => 'Logs',
'clear' => 'Svuota logs',
'empty' => 'File di log vuoto',
'title' => 'Logs',
),
'menu' => array(
'about' => 'Informazioni',
'add_query' => 'Aggiungi ricerca',
'before_one_day' => 'Giorno precedente',
'before_one_week' => 'Settimana precedente',
'favorites' => 'Preferiti (%s)',
'global_view' => 'Vista globale per categorie',
'main_stream' => 'Flusso principale',
'mark_all_read' => 'Segna tutto come letto',
'mark_cat_read' => 'Segna la categoria come letta',
'mark_feed_read' => 'Segna il feed come letto',
'newer_first' => 'Mostra prima i recenti',
'non-starred' => 'Escludi preferiti',
'normal_view' => 'Vista elenco',
'older_first' => 'Ordina per meno recenti',
'queries' => 'Chiavi di ricerca',
'read' => 'Mostra solo letti',
'reader_view' => 'Modalità di lettura',
'rss_view' => 'RSS feed',
'search_short' => 'Cerca',
'starred' => 'Mostra solo preferiti',
'stats' => 'Statistiche',
'subscription' => 'Gestione sottoscrizioni',
'unread' => 'Mostra solo non letti',
),
'share' => 'Condividi',
'tag' => array(
'related' => 'Tags correlati',
),
);

View file

@ -1,122 +0,0 @@
<?php
return array(
'action' => array(
'finish' => 'Installazione completata',
'fix_errors_before' => 'Per favore correggi gli errori prima di passare al passaggio successivo.',
'keep_install' => 'Mantieni installazione precedente',
'next_step' => 'Vai al prossimo passaggio',
'reinstall' => 'Reinstalla FreshRSS',
),
'auth' => array(
'email_persona' => 'Indirizzo mail<br /><small>(per <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Web form (tradizionale, richiede JavaScript)',
'http' => 'HTTP (per gli utenti avanzati con HTTPS)',
'none' => 'Nessuno (pericoloso)',
'password_form' => 'Password<br /><small>(per il login tramite Web-form tradizionale)</small>',
'password_format' => 'Almeno 7 caratteri',
'persona' => 'Mozilla Persona (moderno, richiede JavaScript)',
'type' => 'Metodo di autenticazione',
),
'bdd' => array(
'_' => 'Database',
'conf' => array(
'_' => 'Configurazione database',
'ko' => 'Verifica le informazioni del database.',
'ok' => 'Le configurazioni del database sono state salvate.',
),
'host' => 'Host',
'prefix' => 'Prefisso tabella',
'password' => 'HTTP password',
'type' => 'Tipo di database',
'username' => 'HTTP username',
),
'check' => array(
'_' => 'Controlli',
'already_installed' => 'FreshRSS risulta già installato!',
'cache' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/cache</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella della cache sono corretti.',
),
'ctype' => array(
'nok' => 'Manca una libreria richiesta per il controllo dei caratteri (php-ctype).',
'ok' => 'Libreria richiesta per il controllo dei caratteri presente (ctype).',
),
'curl' => array(
'nok' => 'Manca il supporto per cURL (pacchetto php5-curl).',
'ok' => 'Estensione cURL presente.',
),
'data' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella data sono corretti.',
),
'dom' => array(
'nok' => 'Manca una libreria richiesta per leggere DOM.',
'ok' => 'Libreria richiesta per leggere DOM presente.',
),
'favicons' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/favicons</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella favicons sono corretti.',
),
'http_referer' => array(
'nok' => 'Per favore verifica che non stai alterando il tuo HTTP REFERER.',
'ok' => 'Il tuo HTTP REFERER riconosciuto corrisponde al tuo server.',
),
'json' => array(
'nok' => 'You lack a recommended library to parse JSON.',
'ok' => 'You have a recommended library to parse JSON.',
),
'minz' => array(
'nok' => 'Manca il framework Minz.',
'ok' => 'Framework Minz presente.',
),
'pcre' => array(
'nok' => 'Manca una libreria richiesta per le regular expressions (php-pcre).',
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/persona</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella Mozilla Persona sono corretti.',
),
'php' => array(
'_' => 'Installazione PHP',
'nok' => 'Versione di PHP %s FreshRSS richiede almeno la versione %s.',
'ok' => 'Versione di PHP %s, compatibile con FreshRSS.',
),
'users' => array(
'nok' => 'Verifica i permessi sulla cartella <em>./data/users</em>. Il server HTTP deve avere i permessi per scriverci dentro',
'ok' => 'I permessi sulla cartella users sono corretti.',
),
'xml' => array(
'nok' => 'You lack the required library to parse XML.',
'ok' => 'You have the required library to parse XML.',
),
),
'conf' => array(
'_' => 'Configurazioni generali',
'ok' => 'Configurazioni generali salvate.',
),
'congratulations' => 'Congratulazione!',
'default_user' => 'Username utente predefinito <small>(massimo 16 caratteri alfanumerici)</small>',
'delete_articles_after' => 'Rimuovi articoli dopo',
'fix_errors_before' => 'Per favore correggi gli errori prima di passare al passaggio successivo.',
'javascript_is_better' => 'FreshRSS funziona meglio con JavaScript abilitato',
'js' => array(
'confirm_reinstall' => 'Reinstallando FreshRSS perderai la configurazione precedente. Sei sicuro di voler procedere?',
),
'language' => array(
'_' => 'Lingua',
'choose' => 'Seleziona la lingua per FreshRSS',
'defined' => 'Lingua impostata.',
),
'not_deleted' => 'Qualcosa non ha funzionato; devi cancellare il file <em>%s</em> manualmente.',
'ok' => 'Processo di installazione terminato con successo.',
'step' => 'Passaggio %d',
'steps' => 'Passaggi',
'title' => 'Installazione · FreshRSS',
'this_is_the_end' => 'Fine',
);

View file

@ -1,62 +0,0 @@
<?php
return array(
'category' => array(
'_' => 'Categoria',
'add' => 'Aggiungi una categoria',
'empty' => 'Categoria vuota',
'new' => 'Nuova categoria',
),
'feed' => array(
'add' => 'Aggiungi un Feed RSS',
'advanced' => 'Avanzate',
'archiving' => 'Archiviazione',
'auth' => array(
'configuration' => 'Autenticazione',
'help' => 'Accesso per feeds protetti',
'http' => 'Autenticazione HTTP',
'password' => 'HTTP password',
'username' => 'HTTP username',
),
'css_help' => 'In caso di RSS feeds troncati (attenzione, richiede molto tempo!)',
'css_path' => 'Percorso del foglio di stile CSS del sito di origine',
'description' => 'Descrizione',
'empty' => 'Questo feed non contiene articoli. Per favore verifica il sito direttamente.',
'error' => 'Questo feed ha generato un errore. Per favore verifica se ancora disponibile.',
'in_main_stream' => 'Mostra in homepage',
'informations' => 'Informazioni',
'keep_history' => 'Numero minimo di articoli da mantenere',
'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come <em>%s</em>.',
'no_selected' => 'Nessun feed selezionato.',
'number_entries' => '%d articoli',
'stats' => 'Statistiche',
'think_to_add' => 'Aggiungi feed.',
'title' => 'Titolo',
'title_add' => 'Aggiungi RSS feed',
'ttl' => 'Non aggiornare automaticamente piu di',
'url' => 'Feed URL',
'validator' => 'Controlla la validita del feed ',
'website' => 'URL del sito',
'pubsubhubbub' => 'Notifica istantanea con PubSubHubbub',
),
'import_export' => array(
'export' => 'Esporta',
'export_opml' => 'Esporta tutta la lista dei feed (OPML)',
'export_starred' => 'Esporta i tuoi preferiti',
'feed_list' => 'Elenco di %s articoli',
'file_to_import' => 'File da importare<br />(OPML, Json o Zip)',
'file_to_import_no_zip' => 'File da importare<br />(OPML o Json)',
'import' => 'Importa',
'starred_list' => 'Elenco articoli preferiti',
'title' => 'Importa / esporta',
),
'menu' => array(
'bookmark' => 'Bookmark (trascina nei preferiti)',
'import_export' => 'Importa / esporta',
'subscription_management' => 'Gestione sottoscrizioni',
),
'title' => array(
'_' => 'Gestione sottoscrizioni',
'feed_management' => 'Gestione RSS feeds',
),
);

View file

@ -1,188 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'auth' => array(
'allow_anonymous' => 'Sta bezoekers toe om artikelen te lezen van de standaard gebruiker (%s)',
'allow_anonymous_refresh' => 'Sta bezoekers toe om de artikelen te vernieuwen',
'api_enabled' => 'Sta <abbr>API</abbr> toegang toe <small>(nodig voor mobiele apps)</small>',
'form' => 'Web formulier (traditioneel, benodigd JavaScript)',
'http' => 'HTTP (voor geavanceerde gebruikers met HTTPS)',
'none' => 'Geen (gevaarlijk)',
'persona' => 'Mozilla Persona (modern, benodigd JavaScript)',
'title' => 'Authenticatie',
'title_reset' => 'Authenticatie terugzetten',
'token' => 'Authenticatie teken',
'token_help' => 'Sta toegang toe tot de RSS uitvoer van de standaard gebruiker zonder authenticatie:',
'type' => 'Authenticatie methode',
'unsafe_autologin' => 'Sta onveilige automatische log in toe met het volgende formaat: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Controleer de permissies van de <em>./data/cache</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies van de cache map zijn goed.',
),
'categories' => array(
'nok' => 'Categorie tabel is slecht geconfigureerd.',
'ok' => 'Categorie tabel is ok.',
),
'connection' => array(
'nok' => 'Verbinding met de database kan niet worden gemaakt.',
'ok' => 'Verbinding met de database is ok.',
),
'ctype' => array(
'nok' => 'U mist de benodigde bibliotheek voor character type checking (php-ctype).',
'ok' => 'U hebt de benodigde bibliotheek voor character type checking (ctype).',
),
'curl' => array(
'nok' => 'U mist de cURL (php5-curl package).',
'ok' => 'U hebt de cURL uitbreiding.',
),
'data' => array(
'nok' => 'Controleer de permissies op de <em>./data</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de data map zijn goed.',
),
'database' => 'Database installatie',
'dom' => array(
'nok' => 'U mist de benodigde bibliotheek voor het bladeren van DOM (php-xml package).',
'ok' => 'U hebt de benodigde bibliotheek voor het bladeren van DOM.',
),
'entries' => array(
'nok' => 'Invoer tabel is slecht geconfigureerd.',
'ok' => 'Invoer tabel is ok.',
),
'favicons' => array(
'nok' => 'Controleer de permissies op de <em>./data/favicons</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de favicons map zijn goed.',
),
'feeds' => array(
'nok' => 'Feed tabel is slecht geconfigureerd.',
'ok' => 'Feed tabel is ok.',
),
'files' => 'Bestanden installatie',
'json' => array(
'nok' => 'U mist JSON (php5-json package).',
'ok' => 'U hebt JSON uitbreiding.',
),
'minz' => array(
'nok' => 'U mist Minz framework.',
'ok' => 'U hebt Minz framework.',
),
'pcre' => array(
'nok' => 'U mist de benodigde bibliotheek voor regular expressions (php-pcre).',
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
'nok' => 'U mist PDO of een van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Controleer de permissies op de <em>./data/persona</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de Mozilla Persona map zijn goed.',
),
'php' => array(
'_' => 'PHP installatie',
'nok' => 'Uw PHP versie is %s maar FreshRSS benodigd tenminste versie %s.',
'ok' => 'Uw PHP versie is %s, welke compatibel is met FreshRSS.',
),
'tables' => array(
'nok' => 'Er zijn één of meer ontbrekende tabellen in de database.',
'ok' => 'Alle tabellen zijn aanwezig in de database.',
),
'title' => 'Installatie controle',
'tokens' => array(
'nok' => 'Controleer de permissies op de <em>./data/tokens</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de tokens map zijn goed.',
),
'users' => array(
'nok' => 'Controleer de permissies op de <em>./data/users</em> map. HTTP server moet rechten hebben om hierin te schrijven',
'ok' => 'Permissies op de users map zijn goed.',
),
'zip' => array(
'nok' => 'U mist ZIP uitbreiding (php5-zip package).',
'ok' => 'U hebt ZIP uitbreiding.',
),
),
'extensions' => array(
'disabled' => 'Uitgeschakeld',
'empty_list' => 'Er zijn geïnstalleerde uitbreidingen',
'enabled' => 'Ingeschakeld',
'no_configure_view' => 'Deze uitbreiding kan niet worden geconfigureerd.',
'system' => array(
'_' => 'Systeem uitbreidingen',
'no_rights' => 'Systeem uitbreidingen (U hebt hier geen rechten op)',
),
'title' => 'Uitbreidingen',
'user' => 'Gebruikers uitbreidingen',
),
'stats' => array(
'_' => 'Statistieken',
'all_feeds' => 'Alle feeds',
'category' => 'Categorie',
'entry_count' => 'Invoer aantallen',
'entry_per_category' => 'Aantallen per categorie',
'entry_per_day' => 'Aantallen per dag (laatste 30 dagen)',
'entry_per_day_of_week' => 'Per dag of week (gemiddeld: %.2f berichten)',
'entry_per_hour' => 'Per uur (gemiddeld: %.2f berichten)',
'entry_per_month' => 'Per maand (gemiddeld: %.2f berichten)',
'entry_repartition' => 'Invoer verdeling',
'feed' => 'Feed',
'feed_per_category' => 'Feeds per categorie',
'idle' => 'Gepauzeerde feeds',
'main' => 'Hoofd statistieken',
'main_stream' => 'Overzicht',
'menu' => array(
'idle' => 'Gepauzeerde feeds',
'main' => 'Hoofd statistieken',
'repartition' => 'Artikelen verdeling',
),
'no_idle' => 'Er is geen gepauzeerde feed!',
'number_entries' => '%d artikelen',
'percent_of_total' => '%% van totaal',
'repartition' => 'Artikelen verdeling',
'status_favorites' => 'Favorieten',
'status_read' => 'Gelezen',
'status_total' => 'Totaal',
'status_unread' => 'Ongelezen',
'title' => 'Statistieken',
'top_feed' => 'Top tien feeds',
),
'system' => array(
'_' => 'Systeem configuratie',
'auto-update-url' => 'Automatische update server URL',
'instance-name' => 'Voorbeeld naam',
'max-categories' => 'Categoriën limiet per gebruiker',
'max-feeds' => 'Feed limiet per gebruiker',
'registration' => array(
'help' => '0 betekent geen account limiet',
'number' => 'Maximum aantal accounts',
),
),
'update' => array(
'_' => 'Versie controle',
'apply' => 'Toepassen',
'check' => 'Controleer op nieuwe versies',
'current_version' => 'Uw huidige versie van FreshRSS is %s.',
'last' => 'Laatste controle: %s',
'none' => 'Geen nieuwe versie om toe te passen',
'title' => 'Vernieuw systeem',
),
'user' => array(
'articles_and_size' => '%s artikelen (%s)',
'create' => 'Creëer nieuwe gebruiker',
'email_persona' => 'Log in mail adres<br /><small>(voor <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Taal',
'number' => 'Er is %d accounts gemaakt',
'numbers' => 'Er zijn %d accounts gemaakt',
'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
'password_format' => 'Ten minste 7 tekens',
'registration' => array(
'allow' => 'Sta het maken van nieuwe accounts toe',
'help' => '0 betekent dat er geen account limiet is',
'number' => 'Max aantal accounts',
),
'title' => 'Beheer gebruikers',
'user_list' => 'Lijst van gebruikers ',
'username' => 'Gebruikers naam',
'users' => 'Gebruikers',
),
);

View file

@ -1,174 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'archiving' => array(
'_' => 'Archivering',
'advanced' => 'Geavanceerd',
'delete_after' => 'Verwijder artikelen na',
'help' => 'Meer opties zijn beschikbaar in de persoonlijke stroom instellingen',
'keep_history_by_feed' => 'Minimum aantal te behouden artikelen in de feed',
'optimize' => 'Optimaliseer database',
'optimize_help' => 'Doe dit zo af en toe om de omvang van de database te verkleinen',
'purge_now' => 'Schoon nu op',
'title' => 'Archivering',
'ttl' => 'Vernieuw niet automatisch meer dan',
),
'display' => array(
'_' => 'Opmaak',
'icon' => array(
'bottom_line' => 'Onderaan',
'entry' => 'Artikel pictogrammen',
'publication_date' => 'Publicatie datum',
'related_tags' => 'Gerelateerde labels',
'sharing' => 'Delen',
'top_line' => 'Bovenaan',
),
'language' => 'Taal',
'notif_html5' => array(
'seconds' => 'seconden (0 betekent geen stop)',
'timeout' => 'HTML5 notificatie stop',
),
'theme' => 'Thema',
'title' => 'Opmaak',
'width' => array(
'content' => 'Inhoud breedte',
'large' => 'Breed',
'medium' => 'Normaal',
'no_limit' => 'Geen limiet',
'thin' => 'Smal',
),
),
'query' => array(
'_' => 'Gebruikers queries (informatie aanvragen)',
'deprecated' => 'Deze query (informatie aanvraag) is niet langer geldig. De bedoelde categorie of feed is al verwijderd.',
'filter' => 'Filter toegepast:',
'get_all' => 'Toon alle artikelen',
'get_category' => 'Toon "%s" categorie',
'get_favorite' => 'Toon favoriete artikelen',
'get_feed' => 'Toon "%s" feed',
'no_filter' => 'Geen filter',
'none' => 'U hebt nog geen gebruikers query aangemaakt..',
'number' => 'Query n°%d',
'order_asc' => 'Toon oudste artikelen eerst',
'order_desc' => 'Toon nieuwste artikelen eerst',
'search' => 'Zoek naar "%s"',
'state_0' => 'Toon alle artikelen',
'state_1' => 'Toon gelezen artikelen',
'state_2' => 'Toon ongelezen artikelen',
'state_3' => 'Toon alle artikelen',
'state_4' => 'Toon favoriete artikelen',
'state_5' => 'Toon gelezen favoriete artikelen',
'state_6' => 'Toon ongelezen favoriete artikelen',
'state_7' => 'Toon favoriete artikelen',
'state_8' => 'Toon niet favoriete artikelen',
'state_9' => 'Toon gelezen niet favoriete artikelen',
'state_10' => 'Toon ongelezen niet favoriete artikelen',
'state_11' => 'Toon niet favoriete artikelen',
'state_12' => 'Toon alle artikelen',
'state_13' => 'Toon gelezen artikelen',
'state_14' => 'Toon ongelezen artikelen',
'state_15' => 'Toon alle artikelen',
'title' => 'Gebruikers queries',
),
'profile' => array(
'_' => 'Profiel beheer',
'delete' => array(
'_' => 'Account verwijderen',
'warn' => 'Uw account en alle gerelateerde gegvens worden verwijderd.',
),
'email_persona' => 'Log in mail adres<br /><small>(voor <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Wachtwoord API<br /><small>(e.g., voor mobiele apps)</small>',
'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
'password_format' => 'Ten minste 7 tekens',
'title' => 'Profiel',
),
'reading' => array(
'_' => 'Lezen',
'after_onread' => 'Na “markeer alles als gelezen”,',
'articles_per_page' => 'Aantal artikelen per pagina',
'auto_load_more' => 'Laad volgende artikel onderaan de pagina',
'auto_remove_article' => 'Verberg artikel na lezen',
'confirm_enabled' => 'Toon een bevestigings dialoog op “markeer alles als gelezen” acties',
'display_articles_unfolded' => 'Toon artikelen uitgeklapt als standaard',
'display_categories_unfolded' => 'Toon categoriën ingeklapt als standaard',
'hide_read_feeds' => 'Verberg categoriën en feeds zonder ongelezen artikelen (werkt niet met “Toon alle artikelen” configuratie)',
'img_with_lazyload' => 'Gebruik "lazy load" methode om afbeeldingen te laden',
'jump_next' => 'Ga naar volgende ongelezen (feed of categorie)',
'mark_updated_article_unread' => 'Markeer vernieuwd artikel als ongelezen',
'number_divided_when_reader' => 'Gedeeld door 2 in de lees modus.',
'read' => array(
'article_open_on_website' => 'Als het artikel is geopend op de originele website',
'article_viewed' => 'Als het artikel is bekeken',
'scroll' => 'Tijdens scrollen',
'upon_reception' => 'Tijdens ontvangst van het artikel',
'when' => 'Markeer artikel als gelezen…',
),
'show' => array(
'_' => 'Artikelen om te tonen',
'adaptive' => 'Pas weergave aan',
'all_articles' => 'Bekijk alle artikelen',
'unread' => 'Bekijk alleen ongelezen',
),
'sort' => array(
'_' => 'Sorteer volgorde',
'newer_first' => 'Nieuwste eerst',
'older_first' => 'Oudste eerst',
),
'sticky_post' => 'Koppel artikel aan de bovenkant als het geopend wordt',
'title' => 'Lees modus',
'view' => array(
'default' => 'Standaard weergave',
'global' => 'Globale weergave',
'normal' => 'Normale weergave',
'reader' => 'Lees weergave',
),
),
'sharing' => array(
'_' => 'Delen',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'Meer informatie',
'print' => 'Afdrukken',
'shaarli' => 'Shaarli',
'share_name' => 'Gedeelde naam om weer te geven',
'share_url' => 'Deel URL voor gebruik',
'title' => 'Delen',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Shortcuts',
'article_action' => 'Artikel acties',
'auto_share' => 'Delen',
'auto_share_help' => 'Als er slechts één deel methode i, dan wordt deze gebruikt. Anders zijn ze toegankelijk met hun nummer.',
'close_dropdown' => 'Sluit menu',
'collapse_article' => 'Inklappen',
'first_article' => 'Spring naar eerste artikel',
'focus_search' => 'Toegang zoek venster',
'help' => 'Toon documentatie',
'javascript' => 'JavaScript moet geactiveerd zijn om verwijzingen te gebruiken',
'last_article' => 'Spring naar laatste artikel',
'load_more' => 'Laad meer artikelen',
'mark_read' => 'Markeer als gelezen',
'mark_favorite' => 'Markeer als favoriet',
'navigation' => 'Navigatie',
'navigation_help' => 'Met de "Shift" toets, kunt u navigatie verwijzingen voor feeds gebruiken.<br/>Met de "Alt" toets, kunt u navigatie verwijzingen voor categoriën gebruiken.',
'next_article' => 'Spring naar volgende artikel',
'other_action' => 'Andere acties',
'previous_article' => 'Spring naar vorige artikel',
'see_on_website' => 'Bekijk op originale website',
'shift_for_all_read' => '+ <code>shift</code> om alle artikelen als gelezen te markeren',
'title' => 'Verwijzingen',
'user_filter' => 'Toegang gebruikers filters',
'user_filter_help' => 'Als er slechts één gebruikers filter s, dan wordt deze gebruikt. Anders zijn ze toegankelijk met hun nummer.',
),
'user' => array(
'articles_and_size' => '%s artikelen (%s)',
'current' => 'Huidige gebruiker',
'is_admin' => 'is administrateur',
'users' => 'Gebruikers',
),
);

View file

@ -1,111 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'admin' => array(
'optimization_complete' => 'Optimalisatie compleet',
),
'access' => array(
'denied' => 'U hebt geen rechten om deze pagina te bekijken.',
'not_found' => 'Deze pagina bestaat niet',
),
'auth' => array(
'form' => array(
'not_set' => 'Een probleem is opgetreden tijdens de controle van de systeem configuratie. Probeer het later nog eens.',
'set' => 'Formulier is nu uw standaard authenticatie systeem.',
),
'login' => array(
'invalid' => 'Log in is ongeldig',
'success' => 'U bent ingelogd',
),
'logout' => array(
'success' => 'U bent uitgelogd',
),
'no_password_set' => 'Administrateur wachtwoord is niet ingesteld. Deze mogelijkheid is niet beschikbaar.',
'not_persona' => 'Alleen Persona systeem kan worden gereset.',
),
'conf' => array(
'error' => 'Er is een fout opgetreden tijdens het opslaan van de configuratie',
'query_created' => 'Query "%s" is gemaakt.',
'shortcuts_updated' => 'Verwijzingen zijn vernieuwd',
'updated' => 'Configuratie is vernieuwd',
),
'extensions' => array(
'already_enabled' => '%s is al ingeschakeld',
'disable' => array(
'ko' => '%s kan niet worden uitgeschakeld. <a href="%s">Controleer FressRSS log bestanden</a> voor details.',
'ok' => '%s is nu uitgeschakeld',
),
'enable' => array(
'ko' => '%s kan niet worden ingeschakeld. <a href="%s">Controleer FressRSS log bestanden</a> voor details.',
'ok' => '%s is nn ingeschakeld',
),
'no_access' => 'U hebt geen toegang voor %s',
'not_enabled' => '%s is nog niet ingeschakeld',
'not_found' => '%s bestaat niet',
),
'import_export' => array(
'export_no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server. Exporteer a.u.b. uw bestanden één voor één.',
'feeds_imported' => 'Uw feeds zijn geimporteerd en worden nu vernieuwd',
'feeds_imported_with_errors' => 'Uw feeds zijn geimporteerd maar er zijn enige fouten opgetreden',
'file_cannot_be_uploaded' => 'Bestand kan niet worden verzonden!',
'no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server.',
'zip_error' => 'Er is een fout opgetreden tijdens het imporeren van het Zip bestand.',
),
'sub' => array(
'actualize' => 'Actualiseren',
'category' => array(
'created' => 'Categorie %s is gemaakt.',
'deleted' => 'Categorie is verwijderd.',
'emptied' => 'Categorie is leeg gemaakt',
'error' => 'Categorie kan niet worden vernieuwd',
'name_exists' => 'Categorie naam bestaat al.',
'no_id' => 'U moet de id specificeren of de categorie.',
'no_name' => 'Categorie naam mag niet leeg zijn.',
'not_delete_default' => 'U kunt de standaard categorie niet verwijderen!',
'not_exist' => 'De categorie bestaat niet!',
'over_max' => 'U hebt het maximale aantal categoriën bereikt (%d)',
'updated' => 'Categorie is vernieuwd.',
),
'feed' => array(
'actualized' => '<em>%s</em> is vernieuwd',
'actualizeds' => 'RSS feeds zijn vernieuwd',
'added' => 'RSS feed <em>%s</em> is toegevoegd',
'already_subscribed' => 'U bent al geabonneerd op <em>%s</em>',
'deleted' => 'Feed is verwijderd',
'error' => 'Feed kan niet worden vernieuwd',
'internal_problem' => 'De RSS feed kon niet worden toegevoegd. <a href="%s">Controleer FressRSS log bestanden</a> voor details.',
'invalid_url' => 'URL <em>%s</em> is ongeldig',
'marked_read' => 'Feeds zijn gemarkeerd als gelezen',
'n_actualized' => '%d feeds zijn vernieuwd',
'n_entries_deleted' => '%d artikelen zijn verwijderd',
'no_refresh' => 'Er is geen feed om te vernieuwen…',
'not_added' => '<em>%s</em> kon niet worden toegevoegd',
'over_max' => 'U hebt het maximale aantal feeds bereikt(%d)',
'updated' => 'Feed is vernieuwd',
),
'purge_completed' => 'Opschonen klaar (%d artikelen verwijderd)',
),
'update' => array(
'can_apply' => 'FreshRSS word nu vernieud naar <strong>versie %s</strong>.',
'error' => 'Het vernieuwingsproces kwam een fout tegen: %s',
'file_is_nok' => 'Controleer permissies op <em>%s</em> map. HTTP server moet rechten hebben om er in te schrijven',
'finished' => 'Vernieuwing compleet!',
'none' => 'Geen vernieuwing om toe te passen',
'server_not_found' => 'Vernieuwings server kan niet worden gevonden. [%s]',
),
'user' => array(
'created' => array(
'_' => 'Gebruiker %s is aangemaakt',
'error' => 'Gebruiker %s kan niet worden aangemaakt',
),
'deleted' => array(
'_' => 'Gebruiker %s is verwijderd',
'error' => 'Gebruiker %s kan niet worden verwijderd',
),
'set_registration' => 'Het maximale aantal accounts is vernieuwd.',
),
'profile' => array(
'error' => 'Uw profiel kan niet worden aangepast',
'updated' => 'Uw profiel is aangepast',
),
);

View file

@ -1,185 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'action' => array(
'actualize' => 'Actualiseren',
'back_to_rss_feeds' => '← Ga terug naar je RSS feeds',
'cancel' => 'Annuleren',
'create' => 'Opslaan',
'disable' => 'Uitzetten',
'empty' => 'Leeg',
'enable' => 'Aanzetten',
'export' => 'Exporteren',
'filter' => 'Filteren',
'import' => 'Importeren',
'manage' => 'Beheren',
'mark_read' => 'Markeer als gelezen',
'mark_favorite' => 'Markeer als favoriet',
'remove' => 'Verwijder',
'see_website' => 'Bekijk website',
'submit' => 'Opslaan',
'truncate' => 'Verwijder alle artikelen',
),
'auth' => array(
'email' => 'Email adres',
'keep_logged_in' => 'Ingelogd blijven voor <small>(1 maand)</small>',
'login' => 'Log in',
'login_persona' => 'Login met Persona',
'login_persona_problem' => 'Connectiviteits problemen met Persona',
'logout' => 'Log uit',
'password' => array(
'_' => 'Wachtwoord',
'format' => '<small>Ten minste 7 tekens</small>',
),
'registration' => array(
'_' => 'Nieuw account',
'ask' => 'Maak een account?',
'title' => 'Account maken',
),
'reset' => 'Authenticatie reset',
'username' => array(
'_' => 'Gebruikersnaam',
'admin' => 'Administrator gebruikersnaam',
'format' => '<small>maximaal 16 alphanumerieke tekens</small>',
),
'will_reset' => 'Het authenticatie system zal worden gereset: een formulier zal worden gebruikt in plaats van Persona.',
),
'date' => array(
'Apr' => '\\A\\p\\r\\i\\l',
'Aug' => '\\A\\u\\g\\u\\s\\t\\u\\s',
'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r',
'Feb' => '\\F\\e\\b\\r\\u\\a\\r\\i',
'Jan' => '\\J\\a\\n\\u\\a\\r\\i',
'Jul' => '\\J\\u\\l\\i',
'Jun' => '\\J\\u\\n\\i',
'Mar' => '\\M\\a\\a\\r\\t',
'May' => '\\M\\e\\i',
'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r',
'Oct' => '\\O\\k\\t\\o\\b\\e\\r',
'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r',
'apr' => 'apr',
'april' => 'Apr',
'aug' => 'aug',
'august' => 'Aug',
'before_yesterday' => 'Ouder',
'dec' => 'dec',
'december' => 'Dec',
'feb' => 'feb',
'february' => 'Feb',
'format_date' => 'j %s Y', //<-- European date format // 'format_date' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y',
'format_date_hour' => 'j %s Y \\o\\m H\\:i', //<-- European date format // 'format_date_hour' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y \\a\\t H\\:i',
'fri' => 'Vr',
'jan' => 'jan',
'january' => 'Jan',
'jul' => 'jul',
'july' => 'Jul',
'jun' => 'jun',
'june' => 'Jun',
'last_3_month' => 'Laatste drie maanden',
'last_6_month' => 'Laatste zes maanden',
'last_month' => 'Vorige maand',
'last_week' => 'Vorige week',
'last_year' => 'Vorig jaar',
'mar' => 'mar',
'march' => 'Mar',
'may' => 'Mei',
'mon' => 'Ma',
'month' => 'maanden',
'nov' => 'nov',
'november' => 'Nov',
'oct' => 'okt',
'october' => 'Okt',
'sat' => 'Za',
'sep' => 'sep',
'september' => 'Sep',
'sun' => 'Zo',
'thu' => 'Do',
'today' => 'Vandaag',
'tue' => 'Di',
'wed' => 'Wo',
'yesterday' => 'Gisteren',
),
'freshrss' => array(
'_' => 'FreshRSS',
'about' => 'Over FreshRSS',
),
'js' => array(
'category_empty' => 'Lege categorie',
'confirm_action' => 'Weet u zeker dat u dit wilt doen? Het kan niet ongedaan worden gemaakt!',
'confirm_action_feed_cat' => 'Weet u zeker dat u dit wilt doen? U verliest alle gereleteerde favorieten en gebruikers informatie. Het kan niet ongedaan worden gemaakt!',
'feedback' => array(
'body_new_articles' => 'Er zijn %%d nieuwe artikelen om te lezen op FreshRSS.',
'request_failed' => 'Een opdracht is mislukt, mogelijk door Internet verbindings problemen.',
'title_new_articles' => 'FreshRSS: nieuwe artikelen!',
),
'new_article' => 'Er zijn nieuwe artikelen beschikbaar, klik om de pagina te vernieuwen.',
'should_be_activated' => 'JavaScript moet aan staan',
),
'lang' => array(
'cz' => 'Čeština',
'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
'it' => 'Italiano',
'nl' => 'Nederlands',
'ru' => 'Русский',
'tr' => 'Türkçe',
),
'menu' => array(
'about' => 'Over',
'admin' => 'Administratie',
'archiving' => 'Archiveren',
'authentication' => 'Authenticatie',
'check_install' => 'Installatie controle',
'configuration' => 'Configuratie',
'display' => 'Opmaak',
'extensions' => 'Uitbreidingen',
'logs' => 'Log boeken',
'queries' => 'Gebruikers informatie',
'reading' => 'Lezen',
'search' => 'Zoek woorden of #labels',
'sharing' => 'Delen',
'shortcuts' => 'Snelle toegang',
'stats' => 'Statistieken',
'system' => 'Systeem configuratie',
'update' => 'Versie controle',
'user_management' => 'Beheer gebruikers',
'user_profile' => 'Profiel',
),
'pagination' => array(
'first' => 'Eerste',
'last' => 'Laatste',
'load_more' => 'Laad meer artikelen',
'mark_all_read' => 'Markeer alle als gelezen',
'next' => 'Volgende',
'nothing_to_load' => 'Er zijn geen artikelen meer',
'previous' => 'Vorige',
),
'share' => array(
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'movim' => 'Movim',
'print' => 'Print',
'shaarli' => 'Shaarli',
'twitter' => 'Twitter',
'wallabag' => 'wallabag v1',
'wallabagv2' => 'wallabag v2',
'jdh' => 'Journal du hacker',
),
'short' => array(
'attention' => 'Attentie!',
'blank_to_disable' => 'Laat leeg om uit te zetten',
'by_author' => 'Door <em>%s</em>',
'by_default' => 'Door standaard',
'damn' => 'Potverdorie!',
'default_category' => 'Niet ingedeeld',
'no' => 'Nee',
'not_applicable' => 'Niet aanwezig',
'ok' => 'Ok!',
'or' => 'of',
'yes' => 'Ja',
),
);

View file

@ -1,61 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'about' => array(
'_' => 'Over',
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
'bugs_reports' => 'Rapporteer fouten',
'credits' => 'Waarderingen',
'credits_content' => 'Sommige ontwerp elementen komen van <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> alhoewel FreshRSS dit raamwerk niet gebruikt. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Pictogrammen</a> komen van het <a href="https://www.gnome.org/">GNOME project</a>. <em>De Open Sans</em> font police is gemaakt door <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicons zijn verzameld met de <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS is gebaseerd op <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, een PHP raamwerk. Nederlandse vertaling door Wanabo, <a href="http://www.nieuwskop.be" title="NieuwsKop">NieuwsKop.be</a>. Link naar de Nederlandse vertaling, <a href="https://github.com/Wanabo/FreshRSS-Dutch-translation/tree/master">FreshRSS-Dutch-translation</a>.',
'freshrss_description' => 'FreshRSS is een RSS feed aggregator om zelf te hosten zoals <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> of <a href="http://projet.idleman.fr/leed/">Leed</a>. Het gebruikt weinig systeembronnen en is makkelijk te administreren terwijl het een krachtig en makkelijk te configureren programma is.',
'github' => '<a href="https://github.com/FreshRSS/FreshRSS/issues">op Github</a>',
'license' => 'License',
'project_website' => 'Project website',
'title' => 'Over',
'version' => 'Versie',
'website' => 'Website',
),
'feed' => array(
'add' => 'U kunt wat feeds toevoegen.',
'empty' => 'Er is geen artikel om te laten zien.',
'rss_of' => 'RSS feed van %s',
'title' => 'Overzicht RSS feeds',
'title_global' => 'Globale weergave',
'title_fav' => 'Uw favorieten',
),
'log' => array(
'_' => 'Log bestanden',
'clear' => 'Leeg de log bestanden',
'empty' => 'Log bestand is leeg',
'title' => 'Log bestanden',
),
'menu' => array(
'about' => 'Over FreshRSS',
'add_query' => 'Voeg een query toe',
'before_one_day' => 'Ouder als een dag',
'before_one_week' => 'Ouder als een week',
'favorites' => 'Favorieten (%s)',
'global_view' => 'Globale weergave',
'main_stream' => 'Overzicht',
'mark_all_read' => 'Markeer alles als gelezen',
'mark_cat_read' => 'Markeer categorie als gelezen',
'mark_feed_read' => 'Markeer feed als gelezen',
'newer_first' => 'Nieuwste eerst',
'non-starred' => 'Laat alles zien behalve favorieten',
'normal_view' => 'Normale weergave',
'older_first' => 'Oudste eerst',
'queries' => 'Gebruikers queries',
'read' => 'Laat alleen gelezen zien',
'reader_view' => 'Lees modus',
'rss_view' => 'RSS feed',
'search_short' => 'Zoeken',
'starred' => 'Laat alleen favorieten zien',
'stats' => 'Statistieken',
'subscription' => 'Abonnementen beheer',
'unread' => 'Laat alleen ongelezen zien',
),
'share' => 'Delen',
'tag' => array(
'related' => 'Verwante labels',
),
);

View file

@ -1,121 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'action' => array(
'finish' => 'Completeer installatie',
'fix_errors_before' => 'Repareer de fouten alvorens naar de volgende stap te gaan.',
'keep_install' => 'Behoud de vorige installatie',
'next_step' => 'Ga naar de volgende stap',
'reinstall' => 'Installeer FreshRSS opnieuw',
),
'auth' => array(
'email_persona' => 'Log in mail adres<br /><small>(voor <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'form' => 'Web formulier (traditioneel, benodigd JavaScript)',
'http' => 'HTTP (voor geavanceerde gebruikers met HTTPS)',
'none' => 'Geen (gevaarlijk)',
'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
'password_format' => 'Tenminste 7 tekens',
'persona' => 'Mozilla Persona (modern, benodigd JavaScript)',
'type' => 'Authenticatie methode',
),
'bdd' => array(
'_' => 'Database',
'conf' => array(
'_' => 'Database configuratie',
'ko' => 'Controleer uw database informatie.',
'ok' => 'Database configuratie is opgeslagen.',
),
'host' => 'Host',
'prefix' => 'Tabel voorvoegsel',
'password' => 'HTTP wachtwoord',
'type' => 'Type database',
'username' => 'HTTP gebruikersnaam',
),
'check' => array(
'_' => 'Controles',
'already_installed' => 'We hebben geconstateerd dat FreshRSS al is geïnstallerd!',
'cache' => array(
'nok' => 'Controleer permissies van de <em>./data/cache</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
'ok' => 'Permissies van de cache map zijn goed.',
),
'ctype' => array(
'nok' => 'U mist een benodigde bibliotheek voor character type checking (php-ctype).',
'ok' => 'U hebt de benodigde bibliotheek voor character type checking (ctype).',
),
'curl' => array(
'nok' => 'U mist cURL (php5-curl package).',
'ok' => 'U hebt de cURL uitbreiding.',
),
'data' => array(
'nok' => 'Controleer permissies van de <em>./data</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
'ok' => 'Permissies van de data map zijn goed.',
),
'dom' => array(
'nok' => 'U mist een benodigde bibliotheek om te bladeren in de DOM.',
'ok' => 'U hebt de benodigde bibliotheek om te bladeren in de DOM.',
),
'favicons' => array(
'nok' => 'Controleer permissies van de <em>./data/favicons</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
'ok' => 'Permissies van de favicons map zijn goed.',
),
'http_referer' => array(
'nok' => 'Controleer a.u.b. dat u niet uw HTTP REFERER wijzigd.',
'ok' => 'Uw HTTP REFERER is bekend en komt overeen met uw server.',
),
'json' => array(
'nok' => 'U mist een benodigede bibliotheek om JSON te gebruiken.',
'ok' => 'U hebt de benodigde bibliotheek om JSON te gebruiken.',
),
'minz' => array(
'nok' => 'U mist het Minz framework.',
'ok' => 'U hebt het Minz framework.',
),
'pcre' => array(
'nok' => 'U mist een benodigde bibliotheek voor regular expressions (php-pcre).',
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
'nok' => 'U mist PDO of één van de ondersteunde (pdo_mysql, pdo_sqlite).',
'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Controleer permissies van de <em>./data/persona</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
'ok' => 'Permissies van de Mozilla Persona map zijn goed.',
),
'php' => array(
'nok' => 'Uw PHP versie is %s maar FreshRSS benodigd tenminste versie %s.',
'ok' => 'Uw PHP versie is %s, welke compatibel is met FreshRSS.',
),
'users' => array(
'nok' => 'Controleer permissies van de <em>./data/users</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
'ok' => 'Permissies van de users map zijn goed.',
),
'xml' => array(
'nok' => 'U mist de benodigde bibliotheek om XML te gebruiken.',
'ok' => 'U hebt de benodigde bibliotheek om XML te gebruiken.',
),
),
'conf' => array(
'_' => 'Algemene configuratie',
'ok' => 'Algemene configuratie is opgeslagen.',
),
'congratulations' => 'Gefeliciteerd!',
'default_user' => 'Gebruikersnaam van de standaard gebruiker <small>(maximaal 16 alphanumerieke tekens)</small>',
'delete_articles_after' => 'Verwijder artikelen na',
'fix_errors_before' => 'Repareer fouten alvorens U naar de volgende stap gaat.',
'javascript_is_better' => 'FreshRSS werkt beter JavaScript ingeschakeld',
'js' => array(
'confirm_reinstall' => 'U verliest uw vorige configuratie door FreshRSS opnieuw te installeren. Weet u zeker dat u verder wilt gaan?',
),
'language' => array(
'_' => 'Taal',
'choose' => 'Kies een taal voor FreshRSS',
'defined' => 'Taal is bepaald.',
),
'not_deleted' => 'Er ging iets fout! U moet het bestand <em>%s</em> handmatig verwijderen.',
'ok' => 'De installatie procedure is geslaagd.',
'step' => 'stap %d',
'steps' => 'Stappen',
'title' => 'Installatie · FreshRSS',
'this_is_the_end' => 'Dit is het einde',
);

View file

@ -1,62 +0,0 @@
<?php
/* Dutch translation by Wanabo. http://www.nieuwskop.be */
return array(
'category' => array(
'_' => 'Categorie',
'add' => 'Voeg categorie toe',
'empty' => 'Lege categorie',
'new' => 'Nieuwe categorie',
),
'feed' => array(
'add' => 'Voeg een RSS feed toe',
'advanced' => 'Geavanceerd',
'archiving' => 'Archiveren',
'auth' => array(
'configuration' => 'Log in',
'help' => 'Verbinding toestaan toegang te krijgen tot HTTP beveiligde RSS feeds',
'http' => 'HTTP Authenticatie',
'password' => 'HTTP wachtwoord',
'username' => 'HTTP gebruikers naam',
),
'css_help' => 'Haalt verstoorde RSS feeds op (attentie, heeft meer tijd nodig!)',
'css_path' => 'Artikelen CSS pad op originele website',
'description' => 'Omschrijving',
'empty' => 'Deze feed is leeg. Controleer of deze nog actueel is.',
'error' => 'Deze feed heeft problemen. Verifieer a.u.b het doeladres en actualiseer het.',
'in_main_stream' => 'Zichtbaar in het overzicht',
'informations' => 'Informatie',
'keep_history' => 'Minimum aantal artikelen om te houden',
'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder <em>%s</em>.',
'no_selected' => 'Geen feed geselecteerd.',
'number_entries' => '%d artikelen',
'pubsubhubbub' => 'Directe notificaties met PubSubHubbub',
'stats' => 'Statistieken',
'think_to_add' => 'Voeg wat feeds toe.',
'title' => 'Titel',
'title_add' => 'Voeg een RSS feed toe',
'ttl' => 'Vernieuw automatisch niet vaker dan',
'url' => 'Feed URL',
'validator' => 'Controleer de geldigheid van de feed',
'website' => 'Website URL',
),
'import_export' => array(
'export' => 'Exporteer',
'export_opml' => 'Exporteer lijst van feeds (OPML)',
'export_starred' => 'Exporteer je fovorieten',
'feed_list' => 'Lijst van %s artikelen',
'file_to_import' => 'Bestand om te importeren<br />(OPML, Json of Zip)',
'file_to_import_no_zip' => 'Bestand om te importeren<br />(OPML of Json)',
'import' => 'Importeer',
'starred_list' => 'Lijst van favoriete artikelen',
'title' => 'Importeren / exporteren',
),
'menu' => array(
'bookmark' => 'Abonneer (FreshRSS bladwijzer)',
'import_export' => 'Importeer / exporteer',
'subscription_management' => 'Abonnementen beheer',
),
'title' => array(
'_' => 'Abonnementen beheer',
'feed_management' => 'RSS feed beheer',
),
);

View file

@ -1,183 +0,0 @@
<?php
return array(
'auth' => array(
'allow_anonymous' => 'Разрешить анонимное чтение статей для пользователя по умолчанию (%s)',
'allow_anonymous_refresh' => 'Разрешить анонимное обновление статей',
'api_enabled' => 'Включить доступ к <abbr>API</abbr> <small>(необходимо для мобильных приложений)</small>',
'form' => 'На основе веб-формы (традиционный, необходим JavaScript)',
'http' => 'HTTP (для продвинутых пользователей - по HTTPS)',
'none' => 'Без аутентификации (небезопасный)',
'persona' => 'Mozilla Persona (новый, необходим JavaScript)',
'title' => 'Аутентификации',
'title_reset' => 'Сброс аутентицикации',
'token' => 'Токен аутентификации',
'token_help' => 'Разрешает доступ к RSS ленте пользователя по умолчанию без аутентификации:',
'type' => 'Метод аутентификации',
'unsafe_autologin' => 'Разрешить небезопасный автоматический вход с использованием следующего формата: ',
),
'check_install' => array(
'cache' => array(
'nok' => 'Проверьте права доступа к папке <em>./data/cache</em>. Сервер HTTP должен иметь права на запись в эту папку',
'ok' => 'Права на <em>./data/cache</em> в порядке.',
),
'categories' => array(
'nok' => 'Таблица категорий настроена неправильно.',
'ok' => 'Таблица категорий настроена правильно.',
),
'connection' => array(
'nok' => 'Подключение к базе данных не может быть установлено.',
'ok' => 'Подключение к базе данных в порядке.',
),
'ctype' => array(
'nok' => 'У вас не установлена библиотека для проверки типов символов (php-ctype).',
'ok' => 'У вас не установлена библиотека для проверки типов символов (ctype).',
),
'curl' => array(
'nok' => 'У вас не установлено расширение cURL (пакет php5-curl).',
'ok' => 'У вас установлено расширение cURL.',
),
'data' => array(
'nok' => 'Проверьте права доступа к папке <em>./data</em> . Сервер HTTP должен иметь права на запись в эту папку.',
'ok' => 'Права на <em>./data/</em> в порядке.',
),
'database' => 'Установка базы данных',
'dom' => array(
'nok' => 'У вас не установлена библиотека для просмотра DOM (пакет php-xml).',
'ok' => 'У вас установлена библиотека для просмотра DOM.',
),
'entries' => array(
'nok' => 'Таблица статей (entry) неправильно настроена.',
'ok' => 'Таблица статей (entry) настроена правильно.',
),
'favicons' => array(
'nok' => 'Проверьте права доступа к папке <em>./data/favicons</em> . Сервер HTTP должен иметь права на запись в эту папку.',
'ok' => 'Права на папку значков в порядке.',
),
'feeds' => array(
'nok' => 'Таблица подписок (feed) неправильно настроена.',
'ok' => 'Таблица подписок (feed) настроена правильно.',
),
'files' => 'Установка файлов',
'json' => array(
'nok' => 'У вас не установлена библиотека для работы с JSON (пакет php5-json).',
'ok' => 'У вас установлена библиотека для работы с JSON.',
),
'minz' => array(
'nok' => 'У вас не установлен фрейворк Minz.',
'ok' => 'У вас установлен фрейворк Minz.',
),
'pcre' => array(
'nok' => 'У вас не установлена необходимая библиотека для работы с регулярными выражениями (php-pcre).',
'ok' => 'У вас установлена необходимая библиотека для работы с регулярными выражениями (PCRE).',
),
'pdo' => array(
'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite).',
'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite).',
),
'persona' => array(
'nok' => 'Проверьте права доступа к папке <em>./data/persona</em> . Сервер HTTP должен иметь права на запись в эту папку.',
'ok' => 'Права на папку Mozilla Persona в порядке.',
),
'php' => array(
'_' => 'PHP installation',
'nok' => 'У вас установлен PHP версии %s, но FreshRSS необходима версия не ниже %s.',
'ok' => 'У вас установлен PHP версии %s, который совместим с FreshRSS.',
),
'tables' => array(
'nok' => 'В базе данных отсуствует одна или больше таблица.',
'ok' => 'Все таблицы есть в базе данных.',
),
'title' => 'Проверка установки и настройки',
'tokens' => array(
'nok' => 'Проверьте права доступа к папке <em>./data/tokens</em> . Сервер HTTP должен иметь права на запись в эту папку.',
'ok' => 'Права на папку tokens в порядке.',
),
'users' => array(
'nok' => 'Проверьте права доступа к папке <em>./data/users</em> . Сервер HTTP должен иметь права на запись в эту папку.',
'ok' => 'Права на папку users в порядке.',
),
'zip' => array(
'nok' => 'You lack ZIP extension (php5-zip package).',
'ok' => 'You have ZIP extension.',
),
),
'extensions' => array(
'disabled' => 'Отключены',
'empty_list' => 'Расширения не установлены',
'enabled' => 'Включены',
'no_configure_view' => 'Это расширение нельзя настроить.',
'system' => array(
'_' => 'Системные расширения',
'no_rights' => 'Системные расширения (у вас нет к ним доступа)',
),
'title' => 'Расширения',
'user' => 'Расширения пользователя',
),
'stats' => array(
'_' => 'Статистика',
'all_feeds' => 'Все подписки',
'category' => 'Категория',
'entry_count' => 'Количество статей',
'entry_per_category' => 'Статей в категории',
'entry_per_day' => 'Статей за день (за последние 30 дней)',
'entry_per_day_of_week' => 'За неделю (в среднем - %.2f сообщений)',
'entry_per_hour' => 'За час (в среднем - %.2f сообщений)',
'entry_per_month' => 'За месяц (в среднем - %.2f сообщений)',
'entry_repartition' => 'Перерасределение статей',
'feed' => 'Подписка',
'feed_per_category' => 'Подписок в категории',
'idle' => 'Неактивные подписки',
'main' => 'Основная статистика',
'main_stream' => 'Основной поток',
'menu' => array(
'idle' => 'Неактивные подписки',
'main' => 'Основная статистика',
'repartition' => 'Перерасределение статей',
),
'no_idle' => 'Нет неактивных подписок!',
'number_entries' => 'статей: %d',
'percent_of_total' => '%% от всего',
'repartition' => 'Перераспределение статей',
'status_favorites' => 'Избранное',
'status_read' => 'Читать',
'status_total' => 'Всего',
'status_unread' => 'Не прочитано',
'title' => 'Статистика',
'top_feed' => '10 лучших подписок',
),
'system' => array(
'_' => 'Системные настройки',
'auto-update-url' => 'Адрес сервера для автоматического обновления',
'instance-name' => 'Название этого сервера',
'max-categories' => 'Количество категорий на пользователя',
'max-feeds' => 'Количество статей на пользователя',
'registration' => array(
'help' => '0 означает неограниченное количество пользователей',
'number' => 'Максимальное количество пользователей',
),
),
'update' => array(
'_' => 'Обновление системы',
'apply' => 'Применить',
'check' => 'Проверить обновления',
'current_version' => 'Ваша текущая версия FreshRSS: %s.',
'last' => 'Последняя проверка: %s',
'none' => 'Нечего обновлять',
'title' => 'Обновить систему',
),
'user' => array(
'articles_and_size' => '%s статей (%s)',
'create' => 'Создать нового пользователя',
'email_persona' => 'Адрес электронной почты для входа<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Язык',
'number' => 'На данный момент создан %d аккаунт',
'numbers' => 'На данный момент аккаунтов создано: %d',
'password_form' => 'Пароль<br /><small>(для входа через Веб-форму)</small>',
'password_format' => 'Минимум 7 символов',
'title' => 'Управление пользователями',
'user_list' => 'Список пользователей',
'username' => 'Имя пользователя',
'users' => 'Пользователи',
),
);

View file

@ -1,174 +0,0 @@
<?php
return array(
'archiving' => array(
'_' => 'Архивация',
'advanced' => 'Продвинутые настройки',
'delete_after' => 'Удалять статьи после',
'help' => 'Каждую подписку можно настроить более гибко',
'keep_history_by_feed' => 'Minimum number of articles to keep by feed',
'optimize' => 'Оптимизировать базу данных',
'optimize_help' => 'To do occasionally to reduce the size of the database',
'purge_now' => 'Очистить сейчас',
'title' => 'Архивация',
'ttl' => 'Не обновлять чаще чем',
),
'display' => array(
'_' => 'Display',
'icon' => array(
'bottom_line' => 'Bottom line',
'entry' => 'Article icons',
'publication_date' => 'Date of publication',
'related_tags' => 'Related tags',
'sharing' => 'Sharing',
'top_line' => 'Top line',
),
'language' => 'Язык',
'notif_html5' => array(
'seconds' => 'seconds (0 means no timeout)',
'timeout' => 'HTML5 notification timeout',
),
'theme' => 'Тема',
'title' => 'Display',
'width' => array(
'content' => 'Content width',
'large' => 'Large',
'medium' => 'Medium',
'no_limit' => 'No limit',
'thin' => 'Thin',
),
),
'query' => array(
'_' => 'User queries',
'deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.',
'filter' => 'Filter applied:',
'get_all' => 'Display all articles',
'get_category' => 'Display "%s" category',
'get_favorite' => 'Display favorite articles',
'get_feed' => 'Display "%s" feed',
'no_filter' => 'No filter',
'none' => 'You havent created any user query yet.',
'number' => 'Query n°%d',
'order_asc' => 'Display oldest articles first',
'order_desc' => 'Display newest articles first',
'search' => 'Search for "%s"',
'state_0' => 'Display all articles',
'state_1' => 'Display read articles',
'state_2' => 'Display unread articles',
'state_3' => 'Display all articles',
'state_4' => 'Display favorite articles',
'state_5' => 'Display read favorite articles',
'state_6' => 'Display unread favorite articles',
'state_7' => 'Display favorite articles',
'state_8' => 'Display not favorite articles',
'state_9' => 'Display read not favorite articles',
'state_10' => 'Display unread not favorite articles',
'state_11' => 'Display not favorite articles',
'state_12' => 'Display all articles',
'state_13' => 'Display read articles',
'state_14' => 'Display unread articles',
'state_15' => 'Display all articles',
'title' => 'User queries',
),
'profile' => array(
'_' => 'Profile management',
'delete' => array(
'_' => 'Account deletion',
'warn' => 'Your account and all the related data will be deleted.',
),
'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
'title' => 'Profile',
),
'reading' => array(
'_' => 'Reading',
'after_onread' => 'After “mark all as read”,',
'articles_per_page' => 'Number of articles per page',
'auto_load_more' => 'Load next articles at the page bottom',
'auto_remove_article' => 'Hide articles after reading',
'mark_updated_article_unread' => 'Mark updated articles as unread',
'confirm_enabled' => 'Display a confirmation dialog on “mark all as read” actions',
'display_articles_unfolded' => 'Show articles unfolded by default',
'display_categories_unfolded' => 'Show categories folded by default',
'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)',
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
'jump_next' => 'jump to next unread sibling (feed or category)',
'number_divided_when_reader' => 'Divided by 2 in the reading view.',
'read' => array(
'article_open_on_website' => 'when article is opened on its original website',
'article_viewed' => 'when article is viewed',
'scroll' => 'while scrolling',
'upon_reception' => 'upon reception of the article',
'when' => 'Mark article as read…',
),
'show' => array(
'_' => 'Articles to display',
'adaptive' => 'Adjust showing',
'all_articles' => 'Show all articles',
'unread' => 'Show only unread',
),
'sort' => array(
'_' => 'Sort order',
'newer_first' => 'Newer first',
'older_first' => 'Oldest first',
),
'sticky_post' => 'Stick the article to the top when opened',
'title' => 'Reading',
'view' => array(
'default' => 'Default view',
'global' => 'Global view',
'normal' => 'Normal view',
'reader' => 'Reading view',
),
),
'sharing' => array(
'_' => 'Sharing',
'blogotext' => 'Blogotext',
'diaspora' => 'Diaspora*',
'email' => 'Email',
'facebook' => 'Facebook',
'g+' => 'Google+',
'more_information' => 'More information',
'print' => 'Print',
'shaarli' => 'Shaarli',
'share_name' => 'Share name to display',
'share_url' => 'Share URL to use',
'title' => 'Sharing',
'twitter' => 'Twitter',
'wallabag' => 'wallabag',
),
'shortcut' => array(
'_' => 'Shortcuts',
'article_action' => 'Article actions',
'auto_share' => 'Share',
'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
'close_dropdown' => 'Close menus',
'collapse_article' => 'Collapse',
'first_article' => 'Skip to the first article',
'focus_search' => 'Access search box',
'help' => 'Display documentation',
'javascript' => 'JavaScript must be enabled in order to use shortcuts',
'last_article' => 'Skip to the last article',
'load_more' => 'Load more articles',
'mark_read' => 'Mark as read',
'mark_favorite' => 'Mark as favourite',
'navigation' => 'Navigation',
'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
'next_article' => 'Skip to the next article',
'other_action' => 'Other actions',
'previous_article' => 'Skip to the previous article',
'see_on_website' => 'See on original website',
'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
'title' => 'Shortcuts',
'user_filter' => 'Access user filters',
'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.',
),
'user' => array(
'articles_and_size' => '%s articles (%s)',
'current' => 'Current user',
'is_admin' => 'is administrator',
'users' => 'Users',
),
);

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