mirror of
https://github.com/YunoHost-Apps/freshrss_ynh.git
synced 2024-09-03 18:36:33 +02:00
Initial Commit
This commit is contained in:
commit
c441bb3666
287 changed files with 52283 additions and 0 deletions
25
conf/config.php
Normal file
25
conf/config.php
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
return array (
|
||||||
|
'general' =>
|
||||||
|
array (
|
||||||
|
'environment' => 'production',
|
||||||
|
'salt' => 'yunosalt',
|
||||||
|
'base_url' => 'yunopath',
|
||||||
|
'title' => 'FreshRSS',
|
||||||
|
'default_user' => 'yunoadminuser',
|
||||||
|
'allow_anonymous' => false,
|
||||||
|
'allow_anonymous_refresh' => false,
|
||||||
|
'auth_type' => 'http',
|
||||||
|
'api_enabled' => true,
|
||||||
|
'unsafe_autologin_enabled' => false,
|
||||||
|
),
|
||||||
|
'db' =>
|
||||||
|
array (
|
||||||
|
'type' => 'mysql',
|
||||||
|
'host' => 'localhost',
|
||||||
|
'user' => 'yunouser',
|
||||||
|
'password' => 'yunopass',
|
||||||
|
'base' => 'yunobase',
|
||||||
|
'prefix' => false,
|
||||||
|
),
|
||||||
|
);
|
61
conf/dist_user.conf
Normal file
61
conf/dist_user.conf
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php
|
||||||
|
return array (
|
||||||
|
'language' => 'en',
|
||||||
|
'old_entries' => 3,
|
||||||
|
'keep_history_default' => 0,
|
||||||
|
'ttl_default' => 3600,
|
||||||
|
'mail_login' => 'ynoUserEmail',
|
||||||
|
'token' => '',
|
||||||
|
'passwordHash' => '',
|
||||||
|
'apiPasswordHash' => '',
|
||||||
|
'posts_per_page' => 20,
|
||||||
|
'view_mode' => 'normal',
|
||||||
|
'default_view' => 2,
|
||||||
|
'auto_load_more' => true,
|
||||||
|
'display_posts' => true,
|
||||||
|
'onread_jump_next' => true,
|
||||||
|
'lazyload' => true,
|
||||||
|
'sticky_post' => true,
|
||||||
|
'reading_confirm' => false,
|
||||||
|
'sort_order' => 'DESC',
|
||||||
|
'anon_access' => false,
|
||||||
|
'mark_when' =>
|
||||||
|
array (
|
||||||
|
'article' => true,
|
||||||
|
'site' => true,
|
||||||
|
'scroll' => false,
|
||||||
|
'reception' => false,
|
||||||
|
),
|
||||||
|
'theme' => 'Flat',
|
||||||
|
'content_width' => 'thin',
|
||||||
|
'shortcuts' =>
|
||||||
|
array (
|
||||||
|
'mark_read' => 'r',
|
||||||
|
'mark_favorite' => 'f',
|
||||||
|
'go_website' => 'space',
|
||||||
|
'next_entry' => 'n',
|
||||||
|
'prev_entry' => 'k',
|
||||||
|
'first_entry' => 'home',
|
||||||
|
'last_entry' => 'end',
|
||||||
|
'collapse_entry' => 'c',
|
||||||
|
'load_more' => 'm',
|
||||||
|
'auto_share' => 's',
|
||||||
|
'focus_search' => 'a',
|
||||||
|
),
|
||||||
|
'topline_read' => true,
|
||||||
|
'topline_favorite' => true,
|
||||||
|
'topline_date' => true,
|
||||||
|
'topline_link' => true,
|
||||||
|
'bottomline_read' => true,
|
||||||
|
'bottomline_favorite' => true,
|
||||||
|
'bottomline_sharing' => true,
|
||||||
|
'bottomline_tags' => true,
|
||||||
|
'bottomline_date' => true,
|
||||||
|
'bottomline_link' => true,
|
||||||
|
'sharing' =>
|
||||||
|
array (),
|
||||||
|
'queries' =>
|
||||||
|
array (
|
||||||
|
),
|
||||||
|
'user' => 'YnoUser',
|
||||||
|
);
|
20
conf/nginx.conf
Normal file
20
conf/nginx.conf
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
location PATHTOCHANGE {
|
||||||
|
alias ALIASTOCHANGE;
|
||||||
|
if ($scheme = http) {
|
||||||
|
rewrite ^ https://$server_name$request_uri? permanent;
|
||||||
|
}
|
||||||
|
index index.php;
|
||||||
|
try_files $uri $uri/ /index.php?$args;
|
||||||
|
location ~ [^/]\.php(/|$) {
|
||||||
|
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
|
||||||
|
fastcgi_pass unix:/var/run/php5-fpm.sock;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param AUTH_USER $remote_user;
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Include SSOWAT user panel.
|
||||||
|
include conf.d/yunohost_panel.conf.inc;
|
||||||
|
}
|
42
manifest.json
Normal file
42
manifest.json
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "FreshRSS",
|
||||||
|
"id": "freshrss",
|
||||||
|
"description": {
|
||||||
|
"en": "FreshRSS is a selfhostable RSS reader",
|
||||||
|
"fr": "FreshRSS est un agrégateur de flux RSS à auto-héberger"
|
||||||
|
},
|
||||||
|
"developer": {
|
||||||
|
"name": "plopoyop",
|
||||||
|
"email": "plopoyop@gmail.com"
|
||||||
|
},
|
||||||
|
"multi_instance": "true",
|
||||||
|
"arguments": {
|
||||||
|
"install" : [
|
||||||
|
{
|
||||||
|
"name": "domain",
|
||||||
|
"ask": {
|
||||||
|
"en": "Choose a domain for FreshRSS",
|
||||||
|
"fr": "Choisissez un domaine pour FreshRSS"
|
||||||
|
},
|
||||||
|
"example": "domain.org"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"ask": {
|
||||||
|
"en": "Choose a path for FreshRSS",
|
||||||
|
"fr": "Choisissez un chemin pour FreshRSS"
|
||||||
|
},
|
||||||
|
"example": "/rss",
|
||||||
|
"default": "/rss"
|
||||||
|
}
|
||||||
|
{
|
||||||
|
"name": "admin",
|
||||||
|
"ask": {
|
||||||
|
"en": "Choose the default user (must be an existing YunoHost user)"
|
||||||
|
"fr": "Choisissez l'utilisateur par defaut (doit être un utiliser YunoHost existant)"
|
||||||
|
},
|
||||||
|
"example": "homer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
81
scripts/install
Executable file
81
scripts/install
Executable file
|
@ -0,0 +1,81 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Retrieve arguments
|
||||||
|
domain=$1
|
||||||
|
path=$2
|
||||||
|
admin_user=$3
|
||||||
|
|
||||||
|
# Check domain/path availability
|
||||||
|
sudo yunohost app checkurl $domain$path
|
||||||
|
if [[ ! $? -eq 0 ]]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Generate random DES key & password
|
||||||
|
deskey=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d 'A-Za-z0-9' | sed -n 's/\(.\{24\}\).*/\1/p')
|
||||||
|
db_pwd=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d 'A-Za-z0-9' | sed -n 's/\(.\{24\}\).*/\1/p')
|
||||||
|
app_salt=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d 'A-Za-z0-9' | sed -n 's/\(.\{40\}\).*/\1/p')
|
||||||
|
# Use 'freshrss' as database name and user
|
||||||
|
db_user=freshrss
|
||||||
|
|
||||||
|
# Initialize database and store mysql password for upgrade
|
||||||
|
sudo yunohost app initdb $db_user -p $db_pwd
|
||||||
|
sudo yunohost app setting freshrss mysqlpwd -v $db_pwd
|
||||||
|
|
||||||
|
# Copy files to the right place
|
||||||
|
final_path=/var/www/freshrss
|
||||||
|
sudo mkdir -p $final_path
|
||||||
|
sudo cp -a ../sources/* $final_path
|
||||||
|
sudo cp -a ../conf/config.php $final_path/data
|
||||||
|
|
||||||
|
# Change variables in freshrss configuration
|
||||||
|
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/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/yunopath/$path/g" $final_path/data/config.php
|
||||||
|
sudo sed -i "s/yunoadminuser/$admin_user/g" $final_path/data/config.php
|
||||||
|
|
||||||
|
# Add users
|
||||||
|
freshrss_users=$(ldapsearch -h localhost -b ou=users,dc=yunohost,dc=org -x objectClass=mailAccount uid | grep uid: | sed 's/uid: //' | xargs)
|
||||||
|
for myuser in $freshrss_users
|
||||||
|
do
|
||||||
|
#copy sql
|
||||||
|
sudo cp ../sources/app/SQL/install_ynh.sql /tmp/$myuser-install.sql
|
||||||
|
#change username in sql
|
||||||
|
sudo sed -i "s/YnoUser/$myuser/g" /tmp/$myuser-install.sql
|
||||||
|
#create tables
|
||||||
|
mysql -u $db_user -p$db_pwd $db_user < /tmp/$myuser-install.sql
|
||||||
|
#remove temp sql
|
||||||
|
sudo rm /tmp/$myuser-install.sql
|
||||||
|
#copy default conf
|
||||||
|
sudo cp ../conf/dist_user.conf $final_path/data/$myuser\_user.php
|
||||||
|
#change email
|
||||||
|
user_email=$(ldapsearch -h localhost -b uid=$myuser,ou=users,dc=yunohost,dc=org -x objectClass=mailAccount mail | grep mail: | grep $myuser| sed 's/mail: //')
|
||||||
|
sudo sed -i "s/ynoUserEmail/$user_email/g" $final_path/data/$myuser\_user.php
|
||||||
|
#change username
|
||||||
|
sudo sed -i "s/YnoUser/$myuser/g" $final_path/data/$myuser\_user.php
|
||||||
|
done
|
||||||
|
|
||||||
|
# Delete install directive
|
||||||
|
sudo rm $final_path/data/do-install.txt
|
||||||
|
|
||||||
|
# Modify Nginx configuration file and copy it to Nginx conf directory
|
||||||
|
sed -i "s@PATHTOCHANGE@$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
|
||||||
|
|
||||||
|
#install php5-cli
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y php5-cli
|
||||||
|
#install update cron
|
||||||
|
echo "*/10 * * * * www-data /usr/bin/php $final_path/app/actualize_script.php >/tmp/FreshRSS.log 2>&1" > /tmp/cronfreshrss
|
||||||
|
sudo mv /tmp/cronttrss /etc/cron.d/freshrss
|
||||||
|
sudo chown root /etc/cron.d/freshrss
|
||||||
|
|
||||||
|
# Set permissions to freshrss directory
|
||||||
|
sudo chown -R www-data: $final_path
|
||||||
|
|
||||||
|
# Reload Nginx and regenerate SSOwat conf
|
||||||
|
sudo service nginx reload
|
||||||
|
sudo yunohost app ssowatconf
|
12
scripts/remove
Executable file
12
scripts/remove
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
db_user=freshrss
|
||||||
|
db_name=freshrss
|
||||||
|
root_pwd=$(sudo cat /etc/yunohost/mysql)
|
||||||
|
domain=$(sudo yunohost app setting roundcube domain)
|
||||||
|
|
||||||
|
mysql -u root -p$root_pwd -e "DROP DATABASE $db_name ; DROP USER $db_user@localhost ;"
|
||||||
|
sudo rm -rf /var/www/freshrss
|
||||||
|
sudo rm -f /etc/nginx/conf.d/$domain.d/freshrss.conf
|
||||||
|
|
||||||
|
sudo service nginx reload
|
0
scripts/upgrade
Normal file
0
scripts/upgrade
Normal file
280
sources/CHANGELOG
Executable file
280
sources/CHANGELOG
Executable file
|
@ -0,0 +1,280 @@
|
||||||
|
# Journal des modifications
|
||||||
|
|
||||||
|
## 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 d’une signature MD5 du contenu intéressant pour les flux n’implé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
|
||||||
|
* L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs
|
||||||
|
* Nécessite un contrôle d’accès, soit :
|
||||||
|
* par le nouveau mode de connexion par formulaire (nom d’utilisateur + mot de passe)
|
||||||
|
* relativement sûr même sans HTTPS (le mot de passe n’est 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 d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS
|
||||||
|
* par Mozilla Persona, en renseignant l’adresse 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 d’articles 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 d’articles 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 d’un flux
|
||||||
|
* Option pour marquer les articles comme lus dès la réception
|
||||||
|
* Permet de configurer plus finement le nombre d’articles minimum à conserver par flux
|
||||||
|
* Permet de modifier la description et l’adresse d’un 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 d’affichage : 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 d’utilisateur afin de permettre le mode multi-utilisateurs
|
||||||
|
* Amélioration des performances
|
||||||
|
* Tolère un beaucoup plus grand nombre d’articles
|
||||||
|
* 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 à l’installateur)
|
||||||
|
* 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 d’articles 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 l’URL du flux (en base de données) lorsque SimplePie découvre qu’elle 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 d’absence 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 d’actualisation 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 d’erreur plus explicite si fichier de configuration inaccessible
|
||||||
|
|
||||||
|
|
||||||
|
## 2013-11-17 FreshRSS 0.6
|
||||||
|
|
||||||
|
* Nettoyage du code JavaScript + optimisations
|
||||||
|
* Utilisation d’adresses relatives
|
||||||
|
* Amélioration des performances coté client
|
||||||
|
* Mise à jour automatique du nombre d’articles non lus
|
||||||
|
* Corrections traductions
|
||||||
|
* Mise en cache de FreshRSS
|
||||||
|
* Amélioration des retours utilisateur lorsque la configuration n’est 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 l’installation (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é d’interdire la lecture anonyme
|
||||||
|
* Option pour garder l’historique d’un flux
|
||||||
|
* Lors d’un 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 d’un 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 l’imprimante
|
||||||
|
* Quelques retouches du design par défaut
|
||||||
|
* Les vidéos ne dépassent plus du cadre de l’écran
|
||||||
|
* Nouveau logo
|
||||||
|
* Possibilité d’ajouter un préfixe aux tables lors de l’installation
|
||||||
|
* Ajout d’un champ en base de données keep_history à la table feed
|
||||||
|
* Si possible, création automatique de la base de données si elle n’existe pas lors de l’installation
|
||||||
|
* L’utilisation d’UTF-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 d’installation
|
||||||
|
* Affichage d’erreur si fichier OPML invalide
|
||||||
|
* Les tags sont maintenant cliquables pour filtrer dessus
|
||||||
|
* Amélioration vue mobile (boutons plus gros et ajout d’une barre de navigation)
|
||||||
|
* Possibilité d’ajouter 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 d’une option (désactivable donc) pour charger les images en lazyload permettant de ne pas charger toutes les images d’un coup
|
||||||
|
* Le framework Minz est maintenant directement inclus dans l’archive (plus besoin de passer par ./build.sh)
|
||||||
|
* Amélioration des performances pour la récupération des flux tronqués
|
||||||
|
* Possibilité d’importer des flux sans catégorie lors de l’import OPML
|
||||||
|
* Suppression de “l’API” (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 l’on 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 d’une page de visualisation des logs
|
||||||
|
* Ajout d’une 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 d’une 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 l’application (gestion des langues anglaise et française)
|
||||||
|
* Gestion des flux protégés par authentification HTTP
|
||||||
|
* Mise en cache des favicons
|
||||||
|
* Création d’un logo *temporaire*
|
||||||
|
* Affichage des vidéos dans les articles
|
||||||
|
* Gestion de la recherche et filtre par tags pleinement fonctionnels
|
||||||
|
* Création d’un vrai script CRON permettant de mettre tous les flux à jour
|
||||||
|
* Correction bugs divers
|
||||||
|
|
||||||
|
|
||||||
|
## 2013-04-17 FreshRSS 0.2.0
|
||||||
|
|
||||||
|
* Création d’un 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
|
662
sources/LICENSE
Executable file
662
sources/LICENSE
Executable file
|
@ -0,0 +1,662 @@
|
||||||
|
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/>.
|
||||||
|
|
100
sources/README.md
Executable file
100
sources/README.md
Executable file
|
@ -0,0 +1,100 @@
|
||||||
|
# FreshRSS
|
||||||
|
FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image 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 d’un mode de lecture anonyme.
|
||||||
|
|
||||||
|
* Site officiel : http://freshrss.org
|
||||||
|
* Démo : http://demo.freshrss.org/
|
||||||
|
* Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
|
||||||
|
* Version actuelle : 0.7.3
|
||||||
|
* Date de publication 2014-07-21
|
||||||
|
* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Note sur les branches
|
||||||
|
**Ce logiciel est encore en développement !** Veuillez vous assurer d'utiliser la branche qui vous correspond :
|
||||||
|
|
||||||
|
* Utilisez [la branche master](https://github.com/marienfressinaud/FreshRSS/tree/master/) si vous visez la stabilité.
|
||||||
|
* [La branche beta](https://github.com/marienfressinaud/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois.
|
||||||
|
* Pour les développeurs et ceux qui savent ce qu'ils font, [la branche dev](https://github.com/marienfressinaud/FreshRSS/tree/dev) vous ouvre les bras !
|
||||||
|
|
||||||
|
# Disclaimer
|
||||||
|
Cette application a été développée pour s’adapter à des besoins personnels et non professionnels.
|
||||||
|
Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
|
||||||
|
Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées.
|
||||||
|
Privilégiez pour cela des demandes sur GitHub
|
||||||
|
(https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr)
|
||||||
|
|
||||||
|
# Pré-requis
|
||||||
|
* Serveur modeste, par exemple sous Linux ou Windows
|
||||||
|
* Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées)
|
||||||
|
* Serveur Web Apache2 ou Nginx (non testé sur les autres)
|
||||||
|
* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
|
||||||
|
* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
|
||||||
|
* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv), [Zip](http://php.net/zip)
|
||||||
|
* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ (en bêta)
|
||||||
|
* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
|
||||||
|
* Fonctionne aussi sur mobile
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Installation
|
||||||
|
1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
|
||||||
|
2. Placez l’application 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 d’installation
|
||||||
|
5. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter.
|
||||||
|
|
||||||
|
# Contrôle d’accès
|
||||||
|
Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter l’accès à votre FreshRSS. Au choix :
|
||||||
|
* En utilisant l’identification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé – fonctionne avec certaines versions de PHP 5.3.3+)
|
||||||
|
* En utilisant l’identification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
|
||||||
|
* En utilisant un contrôle d’accès HTTP défini par votre serveur Web
|
||||||
|
* Voir par exemple la [documentation d’Apache sur l’authentification](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 d’actualisation automatique des flux.
|
||||||
|
Consultez la documentation de Cron de votre système d’exploitation ([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)…).
|
||||||
|
C’est une bonne idée d’utiliser le même utilisateur que votre serveur Web (souvent “www-data”).
|
||||||
|
Par exemple, pour exécuter le script toutes les heures :
|
||||||
|
|
||||||
|
```
|
||||||
|
7 * * * * php /chemin/vers/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 d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici.
|
||||||
|
* En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface 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ême, 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/)
|
||||||
|
* [Lazy Load](http://www.appelsiini.net/projects/lazyload)
|
||||||
|
|
||||||
|
## 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)
|
3
sources/app/.htaccess
Executable file
3
sources/app/.htaccess
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
Order Allow,Deny
|
||||||
|
Deny from all
|
||||||
|
Satisfy all
|
382
sources/app/Controllers/configureController.php
Executable file
382
sources/app/Controllers/configureController.php
Executable file
|
@ -0,0 +1,382 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_configure_Controller extends Minz_ActionController {
|
||||||
|
public function firstAction() {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error(
|
||||||
|
403,
|
||||||
|
array('error' => array(Minz_Translate::t('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$catDAO->checkDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function categorizeAction() {
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$defaultCategory = $catDAO->getDefault();
|
||||||
|
$defaultId = $defaultCategory->id();
|
||||||
|
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$cats = Minz_Request::param('categories', array());
|
||||||
|
$ids = Minz_Request::param('ids', array());
|
||||||
|
$newCat = trim(Minz_Request::param('new_category', ''));
|
||||||
|
|
||||||
|
foreach ($cats as $key => $name) {
|
||||||
|
if (strlen($name) > 0) {
|
||||||
|
$cat = new FreshRSS_Category($name);
|
||||||
|
$values = array(
|
||||||
|
'name' => $cat->name(),
|
||||||
|
);
|
||||||
|
$catDAO->updateCategory($ids[$key], $values);
|
||||||
|
} elseif ($ids[$key] != $defaultId) {
|
||||||
|
$feedDAO->changeCategory($ids[$key], $defaultId);
|
||||||
|
$catDAO->deleteCategory($ids[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($newCat != '') {
|
||||||
|
$cat = new FreshRSS_Category($newCat);
|
||||||
|
$values = array(
|
||||||
|
'id' => $cat->id(),
|
||||||
|
'name' => $cat->name(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($catDAO->searchByName($newCat) == null) {
|
||||||
|
$catDAO->addCategory($values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('categories_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'categorize'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->categories = $catDAO->listCategories(false);
|
||||||
|
$this->view->defaultCategory = $catDAO->getDefault();
|
||||||
|
$this->view->feeds = $feedDAO->listFeeds();
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('categories_management') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feedAction() {
|
||||||
|
$catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$this->view->categories = $catDAO->listCategories(false);
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$this->view->feeds = $feedDAO->listFeeds();
|
||||||
|
|
||||||
|
$id = Minz_Request::param('id');
|
||||||
|
if ($id == false && !empty($this->view->feeds)) {
|
||||||
|
$id = current($this->view->feeds)->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->flux = false;
|
||||||
|
if ($id != false) {
|
||||||
|
$this->view->flux = $this->view->feeds[$id];
|
||||||
|
|
||||||
|
if (!$this->view->flux) {
|
||||||
|
Minz_Error::error(
|
||||||
|
404,
|
||||||
|
array('error' => array(Minz_Translate::t('page_not_found')))
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (Minz_Request::isPost() && $this->view->flux) {
|
||||||
|
$user = Minz_Request::param('http_user', '');
|
||||||
|
$pass = Minz_Request::param('http_pass', '');
|
||||||
|
|
||||||
|
$httpAuth = '';
|
||||||
|
if ($user != '' || $pass != '') {
|
||||||
|
$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)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($feedDAO->updateFeed($id, $values)) {
|
||||||
|
$this->view->flux->_category($cat);
|
||||||
|
$this->view->flux->faviconPrepare();
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('feed_updated')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t('error_occurred_update')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · ');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' · ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function displayAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$this->view->conf->_language(Minz_Request::param('language', 'en'));
|
||||||
|
$themeId = Minz_Request::param('theme', '');
|
||||||
|
if ($themeId == '') {
|
||||||
|
$themeId = FreshRSS_Themes::defaultTheme;
|
||||||
|
}
|
||||||
|
$this->view->conf->_theme($themeId);
|
||||||
|
$this->view->conf->_content_width(Minz_Request::param('content_width', 'thin'));
|
||||||
|
$this->view->conf->_topline_read(Minz_Request::param('topline_read', false));
|
||||||
|
$this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false));
|
||||||
|
$this->view->conf->_topline_date(Minz_Request::param('topline_date', false));
|
||||||
|
$this->view->conf->_topline_link(Minz_Request::param('topline_link', false));
|
||||||
|
$this->view->conf->_bottomline_read(Minz_Request::param('bottomline_read', false));
|
||||||
|
$this->view->conf->_bottomline_favorite(Minz_Request::param('bottomline_favorite', false));
|
||||||
|
$this->view->conf->_bottomline_sharing(Minz_Request::param('bottomline_sharing', false));
|
||||||
|
$this->view->conf->_bottomline_tags(Minz_Request::param('bottomline_tags', false));
|
||||||
|
$this->view->conf->_bottomline_date(Minz_Request::param('bottomline_date', false));
|
||||||
|
$this->view->conf->_bottomline_link(Minz_Request::param('bottomline_link', false));
|
||||||
|
$this->view->conf->save();
|
||||||
|
|
||||||
|
Minz_Session::_param('language', $this->view->conf->language);
|
||||||
|
Minz_Translate::reset();
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('configuration_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'display'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->themes = FreshRSS_Themes::get();
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('display_configuration') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readingAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
|
||||||
|
$this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
|
||||||
|
$this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL));
|
||||||
|
$this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
|
||||||
|
$this->view->conf->_display_posts(Minz_Request::param('display_posts', false));
|
||||||
|
$this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
|
||||||
|
$this->view->conf->_lazyload(Minz_Request::param('lazyload', false));
|
||||||
|
$this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false));
|
||||||
|
$this->view->conf->_reading_confirm(Minz_Request::param('reading_confirm', false));
|
||||||
|
$this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
|
||||||
|
$this->view->conf->_mark_when(array(
|
||||||
|
'article' => Minz_Request::param('mark_open_article', false),
|
||||||
|
'site' => Minz_Request::param('mark_open_site', false),
|
||||||
|
'scroll' => Minz_Request::param('mark_scroll', false),
|
||||||
|
'reception' => Minz_Request::param('mark_upon_reception', false),
|
||||||
|
));
|
||||||
|
$this->view->conf->save();
|
||||||
|
|
||||||
|
Minz_Session::_param('language', $this->view->conf->language);
|
||||||
|
Minz_Translate::reset();
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('configuration_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'reading'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('reading_configuration') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function sharingAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$params = Minz_Request::params();
|
||||||
|
$this->view->conf->_sharing($params['share']);
|
||||||
|
$this->view->conf->save();
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('configuration_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'sharing'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('sharing') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
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', '0', '1', '2', '3', '4', '5', '6', '7', '8',
|
||||||
|
'9', '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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->conf->_shortcuts($shortcuts_ok);
|
||||||
|
$this->view->conf->save();
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('shortcuts_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'shortcut'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('shortcuts') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function usersAction() {
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('users') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function archivingAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$old = Minz_Request::param('old_entries', 3);
|
||||||
|
$keepHistoryDefault = Minz_Request::param('keep_history_default', 0);
|
||||||
|
$ttlDefault = Minz_Request::param('ttl_default', -2);
|
||||||
|
|
||||||
|
$this->view->conf->_old_entries($old);
|
||||||
|
$this->view->conf->_keep_history_default($keepHistoryDefault);
|
||||||
|
$this->view->conf->_ttl_default($ttlDefault);
|
||||||
|
$this->view->conf->save();
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('configuration_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'archiving'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' · ');
|
||||||
|
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
$this->view->nb_total = $entryDAO->count();
|
||||||
|
$this->view->size_user = $entryDAO->size();
|
||||||
|
|
||||||
|
if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
|
||||||
|
$this->view->size_total = $entryDAO->size(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queriesAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$queries = Minz_Request::param('queries', array());
|
||||||
|
|
||||||
|
foreach ($queries as $key => $query) {
|
||||||
|
if (!$query['name']) {
|
||||||
|
$query['name'] = Minz_Translate::t('query_number', $key + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->view->conf->_queries($queries);
|
||||||
|
$this->view->conf->save();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('configuration_updated')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true);
|
||||||
|
} else {
|
||||||
|
$this->view->query_get = array();
|
||||||
|
foreach ($this->view->conf->queries as $key => $query) {
|
||||||
|
if (!isset($query['get'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($query['get'][0]) {
|
||||||
|
case 'c':
|
||||||
|
$dao = new FreshRSS_CategoryDAO();
|
||||||
|
$category = $dao->searchById(substr($query['get'], 2));
|
||||||
|
$this->view->query_get[$key] = array(
|
||||||
|
'type' => 'category',
|
||||||
|
'name' => $category->name(),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
$dao = FreshRSS_Factory::createFeedDao();
|
||||||
|
$feed = $dao->searchById(substr($query['get'], 2));
|
||||||
|
$this->view->query_get[$key] = array(
|
||||||
|
'type' => 'feed',
|
||||||
|
'name' => $feed->name(),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
$this->view->query_get[$key] = array(
|
||||||
|
'type' => 'favorite',
|
||||||
|
'name' => 'favorite',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
$this->view->query_get[$key] = array(
|
||||||
|
'type' => 'all',
|
||||||
|
'name' => 'all',
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('queries') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addQueryAction() {
|
||||||
|
$queries = $this->view->conf->queries;
|
||||||
|
$query = Minz_Request::params();
|
||||||
|
$query['name'] = Minz_Translate::t('query_number', count($queries) + 1);
|
||||||
|
unset($query['output']);
|
||||||
|
unset($query['token']);
|
||||||
|
$queries[] = $query;
|
||||||
|
$this->view->conf->_queries($queries);
|
||||||
|
$this->view->conf->save();
|
||||||
|
|
||||||
|
// Minz_Request::forward(array('params' => $query), true);
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true);
|
||||||
|
}
|
||||||
|
}
|
163
sources/app/Controllers/entryController.php
Executable file
163
sources/app/Controllers/entryController.php
Executable file
|
@ -0,0 +1,163 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_entry_Controller extends Minz_ActionController {
|
||||||
|
public function firstAction () {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error (
|
||||||
|
403,
|
||||||
|
array ('error' => array (Minz_Translate::t ('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->params = array ();
|
||||||
|
$output = Minz_Request::param('output', '');
|
||||||
|
if (($output != '') && ($this->view->conf->view_mode !== $output)) {
|
||||||
|
$this->params['output'] = $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect = false;
|
||||||
|
$ajax = Minz_Request::param ('ajax');
|
||||||
|
if ($ajax) {
|
||||||
|
$this->view->_useLayout (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastAction () {
|
||||||
|
$ajax = Minz_Request::param ('ajax');
|
||||||
|
if (!$ajax && $this->redirect) {
|
||||||
|
Minz_Request::forward (array (
|
||||||
|
'c' => 'index',
|
||||||
|
'a' => 'index',
|
||||||
|
'params' => $this->params
|
||||||
|
), true);
|
||||||
|
} else {
|
||||||
|
Minz_Request::_param ('ajax');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readAction () {
|
||||||
|
$this->redirect = true;
|
||||||
|
|
||||||
|
$id = Minz_Request::param ('id');
|
||||||
|
$get = Minz_Request::param ('get');
|
||||||
|
$nextGet = Minz_Request::param ('nextGet', $get);
|
||||||
|
$idMax = Minz_Request::param ('idMax', 0);
|
||||||
|
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
if ($id == false) {
|
||||||
|
if (!$get) {
|
||||||
|
$entryDAO->markReadEntries ($idMax);
|
||||||
|
} else {
|
||||||
|
$typeGet = $get[0];
|
||||||
|
$get = substr ($get, 2);
|
||||||
|
switch ($typeGet) {
|
||||||
|
case 'c':
|
||||||
|
$entryDAO->markReadCat ($get, $idMax);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
$entryDAO->markReadFeed ($get, $idMax);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
$entryDAO->markReadEntries ($idMax, true);
|
||||||
|
break;
|
||||||
|
case 'a':
|
||||||
|
$entryDAO->markReadEntries ($idMax);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($nextGet !== 'a') {
|
||||||
|
$this->params['get'] = $nextGet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('feeds_marked_read')
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
} else {
|
||||||
|
$is_read = (bool)(Minz_Request::param ('is_read', true));
|
||||||
|
$entryDAO->markRead ($id, $is_read);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bookmarkAction () {
|
||||||
|
$this->redirect = true;
|
||||||
|
|
||||||
|
$id = Minz_Request::param ('id');
|
||||||
|
if ($id) {
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
$entryDAO->markFavorite ($id, (bool)(Minz_Request::param ('is_favorite', true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function optimizeAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
@set_time_limit(300);
|
||||||
|
|
||||||
|
// La table des entrées a tendance à grossir énormément
|
||||||
|
// Cette action permet d'optimiser cette table permettant de grapiller un peu de place
|
||||||
|
// Cette fonctionnalité n'est à appeler qu'occasionnellement
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
$entryDAO->optimizeTable();
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$feedDAO->updateCachedValues();
|
||||||
|
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('optimization_complete')
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'configure',
|
||||||
|
'a' => 'archiving'
|
||||||
|
), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function purgeAction() {
|
||||||
|
@set_time_limit(300);
|
||||||
|
|
||||||
|
$nb_month_old = max($this->view->conf->old_entries, 1);
|
||||||
|
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$feeds = $feedDAO->listFeeds();
|
||||||
|
$nbTotal = 0;
|
||||||
|
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
foreach ($feeds as $feed) {
|
||||||
|
$feedHistory = $feed->keepHistory();
|
||||||
|
if ($feedHistory == -2) { //default
|
||||||
|
$feedHistory = $this->view->conf->keep_history_default;
|
||||||
|
}
|
||||||
|
if ($feedHistory >= 0) {
|
||||||
|
$nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feedHistory);
|
||||||
|
if ($nb > 0) {
|
||||||
|
$nbTotal += $nb;
|
||||||
|
Minz_Log::record($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG);
|
||||||
|
//$feedDAO->updateLastUpdate($feed->id());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$feedDAO->updateCachedValues();
|
||||||
|
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t('purge_completed', $nbTotal)
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'configure',
|
||||||
|
'a' => 'archiving'
|
||||||
|
), true);
|
||||||
|
}
|
||||||
|
}
|
26
sources/app/Controllers/errorController.php
Executable file
26
sources/app/Controllers/errorController.php
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_error_Controller extends Minz_ActionController {
|
||||||
|
public function indexAction () {
|
||||||
|
switch (Minz_Request::param ('code')) {
|
||||||
|
case 403:
|
||||||
|
$this->view->code = 'Error 403 - Forbidden';
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
$this->view->code = 'Error 404 - Not found';
|
||||||
|
break;
|
||||||
|
case 500:
|
||||||
|
$this->view->code = 'Error 500 - Internal Server Error';
|
||||||
|
break;
|
||||||
|
case 503:
|
||||||
|
$this->view->code = 'Error 503 - Service Unavailable';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$this->view->code = 'Error 404 - Not found';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->logs = Minz_Request::param ('logs');
|
||||||
|
|
||||||
|
Minz_View::prependTitle ($this->view->code . ' · ');
|
||||||
|
}
|
||||||
|
}
|
422
sources/app/Controllers/feedController.php
Executable file
422
sources/app/Controllers/feedController.php
Executable file
|
@ -0,0 +1,422 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_feed_Controller extends Minz_ActionController {
|
||||||
|
public function firstAction () {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
// Token is useful in the case that anonymous refresh is forbidden
|
||||||
|
// and CRON task cannot be used with php command so the user can
|
||||||
|
// set a CRON task to refresh his feeds by using token inside url
|
||||||
|
$token = $this->view->conf->token;
|
||||||
|
$token_param = Minz_Request::param ('token', '');
|
||||||
|
$token_is_ok = ($token != '' && $token == $token_param);
|
||||||
|
$action = Minz_Request::actionName ();
|
||||||
|
if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) &&
|
||||||
|
$action === 'actualize')
|
||||||
|
) {
|
||||||
|
Minz_Error::error (
|
||||||
|
403,
|
||||||
|
array ('error' => array (Minz_Translate::t ('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAction () {
|
||||||
|
$url = Minz_Request::param('url_rss', false);
|
||||||
|
|
||||||
|
if ($url === false) {
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'configure',
|
||||||
|
'a' => 'feed'
|
||||||
|
), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$this->catDAO = new FreshRSS_CategoryDAO ();
|
||||||
|
$this->catDAO->checkDefault ();
|
||||||
|
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
@set_time_limit(300);
|
||||||
|
|
||||||
|
|
||||||
|
$cat = Minz_Request::param ('category', false);
|
||||||
|
if ($cat === 'nc') {
|
||||||
|
$new_cat = Minz_Request::param ('new_category');
|
||||||
|
if (empty($new_cat['name'])) {
|
||||||
|
$cat = false;
|
||||||
|
} else {
|
||||||
|
$cat = $this->catDAO->addCategory($new_cat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($cat === false) {
|
||||||
|
$def_cat = $this->catDAO->getDefault ();
|
||||||
|
$cat = $def_cat->id ();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = Minz_Request::param ('http_user');
|
||||||
|
$pass = Minz_Request::param ('http_pass');
|
||||||
|
$params = array ();
|
||||||
|
|
||||||
|
$transactionStarted = false;
|
||||||
|
try {
|
||||||
|
$feed = new FreshRSS_Feed ($url);
|
||||||
|
$feed->_category ($cat);
|
||||||
|
|
||||||
|
$httpAuth = '';
|
||||||
|
if ($user != '' || $pass != '') {
|
||||||
|
$httpAuth = $user . ':' . $pass;
|
||||||
|
}
|
||||||
|
$feed->_httpAuth ($httpAuth);
|
||||||
|
|
||||||
|
$feed->load(true);
|
||||||
|
|
||||||
|
$values = array (
|
||||||
|
'url' => $feed->url (),
|
||||||
|
'category' => $feed->category (),
|
||||||
|
'name' => $feed->name (),
|
||||||
|
'website' => $feed->website (),
|
||||||
|
'description' => $feed->description (),
|
||||||
|
'lastUpdate' => time (),
|
||||||
|
'httpAuth' => $feed->httpAuth (),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($feedDAO->searchByUrl ($values['url'])) {
|
||||||
|
// on est déjà abonné à ce flux
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('already_subscribed', $feed->name ())
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
} else {
|
||||||
|
$id = $feedDAO->addFeed ($values);
|
||||||
|
if (!$id) {
|
||||||
|
// problème au niveau de la base de données
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('feed_not_added', $feed->name ())
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
} else {
|
||||||
|
$feed->_id ($id);
|
||||||
|
$feed->faviconPrepare();
|
||||||
|
|
||||||
|
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
|
||||||
|
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
$entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
|
||||||
|
|
||||||
|
// on calcule la date des articles les plus anciens qu'on accepte
|
||||||
|
$nb_month_old = $this->view->conf->old_entries;
|
||||||
|
$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
|
||||||
|
|
||||||
|
//MySQL: http://docs.oracle.com/cd/E17952_01/refman-5.5-en/optimizing-innodb-transaction-management.html
|
||||||
|
//SQLite: http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
|
||||||
|
$preparedStatement = $entryDAO->addEntryPrepare();
|
||||||
|
$transactionStarted = true;
|
||||||
|
$feedDAO->beginTransaction();
|
||||||
|
// on ajoute les articles en masse sans vérification
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$values = $entry->toArray();
|
||||||
|
$values['id_feed'] = $feed->id();
|
||||||
|
$values['id'] = min(time(), $entry->date(true)) . uSecString();
|
||||||
|
$values['is_read'] = $is_read;
|
||||||
|
$entryDAO->addEntry($values, $preparedStatement);
|
||||||
|
}
|
||||||
|
$feedDAO->updateLastUpdate($feed->id());
|
||||||
|
if ($transactionStarted) {
|
||||||
|
$feedDAO->commit();
|
||||||
|
}
|
||||||
|
$transactionStarted = false;
|
||||||
|
|
||||||
|
// ok, ajout terminé
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('feed_added', $feed->name ())
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
|
||||||
|
// permet de rediriger vers la page de conf du flux
|
||||||
|
$params['id'] = $feed->id ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (FreshRSS_BadUrl_Exception $e) {
|
||||||
|
Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('invalid_url', $url)
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
} catch (FreshRSS_Feed_Exception $e) {
|
||||||
|
Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
} catch (Minz_FileNotExistException $e) {
|
||||||
|
// Répertoire de cache n'existe pas
|
||||||
|
Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
}
|
||||||
|
if ($transactionStarted) {
|
||||||
|
$feedDAO->rollBack ();
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// GET request so we must ask confirmation to user
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · ');
|
||||||
|
$this->view->categories = $this->catDAO->listCategories();
|
||||||
|
$this->view->feed = new FreshRSS_Feed($url);
|
||||||
|
try {
|
||||||
|
// We try to get some more information about the feed
|
||||||
|
$this->view->feed->load(true);
|
||||||
|
$this->view->load_ok = true;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->view->load_ok = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$feed = $feedDAO->searchByUrl($this->view->feed->url());
|
||||||
|
if ($feed) {
|
||||||
|
// Already subscribe so we redirect to the feed configuration page
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t(
|
||||||
|
'already_subscribed', $feed->name()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'configure',
|
||||||
|
'a' => 'feed',
|
||||||
|
'params' => array(
|
||||||
|
'id' => $feed->id()
|
||||||
|
)
|
||||||
|
), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function truncateAction () {
|
||||||
|
if (Minz_Request::isPost ()) {
|
||||||
|
$id = Minz_Request::param ('id');
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$n = $feedDAO->truncate($id);
|
||||||
|
$notif = array(
|
||||||
|
'type' => $n === false ? 'bad' : 'good',
|
||||||
|
'content' => Minz_Translate::t ('n_entries_deleted', $n)
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
invalidateHttpCache();
|
||||||
|
Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actualizeAction () {
|
||||||
|
@set_time_limit(300);
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
|
||||||
|
Minz_Session::_param('actualize_feeds', false);
|
||||||
|
$id = Minz_Request::param ('id');
|
||||||
|
$force = Minz_Request::param ('force', false);
|
||||||
|
|
||||||
|
// on créé la liste des flux à mettre à actualiser
|
||||||
|
// si on veut mettre un flux à jour spécifiquement, on le met
|
||||||
|
// dans la liste, mais seul (permet d'automatiser le traitement)
|
||||||
|
$feeds = array ();
|
||||||
|
if ($id) {
|
||||||
|
$feed = $feedDAO->searchById ($id);
|
||||||
|
if ($feed) {
|
||||||
|
$feeds = array ($feed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
// on calcule la date des articles les plus anciens qu'on accepte
|
||||||
|
$nb_month_old = max($this->view->conf->old_entries, 1);
|
||||||
|
$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
$flux_update = 0;
|
||||||
|
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
|
||||||
|
foreach ($feeds as $feed) {
|
||||||
|
if (!$feed->lock()) {
|
||||||
|
Minz_Log::record('Feed already being actualized: ' . $feed->url(), Minz_Log::NOTICE);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$url = $feed->url();
|
||||||
|
$feedHistory = $feed->keepHistory();
|
||||||
|
|
||||||
|
$feed->load(false);
|
||||||
|
$entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
|
||||||
|
$hasTransaction = false;
|
||||||
|
|
||||||
|
if (count($entries) > 0) {
|
||||||
|
//For this feed, check last n entry GUIDs already in database
|
||||||
|
$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
|
||||||
|
$useDeclaredDate = empty($existingGuids);
|
||||||
|
|
||||||
|
if ($feedHistory == -2) { //default
|
||||||
|
$feedHistory = $this->view->conf->keep_history_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$preparedStatement = $entryDAO->addEntryPrepare();
|
||||||
|
$hasTransaction = true;
|
||||||
|
$feedDAO->beginTransaction();
|
||||||
|
|
||||||
|
// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
|
||||||
|
// La BDD refusera l'ajout car (id_feed, guid) doit être unique
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$eDate = $entry->date(true);
|
||||||
|
if ((!isset($existingGuids[$entry->guid()])) &&
|
||||||
|
(($feedHistory != 0) || ($eDate >= $date_min))) {
|
||||||
|
$values = $entry->toArray();
|
||||||
|
//Use declared date at first import, otherwise use discovery date
|
||||||
|
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
|
||||||
|
min(time(), $eDate) . uSecString() :
|
||||||
|
uTimeString();
|
||||||
|
$values['is_read'] = $is_read;
|
||||||
|
$entryDAO->addEntry($values, $preparedStatement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($feedHistory >= 0) && (rand(0, 30) === 1)) {
|
||||||
|
if (!$hasTransaction) {
|
||||||
|
$feedDAO->beginTransaction();
|
||||||
|
}
|
||||||
|
$nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10));
|
||||||
|
if ($nb > 0) {
|
||||||
|
Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on indique que le flux vient d'être mis à jour en BDD
|
||||||
|
$feedDAO->updateLastUpdate ($feed->id (), 0, $hasTransaction);
|
||||||
|
if ($hasTransaction) {
|
||||||
|
$feedDAO->commit();
|
||||||
|
}
|
||||||
|
$flux_update++;
|
||||||
|
if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently
|
||||||
|
Minz_Log::record('Feed ' . $url . ' moved permanently to ' . $feed->url(), Minz_Log::NOTICE);
|
||||||
|
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
|
||||||
|
}
|
||||||
|
} catch (FreshRSS_Feed_Exception $e) {
|
||||||
|
Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
|
||||||
|
$feedDAO->updateLastUpdate ($feed->id (), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$feed->faviconPrepare();
|
||||||
|
$feed->unlock();
|
||||||
|
unset($feed);
|
||||||
|
|
||||||
|
// On arrête à 10 flux pour ne pas surcharger le serveur
|
||||||
|
// sauf si le paramètre $force est à vrai
|
||||||
|
$i++;
|
||||||
|
if ($i >= 10 && !$force) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = array ();
|
||||||
|
if ($flux_update === 1) {
|
||||||
|
// on a mis un seul flux à jour
|
||||||
|
$feed = reset ($feeds);
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('feed_actualized', $feed->name ())
|
||||||
|
);
|
||||||
|
} elseif ($flux_update > 1) {
|
||||||
|
// plusieurs flux on été mis à jour
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('n_feeds_actualized', $flux_update)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// aucun flux n'a été mis à jour, oups
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('no_feed_to_refresh')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($i === 1) {
|
||||||
|
// Si on a voulu mettre à jour qu'un flux
|
||||||
|
// on filtre l'affichage par ce flux
|
||||||
|
$feed = reset ($feeds);
|
||||||
|
$url['params'] = array ('get' => 'f_' . $feed->id ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Minz_Request::param ('ajax', 0) === 0) {
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
Minz_Request::forward ($url, true);
|
||||||
|
} else {
|
||||||
|
// Une requête Ajax met un seul flux à jour.
|
||||||
|
// Comme en principe plusieurs requêtes ont lieu,
|
||||||
|
// on indique que "plusieurs flux ont été mis à jour".
|
||||||
|
// Cela permet d'avoir une notification plus proche du
|
||||||
|
// ressenti utilisateur
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('feeds_actualized')
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
// et on désactive le layout car ne sert à rien
|
||||||
|
$this->view->_useLayout (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAction () {
|
||||||
|
if (Minz_Request::isPost ()) {
|
||||||
|
$type = Minz_Request::param ('type', 'feed');
|
||||||
|
$id = Minz_Request::param ('id');
|
||||||
|
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
if ($type == 'category') {
|
||||||
|
if ($feedDAO->deleteFeedByCategory ($id)) {
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('category_emptied')
|
||||||
|
);
|
||||||
|
//TODO: Delete old favicons
|
||||||
|
} else {
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('error_occured')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($feedDAO->deleteFeed ($id)) {
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => Minz_Translate::t ('feed_deleted')
|
||||||
|
);
|
||||||
|
//TODO: Delete old favicon
|
||||||
|
} else {
|
||||||
|
$notif = array (
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t ('error_occured')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
|
||||||
|
if ($type == 'category') {
|
||||||
|
Minz_Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
|
||||||
|
} else {
|
||||||
|
Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed'), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
391
sources/app/Controllers/importExportController.php
Executable file
391
sources/app/Controllers/importExportController.php
Executable file
|
@ -0,0 +1,391 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_importExport_Controller extends Minz_ActionController {
|
||||||
|
public function firstAction() {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error(
|
||||||
|
403,
|
||||||
|
array('error' => array(Minz_Translate::t('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once(LIB_PATH . '/lib_opml.php');
|
||||||
|
|
||||||
|
$this->catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$this->entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
$this->feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function indexAction() {
|
||||||
|
$this->view->categories = $this->catDAO->listCategories();
|
||||||
|
$this->view->feeds = $this->feedDAO->listFeeds();
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function importAction() {
|
||||||
|
if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) {
|
||||||
|
@set_time_limit(300);
|
||||||
|
|
||||||
|
$file = $_FILES['file'];
|
||||||
|
$type_file = $this->guessFileType($file['name']);
|
||||||
|
|
||||||
|
$list_files = array(
|
||||||
|
'opml' => array(),
|
||||||
|
'json_starred' => array(),
|
||||||
|
'json_feed' => array()
|
||||||
|
);
|
||||||
|
|
||||||
|
// We try to list all files according to their type
|
||||||
|
// A zip file is first opened and then its files are listed
|
||||||
|
$list = array();
|
||||||
|
if ($type_file === 'zip') {
|
||||||
|
$zip = zip_open($file['tmp_name']);
|
||||||
|
|
||||||
|
while (($zipfile = zip_read($zip)) !== false) {
|
||||||
|
$type_zipfile = $this->guessFileType(
|
||||||
|
zip_entry_name($zipfile)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($type_file !== 'unknown') {
|
||||||
|
$list_files[$type_zipfile][] = zip_entry_read(
|
||||||
|
$zipfile,
|
||||||
|
zip_entry_filesize($zipfile)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_close($zip);
|
||||||
|
} elseif ($type_file !== 'unknown') {
|
||||||
|
$list_files[$type_file][] = file_get_contents(
|
||||||
|
$file['tmp_name']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import different files.
|
||||||
|
// OPML first(so categories and feeds are imported)
|
||||||
|
// Starred articles then so the "favourite" status is already set
|
||||||
|
// And finally all other files.
|
||||||
|
$error = false;
|
||||||
|
foreach ($list_files['opml'] as $opml_file) {
|
||||||
|
$error = $this->importOpml($opml_file);
|
||||||
|
}
|
||||||
|
foreach ($list_files['json_starred'] as $article_file) {
|
||||||
|
$error = $this->importArticles($article_file, true);
|
||||||
|
}
|
||||||
|
foreach ($list_files['json_feed'] as $article_file) {
|
||||||
|
$error = $this->importArticles($article_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally, we get import status and redirect to the home page
|
||||||
|
$notif = null;
|
||||||
|
if ($error === true) {
|
||||||
|
$content_notif = Minz_Translate::t(
|
||||||
|
'feeds_imported_with_errors'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
$content_notif = Minz_Translate::t(
|
||||||
|
'feeds_imported'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_Session::_param('notification', array(
|
||||||
|
'type' => 'good',
|
||||||
|
'content' => $content_notif
|
||||||
|
));
|
||||||
|
Minz_Session::_param('actualize_feeds', true);
|
||||||
|
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'index',
|
||||||
|
'a' => 'index'
|
||||||
|
), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// What are you doing? you have to call this controller
|
||||||
|
// with a POST request!
|
||||||
|
Minz_Request::forward(array(
|
||||||
|
'c' => 'importExport',
|
||||||
|
'a' => 'index'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function guessFileType($filename) {
|
||||||
|
// A *very* basic guess file type function. Only based on filename
|
||||||
|
// That's could be improved but should be enough, at least for a first
|
||||||
|
// implementation.
|
||||||
|
// TODO: improve this function?
|
||||||
|
|
||||||
|
if (substr_compare($filename, '.zip', -4) === 0) {
|
||||||
|
return 'zip';
|
||||||
|
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
|
||||||
|
substr_compare($filename, '.xml', -4) === 0) {
|
||||||
|
return 'opml';
|
||||||
|
} elseif (strcmp($filename, 'starred.json') === 0) {
|
||||||
|
return 'json_starred';
|
||||||
|
} elseif (substr_compare($filename, '.json', -5) === 0 &&
|
||||||
|
strpos($filename, 'feed_') === 0) {
|
||||||
|
return 'json_feed';
|
||||||
|
} else {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importOpml($opml_file) {
|
||||||
|
$opml_array = array();
|
||||||
|
try {
|
||||||
|
$opml_array = libopml_parse_string($opml_file);
|
||||||
|
} catch (LibOPML_Exception $e) {
|
||||||
|
Minz_Log::warning($e->getMessage());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->catDAO->checkDefault();
|
||||||
|
|
||||||
|
return $this->addOpmlElements($opml_array['body']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addOpmlElements($opml_elements, $parent_cat = null) {
|
||||||
|
$error = false;
|
||||||
|
foreach ($opml_elements as $elt) {
|
||||||
|
$res = false;
|
||||||
|
if (isset($elt['xmlUrl'])) {
|
||||||
|
$res = $this->addFeedOpml($elt, $parent_cat);
|
||||||
|
} else {
|
||||||
|
$res = $this->addCategoryOpml($elt, $parent_cat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$error && $res) {
|
||||||
|
// oops: there is at least one error!
|
||||||
|
$error = $res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addFeedOpml($feed_elt, $parent_cat) {
|
||||||
|
if (is_null($parent_cat)) {
|
||||||
|
// This feed has no parent category so we get the default one
|
||||||
|
$parent_cat = $this->catDAO->getDefault()->name();
|
||||||
|
}
|
||||||
|
|
||||||
|
$cat = $this->catDAO->searchByName($parent_cat);
|
||||||
|
|
||||||
|
if (!$cat) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We get different useful information
|
||||||
|
$url = html_chars_utf8($feed_elt['xmlUrl']);
|
||||||
|
$name = html_chars_utf8($feed_elt['text']);
|
||||||
|
$website = '';
|
||||||
|
if (isset($feed_elt['htmlUrl'])) {
|
||||||
|
$website = html_chars_utf8($feed_elt['htmlUrl']);
|
||||||
|
}
|
||||||
|
$description = '';
|
||||||
|
if (isset($feed_elt['description'])) {
|
||||||
|
$description = html_chars_utf8($feed_elt['description']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$error = false;
|
||||||
|
try {
|
||||||
|
// Create a Feed object and add it in DB
|
||||||
|
$feed = new FreshRSS_Feed($url);
|
||||||
|
$feed->_category($cat->id());
|
||||||
|
$feed->_name($name);
|
||||||
|
$feed->_website($website);
|
||||||
|
$feed->_description($description);
|
||||||
|
|
||||||
|
// addFeedObject checks if feed is already in DB so nothing else to
|
||||||
|
// check here
|
||||||
|
$id = $this->feedDAO->addFeedObject($feed);
|
||||||
|
$error = ($id === false);
|
||||||
|
} catch (FreshRSS_Feed_Exception $e) {
|
||||||
|
Minz_Log::warning($e->getMessage());
|
||||||
|
$error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addCategoryOpml($cat_elt, $parent_cat) {
|
||||||
|
// Create a new Category object
|
||||||
|
$cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text']));
|
||||||
|
|
||||||
|
$id = $this->catDAO->addCategoryObject($cat);
|
||||||
|
$error = ($id === false);
|
||||||
|
|
||||||
|
if (isset($cat_elt['@outlines'])) {
|
||||||
|
// Our cat_elt contains more categories or more feeds, so we
|
||||||
|
// add them recursively.
|
||||||
|
// Note: FreshRSS does not support yet category arborescence
|
||||||
|
$res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name());
|
||||||
|
if (!$error && $res) {
|
||||||
|
$error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importArticles($article_file, $starred = false) {
|
||||||
|
$article_object = json_decode($article_file, true);
|
||||||
|
if (is_null($article_object)) {
|
||||||
|
Minz_Log::warning('Try to import a non-JSON file');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
|
||||||
|
|
||||||
|
$google_compliant = (
|
||||||
|
strpos($article_object['id'], 'com.google') !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
$error = false;
|
||||||
|
foreach ($article_object['items'] as $item) {
|
||||||
|
$feed = $this->addFeedArticles($item['origin'], $google_compliant);
|
||||||
|
if (is_null($feed)) {
|
||||||
|
$error = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$author = isset($item['author']) ? $item['author'] : '';
|
||||||
|
$key_content = ($google_compliant && !isset($item['content'])) ?
|
||||||
|
'summary' : 'content';
|
||||||
|
$tags = $item['categories'];
|
||||||
|
if ($google_compliant) {
|
||||||
|
$tags = array_filter($tags, function($var) {
|
||||||
|
return strpos($var, '/state/com.google') === false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry = new FreshRSS_Entry(
|
||||||
|
$feed->id(), $item['id'], $item['title'], $author,
|
||||||
|
$item[$key_content]['content'], $item['alternate'][0]['href'],
|
||||||
|
$item['published'], $is_read, $starred
|
||||||
|
);
|
||||||
|
$entry->_tags($tags);
|
||||||
|
|
||||||
|
//FIME: Use entryDAO->addEntryPrepare(). Do not call entryDAO->listLastGuidsByFeed() for each entry. Consider using a transaction.
|
||||||
|
$id = $this->entryDAO->addEntryObject(
|
||||||
|
$entry, $this->view->conf, $feed->keepHistory()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!$error && ($id === false)) {
|
||||||
|
$error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addFeedArticles($origin, $google_compliant) {
|
||||||
|
$default_cat = $this->catDAO->getDefault();
|
||||||
|
|
||||||
|
$return = null;
|
||||||
|
$key = $google_compliant ? 'htmlUrl' : 'feedUrl';
|
||||||
|
$url = $origin[$key];
|
||||||
|
$name = $origin['title'];
|
||||||
|
$website = $origin['htmlUrl'];
|
||||||
|
$error = false;
|
||||||
|
try {
|
||||||
|
// Create a Feed object and add it in DB
|
||||||
|
$feed = new FreshRSS_Feed($url);
|
||||||
|
$feed->_category($default_cat->id());
|
||||||
|
$feed->_name($name);
|
||||||
|
$feed->_website($website);
|
||||||
|
|
||||||
|
// addFeedObject checks if feed is already in DB so nothing else to
|
||||||
|
// check here
|
||||||
|
$id = $this->feedDAO->addFeedObject($feed);
|
||||||
|
|
||||||
|
if ($id !== false) {
|
||||||
|
$feed->_id($id);
|
||||||
|
$return = $feed;
|
||||||
|
}
|
||||||
|
} catch (FreshRSS_Feed_Exception $e) {
|
||||||
|
Minz_Log::warning($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$this->view->_useLayout(false);
|
||||||
|
|
||||||
|
$export_opml = Minz_Request::param('export_opml', false);
|
||||||
|
$export_starred = Minz_Request::param('export_starred', false);
|
||||||
|
$export_feeds = Minz_Request::param('export_feeds', false);
|
||||||
|
|
||||||
|
// From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly
|
||||||
|
$file = tempnam('tmp', 'zip');
|
||||||
|
$zip = new ZipArchive();
|
||||||
|
$zip->open($file, ZipArchive::OVERWRITE);
|
||||||
|
|
||||||
|
// Stuff with content
|
||||||
|
if ($export_opml) {
|
||||||
|
$zip->addFromString(
|
||||||
|
'feeds.opml', $this->generateOpml()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($export_starred) {
|
||||||
|
$zip->addFromString(
|
||||||
|
'starred.json', $this->generateArticles('starred')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
foreach ($export_feeds as $feed_id) {
|
||||||
|
$feed = $this->feedDAO->searchById($feed_id);
|
||||||
|
$zip->addFromString(
|
||||||
|
'feed_' . $feed->category() . '_' . $feed->id() . '.json',
|
||||||
|
$this->generateArticles('feed', $feed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close and send to user
|
||||||
|
$zip->close();
|
||||||
|
header('Content-Type: application/zip');
|
||||||
|
header('Content-Length: ' . filesize($file));
|
||||||
|
header('Content-Disposition: attachment; filename="freshrss_export.zip"');
|
||||||
|
readfile($file);
|
||||||
|
unlink($file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateOpml() {
|
||||||
|
$list = array();
|
||||||
|
foreach ($this->catDAO->listCategories() as $key => $cat) {
|
||||||
|
$list[$key]['name'] = $cat->name();
|
||||||
|
$list[$key]['feeds'] = $this->feedDAO->listByCategory($cat->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->categories = $list;
|
||||||
|
return $this->view->helperToString('export/opml');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateArticles($type, $feed = NULL) {
|
||||||
|
$this->view->categories = $this->catDAO->listCategories();
|
||||||
|
|
||||||
|
if ($type == 'starred') {
|
||||||
|
$this->view->list_title = Minz_Translate::t('starred_list');
|
||||||
|
$this->view->type = 'starred';
|
||||||
|
$unread_fav = $this->entryDAO->countUnreadReadFavorites();
|
||||||
|
$this->view->entries = $this->entryDAO->listWhere(
|
||||||
|
's', '', FreshRSS_Entry::STATE_ALL, 'ASC',
|
||||||
|
$unread_fav['all']
|
||||||
|
);
|
||||||
|
} elseif ($type == 'feed' && !is_null($feed)) {
|
||||||
|
$this->view->list_title = Minz_Translate::t(
|
||||||
|
'feed_list', $feed->name()
|
||||||
|
);
|
||||||
|
$this->view->type = 'feed/' . $feed->id();
|
||||||
|
$this->view->entries = $this->entryDAO->listWhere(
|
||||||
|
'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
|
||||||
|
$this->view->conf->posts_per_page
|
||||||
|
);
|
||||||
|
$this->view->feed = $feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->view->helperToString('export/articles');
|
||||||
|
}
|
||||||
|
}
|
379
sources/app/Controllers/indexController.php
Executable file
379
sources/app/Controllers/indexController.php
Executable file
|
@ -0,0 +1,379 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_index_Controller extends Minz_ActionController {
|
||||||
|
private $nb_not_read_cat = 0;
|
||||||
|
|
||||||
|
public function indexAction () {
|
||||||
|
$output = Minz_Request::param ('output');
|
||||||
|
$token = $this->view->conf->token;
|
||||||
|
|
||||||
|
// check if user is logged in
|
||||||
|
if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous()) {
|
||||||
|
$token_param = Minz_Request::param ('token', '');
|
||||||
|
$token_is_ok = ($token != '' && $token === $token_param);
|
||||||
|
if ($output === 'rss' && !$token_is_ok) {
|
||||||
|
Minz_Error::error (
|
||||||
|
403,
|
||||||
|
array ('error' => array (Minz_Translate::t ('access_denied')))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} elseif ($output !== 'rss') {
|
||||||
|
// "hard" redirection is not required, just ask dispatcher to
|
||||||
|
// forward to the login form without 302 redirection
|
||||||
|
Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$params = Minz_Request::params ();
|
||||||
|
if (isset ($params['search'])) {
|
||||||
|
$params['search'] = urlencode ($params['search']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->url = array (
|
||||||
|
'c' => 'index',
|
||||||
|
'a' => 'index',
|
||||||
|
'params' => $params
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output === 'rss') {
|
||||||
|
// no layout for RSS output
|
||||||
|
$this->view->_useLayout (false);
|
||||||
|
header('Content-Type: application/rss+xml; charset=utf-8');
|
||||||
|
} elseif ($output === 'global') {
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
|
||||||
|
}
|
||||||
|
|
||||||
|
$catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$entryDAO = FreshRSS_Factory::createEntryDao();
|
||||||
|
|
||||||
|
$this->view->cat_aside = $catDAO->listCategories ();
|
||||||
|
$this->view->nb_favorites = $entryDAO->countUnreadReadFavorites ();
|
||||||
|
$this->view->nb_not_read = FreshRSS_CategoryDAO::CountUnreads($this->view->cat_aside, 1);
|
||||||
|
$this->view->currentName = '';
|
||||||
|
|
||||||
|
$this->view->get_c = '';
|
||||||
|
$this->view->get_f = '';
|
||||||
|
|
||||||
|
$get = Minz_Request::param ('get', 'a');
|
||||||
|
$getType = $get[0];
|
||||||
|
$getId = substr ($get, 2);
|
||||||
|
if (!$this->checkAndProcessType ($getType, $getId)) {
|
||||||
|
Minz_Log::record ('Not found [' . $getType . '][' . $getId . ']', Minz_Log::DEBUG);
|
||||||
|
Minz_Error::error (
|
||||||
|
404,
|
||||||
|
array ('error' => array (Minz_Translate::t ('page_not_found')))
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mise à jour des titres
|
||||||
|
$this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title();
|
||||||
|
if ($this->view->nb_not_read > 0) {
|
||||||
|
Minz_View::prependTitle('(' . formatNumber($this->view->nb_not_read) . ') ');
|
||||||
|
}
|
||||||
|
Minz_View::prependTitle(
|
||||||
|
($this->nb_not_read_cat > 0 ? '(' . formatNumber($this->nb_not_read_cat) . ') ' : '') .
|
||||||
|
$this->view->currentName .
|
||||||
|
' · '
|
||||||
|
);
|
||||||
|
|
||||||
|
// On récupère les différents éléments de filtrage
|
||||||
|
$this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view);
|
||||||
|
$state_param = Minz_Request::param ('state', null);
|
||||||
|
$filter = Minz_Request::param ('search', '');
|
||||||
|
$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order);
|
||||||
|
$nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page);
|
||||||
|
$first = Minz_Request::param ('next', '');
|
||||||
|
|
||||||
|
if ($state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all?
|
||||||
|
switch ($getType) {
|
||||||
|
case 'a':
|
||||||
|
$hasUnread = $this->view->nb_not_read > 0;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
// This is deprecated. The favorite button does not exist anymore
|
||||||
|
$hasUnread = $this->view->nb_favorites['unread'] > 0;
|
||||||
|
break;
|
||||||
|
case 'c':
|
||||||
|
$hasUnread = (!isset($this->view->cat_aside[$getId])) || ($this->view->cat_aside[$getId]->nbNotRead() > 0);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
$myFeed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
|
||||||
|
$hasUnread = ($myFeed === null) || ($myFeed->nbNotRead() > 0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$hasUnread = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!$hasUnread && ($state_param === null)) {
|
||||||
|
$this->view->state = $state = FreshRSS_Entry::STATE_ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$today = @strtotime('today');
|
||||||
|
$this->view->today = $today;
|
||||||
|
|
||||||
|
// on calcule la date des articles les plus anciens qu'on affiche
|
||||||
|
$nb_month_old = $this->view->conf->old_entries;
|
||||||
|
$date_min = $today - (3600 * 24 * 30 * $nb_month_old); //Do not use a fast changing value such as time() to allow SQL caching
|
||||||
|
$keepHistoryDefault = $this->view->conf->keep_history_default;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault);
|
||||||
|
|
||||||
|
// Si on a récupéré aucun article "non lus"
|
||||||
|
// on essaye de récupérer tous les articles
|
||||||
|
if ($state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
|
||||||
|
Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
try {
|
||||||
|
$feedDAO->updateCachedValues();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
Minz_Log::record('Failed to automatically correct nbNotRead! ' + $ex->getMessage(), Minz_Log::NOTICE);
|
||||||
|
}
|
||||||
|
$this->view->state = FreshRSS_Entry::STATE_ALL;
|
||||||
|
$entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($entries) <= $nb) {
|
||||||
|
$this->view->nextId = '';
|
||||||
|
} else { //We have more elements for pagination
|
||||||
|
$lastEntry = array_pop($entries);
|
||||||
|
$this->view->nextId = $lastEntry->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->entries = $entries;
|
||||||
|
} catch (FreshRSS_EntriesGetter_Exception $e) {
|
||||||
|
Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
|
||||||
|
Minz_Error::error (
|
||||||
|
404,
|
||||||
|
array ('error' => array (Minz_Translate::t ('page_not_found')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Vérifie que la catégorie / flux sélectionné existe
|
||||||
|
* + Initialise correctement les variables de vue get_c et get_f
|
||||||
|
* + Met à jour la variable $this->nb_not_read_cat
|
||||||
|
*/
|
||||||
|
private function checkAndProcessType ($getType, $getId) {
|
||||||
|
switch ($getType) {
|
||||||
|
case 'a':
|
||||||
|
$this->view->currentName = Minz_Translate::t ('your_rss_feeds');
|
||||||
|
$this->nb_not_read_cat = $this->view->nb_not_read;
|
||||||
|
$this->view->get_c = $getType;
|
||||||
|
return true;
|
||||||
|
case 's':
|
||||||
|
$this->view->currentName = Minz_Translate::t ('your_favorites');
|
||||||
|
$this->nb_not_read_cat = $this->view->nb_favorites['unread'];
|
||||||
|
$this->view->get_c = $getType;
|
||||||
|
return true;
|
||||||
|
case 'c':
|
||||||
|
$cat = isset($this->view->cat_aside[$getId]) ? $this->view->cat_aside[$getId] : null;
|
||||||
|
if ($cat === null) {
|
||||||
|
$catDAO = new FreshRSS_CategoryDAO();
|
||||||
|
$cat = $catDAO->searchById($getId);
|
||||||
|
}
|
||||||
|
if ($cat) {
|
||||||
|
$this->view->currentName = $cat->name ();
|
||||||
|
$this->nb_not_read_cat = $cat->nbNotRead ();
|
||||||
|
$this->view->get_c = $getId;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case 'f':
|
||||||
|
$feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
|
||||||
|
if (empty($feed)) {
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$feed = $feedDAO->searchById($getId);
|
||||||
|
}
|
||||||
|
if ($feed) {
|
||||||
|
$this->view->currentName = $feed->name ();
|
||||||
|
$this->nb_not_read_cat = $feed->nbNotRead ();
|
||||||
|
$this->view->get_f = $getId;
|
||||||
|
$this->view->get_c = $feed->category ();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function aboutAction () {
|
||||||
|
Minz_View::prependTitle (Minz_Translate::t ('about') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logsAction () {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error (
|
||||||
|
403,
|
||||||
|
array ('error' => array (Minz_Translate::t ('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle (Minz_Translate::t ('logs') . ' · ');
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginAction () {
|
||||||
|
$this->view->_useLayout (false);
|
||||||
|
|
||||||
|
$url = 'https://verifier.login.persona.org/verify';
|
||||||
|
$assert = Minz_Request::param ('assertion');
|
||||||
|
$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);
|
||||||
|
|
||||||
|
$loginOk = false;
|
||||||
|
$reason = '';
|
||||||
|
if ($res['status'] === 'okay') {
|
||||||
|
$email = filter_var($res['email'], FILTER_VALIDATE_EMAIL);
|
||||||
|
if ($email != '') {
|
||||||
|
$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
|
||||||
|
if (($currentUser = @file_get_contents($personaFile)) !== false) {
|
||||||
|
$currentUser = trim($currentUser);
|
||||||
|
if (ctype_alnum($currentUser)) {
|
||||||
|
try {
|
||||||
|
$this->conf = new FreshRSS_Configuration($currentUser);
|
||||||
|
$loginOk = strcasecmp($email, $this->conf->mail_login) === 0;
|
||||||
|
} catch (Minz_Exception $e) {
|
||||||
|
$reason = 'Invalid configuration for user [' . $currentUser . ']! ' . $e->getMessage(); //Permission denied or conf file does not exist
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$reason = 'Invalid username format [' . $currentUser . ']!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$reason = 'Invalid email format [' . $res['email'] . ']!';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($loginOk) {
|
||||||
|
Minz_Session::_param('currentUser', $currentUser);
|
||||||
|
Minz_Session::_param ('mail', $email);
|
||||||
|
$this->view->loginOk = true;
|
||||||
|
invalidateHttpCache();
|
||||||
|
} else {
|
||||||
|
$res = array ();
|
||||||
|
$res['status'] = 'failure';
|
||||||
|
$res['reason'] = $reason == '' ? Minz_Translate::t ('invalid_login') : $reason;
|
||||||
|
Minz_Log::record ('Persona: ' . $res['reason'], Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
$this->view->res = json_encode ($res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logoutAction () {
|
||||||
|
$this->view->_useLayout(false);
|
||||||
|
invalidateHttpCache();
|
||||||
|
Minz_Session::_param('currentUser');
|
||||||
|
Minz_Session::_param('mail');
|
||||||
|
Minz_Session::_param('passwordHash');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formLoginAction () {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$ok = false;
|
||||||
|
$nonce = Minz_Session::param('nonce');
|
||||||
|
$username = Minz_Request::param('username', '');
|
||||||
|
$c = Minz_Request::param('challenge', '');
|
||||||
|
if (ctype_alnum($username) && ctype_graph($c) && ctype_alnum($nonce)) {
|
||||||
|
if (!function_exists('password_verify')) {
|
||||||
|
include_once(LIB_PATH . '/password_compat.php');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$conf = new FreshRSS_Configuration($username);
|
||||||
|
$s = $conf->passwordHash;
|
||||||
|
$ok = password_verify($nonce . $s, $c);
|
||||||
|
if ($ok) {
|
||||||
|
Minz_Session::_param('currentUser', $username);
|
||||||
|
Minz_Session::_param('passwordHash', $s);
|
||||||
|
} else {
|
||||||
|
Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
} catch (Minz_Exception $me) {
|
||||||
|
Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Minz_Log::record('Invalid credential parameters: user=' . $username . ' challenge=' . $c . ' nonce=' . $nonce, Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
if (!$ok) {
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => Minz_Translate::t('invalid_login')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
}
|
||||||
|
$this->view->_useLayout(false);
|
||||||
|
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
|
||||||
|
} elseif (Minz_Configuration::unsafeAutologinEnabled() && isset($_GET['u']) && isset($_GET['p'])) {
|
||||||
|
Minz_Session::_param('currentUser');
|
||||||
|
Minz_Session::_param('mail');
|
||||||
|
Minz_Session::_param('passwordHash');
|
||||||
|
$username = ctype_alnum($_GET['u']) ? $_GET['u'] : '';
|
||||||
|
$passwordPlain = $_GET['p'];
|
||||||
|
Minz_Request::_param('p'); //Discard plain-text password ASAP
|
||||||
|
$_GET['p'] = '';
|
||||||
|
if (!function_exists('password_verify')) {
|
||||||
|
include_once(LIB_PATH . '/password_compat.php');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$conf = new FreshRSS_Configuration($username);
|
||||||
|
$s = $conf->passwordHash;
|
||||||
|
$ok = password_verify($passwordPlain, $s);
|
||||||
|
unset($passwordPlain);
|
||||||
|
if ($ok) {
|
||||||
|
Minz_Session::_param('currentUser', $username);
|
||||||
|
Minz_Session::_param('passwordHash', $s);
|
||||||
|
} else {
|
||||||
|
Minz_Log::record('Unsafe password mismatch for user ' . $username, Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
} catch (Minz_Exception $me) {
|
||||||
|
Minz_Log::record('Unsafe login failure: ' . $me->getMessage(), Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
|
||||||
|
} elseif (!Minz_Configuration::canLogIn()) {
|
||||||
|
Minz_Error::error (
|
||||||
|
403,
|
||||||
|
array ('error' => array (Minz_Translate::t ('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
invalidateHttpCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function formLogoutAction () {
|
||||||
|
$this->view->_useLayout(false);
|
||||||
|
invalidateHttpCache();
|
||||||
|
Minz_Session::_param('currentUser');
|
||||||
|
Minz_Session::_param('mail');
|
||||||
|
Minz_Session::_param('passwordHash');
|
||||||
|
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
|
||||||
|
}
|
||||||
|
}
|
46
sources/app/Controllers/javascriptController.php
Executable file
46
sources/app/Controllers/javascriptController.php
Executable file
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_javascript_Controller extends Minz_ActionController {
|
||||||
|
public function firstAction () {
|
||||||
|
$this->view->_useLayout (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function actualizeAction () {
|
||||||
|
header('Content-Type: text/javascript; charset=UTF-8');
|
||||||
|
$feedDAO = FreshRSS_Factory::createFeedDao();
|
||||||
|
$this->view->feeds = $feedDAO->listFeedsOrderUpdate($this->view->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 {
|
||||||
|
$conf = new FreshRSS_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(Minz_Configuration::salt() . uniqid(mt_rand(), true));
|
||||||
|
Minz_Session::_param('nonce', $this->view->nonce);
|
||||||
|
return; //Success
|
||||||
|
}
|
||||||
|
} catch (Minz_Exception $me) {
|
||||||
|
Minz_Log::record('Nonce failure: ' . $me->getMessage(), Minz_Log::WARNING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->view->nonce = ''; //Failure
|
||||||
|
$this->view->salt1 = '';
|
||||||
|
}
|
||||||
|
}
|
67
sources/app/Controllers/statsController.php
Executable file
67
sources/app/Controllers/statsController.php
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_stats_Controller extends Minz_ActionController {
|
||||||
|
|
||||||
|
public function indexAction() {
|
||||||
|
$statsDAO = FreshRSS_Factory::createStatsDAO();
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
|
||||||
|
$this->view->repartition = $statsDAO->calculateEntryRepartition();
|
||||||
|
$this->view->count = ($statsDAO->calculateEntryCount());
|
||||||
|
$this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
|
||||||
|
$this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
|
||||||
|
$this->view->topFeed = $statsDAO->calculateTopFeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function idleAction() {
|
||||||
|
$statsDAO = FreshRSS_Factory::createStatsDAO();
|
||||||
|
$feeds = $statsDAO->calculateFeedLastDate();
|
||||||
|
$idleFeeds = 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 < $lastWeek) {
|
||||||
|
$idleFeeds['last_week'][] = $feed['name'];
|
||||||
|
}
|
||||||
|
if ($feedDate < $lastMonth) {
|
||||||
|
$idleFeeds['last_month'][] = $feed['name'];
|
||||||
|
}
|
||||||
|
if ($feedDate < $last3Month) {
|
||||||
|
$idleFeeds['last_3_month'][] = $feed['name'];
|
||||||
|
}
|
||||||
|
if ($feedDate < $last6Month) {
|
||||||
|
$idleFeeds['last_6_month'][] = $feed['name'];
|
||||||
|
}
|
||||||
|
if ($feedDate < $lastYear) {
|
||||||
|
$idleFeeds['last_year'][] = $feed['name'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->view->idleFeeds = array_reverse($idleFeeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function firstAction() {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error(
|
||||||
|
403, array('error' => array(Minz_Translate::t('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Minz_View::prependTitle(Minz_Translate::t('stats') . ' · ');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
203
sources/app/Controllers/usersController.php
Executable file
203
sources/app/Controllers/usersController.php
Executable file
|
@ -0,0 +1,203 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_users_Controller extends Minz_ActionController {
|
||||||
|
|
||||||
|
const BCRYPT_COST = 9; //Will also have to be computed client side on mobile devices, so do not use a too high cost
|
||||||
|
|
||||||
|
public function firstAction() {
|
||||||
|
if (!$this->view->loginOk) {
|
||||||
|
Minz_Error::error(
|
||||||
|
403,
|
||||||
|
array('error' => array(Minz_Translate::t('access_denied')))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function authAction() {
|
||||||
|
if (Minz_Request::isPost()) {
|
||||||
|
$ok = true;
|
||||||
|
|
||||||
|
$passwordPlain = Minz_Request::param('passwordPlain', '', true);
|
||||||
|
if ($passwordPlain != '') {
|
||||||
|
Minz_Request::_param('passwordPlain'); //Discard plain-text password ASAP
|
||||||
|
$_POST['passwordPlain'] = '';
|
||||||
|
if (!function_exists('password_hash')) {
|
||||||
|
include_once(LIB_PATH . '/password_compat.php');
|
||||||
|
}
|
||||||
|
$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
|
||||||
|
$passwordPlain = '';
|
||||||
|
$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
|
||||||
|
$ok &= ($passwordHash != '');
|
||||||
|
$this->view->conf->_passwordHash($passwordHash);
|
||||||
|
}
|
||||||
|
Minz_Session::_param('passwordHash', $this->view->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 != '');
|
||||||
|
$this->view->conf->_apiPasswordHash($passwordHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
|
||||||
|
$this->view->conf->_mail_login(Minz_Request::param('mail_login', '', true));
|
||||||
|
}
|
||||||
|
$email = $this->view->conf->mail_login;
|
||||||
|
Minz_Session::_param('mail', $email);
|
||||||
|
|
||||||
|
$ok &= $this->view->conf->save();
|
||||||
|
|
||||||
|
if ($email != '') {
|
||||||
|
$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
|
||||||
|
@unlink($personaFile);
|
||||||
|
$ok &= (file_put_contents($personaFile, Minz_Session::param('currentUser', '_')) !== false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
|
||||||
|
$current_token = $this->view->conf->token;
|
||||||
|
$token = Minz_Request::param('token', $current_token);
|
||||||
|
$this->view->conf->_token($token);
|
||||||
|
$ok &= $this->view->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 != Minz_Configuration::allowAnonymous() ||
|
||||||
|
$auth_type != Minz_Configuration::authType() ||
|
||||||
|
$anon_refresh != Minz_Configuration::allowAnonymousRefresh() ||
|
||||||
|
$unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() ||
|
||||||
|
$api_enabled != Minz_Configuration::apiEnabled()) {
|
||||||
|
|
||||||
|
Minz_Configuration::_authType($auth_type);
|
||||||
|
Minz_Configuration::_allowAnonymous($anon);
|
||||||
|
Minz_Configuration::_allowAnonymousRefresh($anon_refresh);
|
||||||
|
Minz_Configuration::_enableAutologin($unsafe_autologin);
|
||||||
|
Minz_Configuration::_enableApi($api_enabled);
|
||||||
|
$ok &= Minz_Configuration::writeFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => $ok ? 'good' : 'bad',
|
||||||
|
'content' => Minz_Translate::t($ok ? 'configuration_updated' : 'error_occurred')
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
}
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAction() {
|
||||||
|
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
|
||||||
|
|
||||||
|
$new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language);
|
||||||
|
if (!in_array($new_user_language, $this->view->conf->availableLanguages())) {
|
||||||
|
$new_user_language = $this->view->conf->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_user_name = Minz_Request::param('new_user_name');
|
||||||
|
$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
|
||||||
|
|
||||||
|
if ($ok) {
|
||||||
|
$ok &= (strcasecmp($new_user_name, Minz_Configuration::defaultUser()) !== 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 = DATA_PATH . '/' . $new_user_name . '_user.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 = DATA_PATH . '/persona/' . $new_user_email . '.txt';
|
||||||
|
@unlink($personaFile);
|
||||||
|
$ok &= (file_put_contents($personaFile, $new_user_name) !== false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($ok) {
|
||||||
|
$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' => Minz_Translate::t($ok ? 'user_created' : 'error_occurred', $new_user_name)
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
}
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteAction() {
|
||||||
|
if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
|
||||||
|
|
||||||
|
$username = Minz_Request::param('username');
|
||||||
|
$ok = ctype_alnum($username);
|
||||||
|
|
||||||
|
if ($ok) {
|
||||||
|
$ok &= (strcasecmp($username, Minz_Configuration::defaultUser()) !== 0); //It is forbidden to delete the default user
|
||||||
|
}
|
||||||
|
if ($ok) {
|
||||||
|
$configPath = DATA_PATH . '/' . $username . '_user.php';
|
||||||
|
$ok &= file_exists($configPath);
|
||||||
|
}
|
||||||
|
if ($ok) {
|
||||||
|
$userDAO = new FreshRSS_UserDAO();
|
||||||
|
$ok &= $userDAO->deleteUser($username);
|
||||||
|
$ok &= unlink($configPath);
|
||||||
|
//TODO: delete Persona file
|
||||||
|
}
|
||||||
|
invalidateHttpCache();
|
||||||
|
|
||||||
|
$notif = array(
|
||||||
|
'type' => $ok ? 'good' : 'bad',
|
||||||
|
'content' => Minz_Translate::t($ok ? 'user_deleted' : 'error_occurred', $username)
|
||||||
|
);
|
||||||
|
Minz_Session::_param('notification', $notif);
|
||||||
|
}
|
||||||
|
Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
|
||||||
|
}
|
||||||
|
}
|
6
sources/app/Exceptions/BadUrlException.php
Executable file
6
sources/app/Exceptions/BadUrlException.php
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
class FreshRSS_BadUrl_Exception extends FreshRSS_Feed_Exception {
|
||||||
|
public function __construct ($url) {
|
||||||
|
parent::__construct ('`' . $url . '` is not a valid URL');
|
||||||
|
}
|
||||||
|
}
|
7
sources/app/Exceptions/EntriesGetterException.php
Executable file
7
sources/app/Exceptions/EntriesGetterException.php
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_EntriesGetter_Exception extends Exception {
|
||||||
|
public function __construct ($message) {
|
||||||
|
parent::__construct ($message);
|
||||||
|
}
|
||||||
|
}
|
6
sources/app/Exceptions/FeedException.php
Executable file
6
sources/app/Exceptions/FeedException.php
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
class FreshRSS_Feed_Exception extends Exception {
|
||||||
|
public function __construct ($message) {
|
||||||
|
parent::__construct ($message);
|
||||||
|
}
|
||||||
|
}
|
146
sources/app/FreshRSS.php
Executable file
146
sources/app/FreshRSS.php
Executable file
|
@ -0,0 +1,146 @@
|
||||||
|
<?php
|
||||||
|
class FreshRSS extends Minz_FrontController {
|
||||||
|
public function init() {
|
||||||
|
if (!isset($_SESSION)) {
|
||||||
|
Minz_Session::init('FreshRSS');
|
||||||
|
}
|
||||||
|
$loginOk = $this->accessControl(Minz_Session::param('currentUser', ''));
|
||||||
|
$this->loadParamsView();
|
||||||
|
$this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests
|
||||||
|
$this->loadNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function accessControl($currentUser) {
|
||||||
|
if ($currentUser == '') {
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
$currentUser = Minz_Configuration::defaultUser();
|
||||||
|
Minz_Session::_param('passwordHash');
|
||||||
|
$loginOk = false;
|
||||||
|
break;
|
||||||
|
case 'http_auth':
|
||||||
|
$currentUser = httpAuthUser();
|
||||||
|
$loginOk = $currentUser != '';
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
$loginOk = false;
|
||||||
|
$email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL);
|
||||||
|
if ($email != '') { //TODO: Remove redundancy with indexController
|
||||||
|
$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
|
||||||
|
if (($currentUser = @file_get_contents($personaFile)) !== false) {
|
||||||
|
$currentUser = trim($currentUser);
|
||||||
|
$loginOk = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$loginOk) {
|
||||||
|
$currentUser = Minz_Configuration::defaultUser();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'none':
|
||||||
|
$currentUser = Minz_Configuration::defaultUser();
|
||||||
|
$loginOk = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$currentUser = Minz_Configuration::defaultUser();
|
||||||
|
$loginOk = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$loginOk = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctype_alnum($currentUser)) {
|
||||||
|
Minz_Session::_param('currentUser', '');
|
||||||
|
die('Invalid username [' . $currentUser . ']!');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->conf = new FreshRSS_Configuration($currentUser);
|
||||||
|
Minz_View::_param ('conf', $this->conf);
|
||||||
|
Minz_Session::_param('currentUser', $currentUser);
|
||||||
|
} catch (Minz_Exception $me) {
|
||||||
|
$loginOk = false;
|
||||||
|
try {
|
||||||
|
$this->conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser());
|
||||||
|
Minz_Session::_param('currentUser', Minz_Configuration::defaultUser());
|
||||||
|
Minz_View::_param('conf', $this->conf);
|
||||||
|
$notif = array(
|
||||||
|
'type' => 'bad',
|
||||||
|
'content' => 'Invalid configuration for user [' . $currentUser . ']!',
|
||||||
|
);
|
||||||
|
Minz_Session::_param ('notification', $notif);
|
||||||
|
Minz_Log::record ($notif['content'] . ' ' . $me->getMessage(), Minz_Log::WARNING);
|
||||||
|
Minz_Session::_param('currentUser', '');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
die($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($loginOk) {
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
$loginOk = Minz_Session::param('passwordHash') === $this->conf->passwordHash;
|
||||||
|
break;
|
||||||
|
case 'http_auth':
|
||||||
|
$loginOk = strcasecmp($currentUser, httpAuthUser()) === 0;
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
$loginOk = strcasecmp(Minz_Session::param('mail'), $this->conf->mail_login) === 0;
|
||||||
|
break;
|
||||||
|
case 'none':
|
||||||
|
$loginOk = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$loginOk = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Minz_View::_param ('loginOk', $loginOk);
|
||||||
|
return $loginOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadParamsView () {
|
||||||
|
Minz_Session::_param ('language', $this->conf->language);
|
||||||
|
Minz_Translate::init();
|
||||||
|
$output = Minz_Request::param ('output', '');
|
||||||
|
if (($output === '') || ($output !== 'normal' && $output !== 'rss' && $output !== 'reader' && $output !== 'global')) {
|
||||||
|
$output = $this->conf->view_mode;
|
||||||
|
Minz_Request::_param ('output', $output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadStylesAndScripts ($loginOk) {
|
||||||
|
$theme = FreshRSS_Themes::load($this->conf->theme);
|
||||||
|
if ($theme) {
|
||||||
|
foreach($theme['files'] as $file) {
|
||||||
|
Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['id'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['id'] . '/' . $file)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
if (!$loginOk) {
|
||||||
|
Minz_View::appendScript(Minz_Url::display ('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
Minz_View::appendScript('https://login.persona.org/include.js');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader');
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad);
|
||||||
|
if ($includeLazyLoad) {
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js')));
|
||||||
|
}
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
|
||||||
|
Minz_View::appendScript (Minz_Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadNotifications () {
|
||||||
|
$notif = Minz_Session::param ('notification');
|
||||||
|
if ($notif) {
|
||||||
|
Minz_View::_param ('notification', $notif);
|
||||||
|
Minz_Session::_param ('notification');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
sources/app/Models/Category.php
Executable file
73
sources/app/Models/Category.php
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Category extends Minz_Model {
|
||||||
|
private $id = 0;
|
||||||
|
private $name;
|
||||||
|
private $nbFeed = -1;
|
||||||
|
private $nbNotRead = -1;
|
||||||
|
private $feeds = null;
|
||||||
|
|
||||||
|
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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->feeds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _id ($value) {
|
||||||
|
$this->id = $value;
|
||||||
|
}
|
||||||
|
public function _name ($value) {
|
||||||
|
$this->name = $value;
|
||||||
|
}
|
||||||
|
public function _feeds ($values) {
|
||||||
|
if (!is_array ($values)) {
|
||||||
|
$values = array ($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->feeds = $values;
|
||||||
|
}
|
||||||
|
}
|
257
sources/app/Models/CategoryDAO.php
Executable file
257
sources/app/Models/CategoryDAO.php
Executable file
|
@ -0,0 +1,257 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_CategoryDAO extends Minz_ModelPdo {
|
||||||
|
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::record('SQL error addCategory: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error updateCategory: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error deleteCategory: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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 '
|
||||||
|
. '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 (Minz_Translate::t ('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;
|
||||||
|
}
|
||||||
|
}
|
292
sources/app/Models/Configuration.php
Executable file
292
sources/app/Models/Configuration.php
Executable file
|
@ -0,0 +1,292 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Configuration {
|
||||||
|
private $filename;
|
||||||
|
|
||||||
|
private $data = array(
|
||||||
|
'language' => 'en',
|
||||||
|
'old_entries' => 3,
|
||||||
|
'keep_history_default' => 0,
|
||||||
|
'ttl_default' => 3600,
|
||||||
|
'mail_login' => '',
|
||||||
|
'token' => '',
|
||||||
|
'passwordHash' => '', //CRYPT_BLOWFISH
|
||||||
|
'apiPasswordHash' => '', //CRYPT_BLOWFISH
|
||||||
|
'posts_per_page' => 20,
|
||||||
|
'view_mode' => 'normal',
|
||||||
|
'default_view' => FreshRSS_Entry::STATE_NOT_READ,
|
||||||
|
'auto_load_more' => true,
|
||||||
|
'display_posts' => false,
|
||||||
|
'onread_jump_next' => true,
|
||||||
|
'lazyload' => true,
|
||||||
|
'sticky_post' => true,
|
||||||
|
'reading_confirm' => false,
|
||||||
|
'sort_order' => 'DESC',
|
||||||
|
'anon_access' => false,
|
||||||
|
'mark_when' => array(
|
||||||
|
'article' => true,
|
||||||
|
'site' => true,
|
||||||
|
'scroll' => false,
|
||||||
|
'reception' => false,
|
||||||
|
),
|
||||||
|
'theme' => 'Origine',
|
||||||
|
'content_width' => 'thin',
|
||||||
|
'shortcuts' => array(
|
||||||
|
'mark_read' => 'r',
|
||||||
|
'mark_favorite' => 'f',
|
||||||
|
'go_website' => 'space',
|
||||||
|
'next_entry' => 'j',
|
||||||
|
'prev_entry' => 'k',
|
||||||
|
'first_entry' => 'home',
|
||||||
|
'last_entry' => 'end',
|
||||||
|
'collapse_entry' => 'c',
|
||||||
|
'load_more' => 'm',
|
||||||
|
'auto_share' => 's',
|
||||||
|
'focus_search' => 'a',
|
||||||
|
),
|
||||||
|
'topline_read' => true,
|
||||||
|
'topline_favorite' => true,
|
||||||
|
'topline_date' => true,
|
||||||
|
'topline_link' => true,
|
||||||
|
'bottomline_read' => true,
|
||||||
|
'bottomline_favorite' => true,
|
||||||
|
'bottomline_sharing' => true,
|
||||||
|
'bottomline_tags' => true,
|
||||||
|
'bottomline_date' => true,
|
||||||
|
'bottomline_link' => true,
|
||||||
|
'sharing' => array(),
|
||||||
|
'queries' => array(),
|
||||||
|
);
|
||||||
|
|
||||||
|
private $available_languages = array(
|
||||||
|
'en' => 'English',
|
||||||
|
'fr' => 'Français',
|
||||||
|
);
|
||||||
|
|
||||||
|
private $shares;
|
||||||
|
|
||||||
|
public function __construct($user) {
|
||||||
|
$this->filename = DATA_PATH . DIRECTORY_SEPARATOR . $user . '_user.php';
|
||||||
|
|
||||||
|
$data = @include($this->filename);
|
||||||
|
if (!is_array($data)) {
|
||||||
|
throw new Minz_PermissionDeniedException($this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($data as $key => $value) {
|
||||||
|
if (isset($this->data[$key])) {
|
||||||
|
$function = '_' . $key;
|
||||||
|
$this->$function($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->data['user'] = $user;
|
||||||
|
|
||||||
|
$this->shares = DATA_PATH . DIRECTORY_SEPARATOR . 'shares.php';
|
||||||
|
|
||||||
|
$shares = @include($this->shares);
|
||||||
|
if (!is_array($shares)) {
|
||||||
|
throw new Minz_PermissionDeniedException($this->shares);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data['shares'] = $shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save() {
|
||||||
|
@rename($this->filename, $this->filename . '.bak.php');
|
||||||
|
unset($this->data['shares']); // Remove shares because it is not intended to be stored in user configuration
|
||||||
|
if (file_put_contents($this->filename, "<?php\n return " . var_export($this->data, true) . ';', LOCK_EX) === false) {
|
||||||
|
throw new Minz_PermissionDeniedException($this->filename);
|
||||||
|
}
|
||||||
|
if (function_exists('opcache_invalidate')) {
|
||||||
|
opcache_invalidate($this->filename); //Clear PHP 5.5+ cache for include
|
||||||
|
}
|
||||||
|
invalidateHttpCache();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __get($name) {
|
||||||
|
if (array_key_exists($name, $this->data)) {
|
||||||
|
return $this->data[$name];
|
||||||
|
} else {
|
||||||
|
$trace = debug_backtrace();
|
||||||
|
trigger_error('Undefined FreshRSS_Configuration->' . $name . 'in ' . $trace[0]['file'] . ' line ' . $trace[0]['line'], E_USER_NOTICE); //TODO: Use Minz exceptions
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function availableLanguages() {
|
||||||
|
return $this->available_languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function _language($value) {
|
||||||
|
if (!isset($this->available_languages[$value])) {
|
||||||
|
$value = 'en';
|
||||||
|
}
|
||||||
|
$this->data['language'] = $value;
|
||||||
|
}
|
||||||
|
public function _posts_per_page ($value) {
|
||||||
|
$value = intval($value);
|
||||||
|
$this->data['posts_per_page'] = $value > 0 ? $value : 10;
|
||||||
|
}
|
||||||
|
public function _view_mode ($value) {
|
||||||
|
if ($value === 'global' || $value === 'reader') {
|
||||||
|
$this->data['view_mode'] = $value;
|
||||||
|
} else {
|
||||||
|
$this->data['view_mode'] = 'normal';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _default_view ($value) {
|
||||||
|
$this->data['default_view'] = $value === FreshRSS_Entry::STATE_ALL ? FreshRSS_Entry::STATE_ALL : FreshRSS_Entry::STATE_NOT_READ;
|
||||||
|
}
|
||||||
|
public function _display_posts ($value) {
|
||||||
|
$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _onread_jump_next ($value) {
|
||||||
|
$this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _lazyload ($value) {
|
||||||
|
$this->data['lazyload'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _sticky_post($value) {
|
||||||
|
$this->data['sticky_post'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _reading_confirm($value) {
|
||||||
|
$this->data['reading_confirm'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _sort_order ($value) {
|
||||||
|
$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
|
||||||
|
}
|
||||||
|
public function _old_entries($value) {
|
||||||
|
$value = intval($value);
|
||||||
|
$this->data['old_entries'] = $value > 0 ? $value : 3;
|
||||||
|
}
|
||||||
|
public function _keep_history_default($value) {
|
||||||
|
$value = intval($value);
|
||||||
|
$this->data['keep_history_default'] = $value >= -1 ? $value : 0;
|
||||||
|
}
|
||||||
|
public function _ttl_default($value) {
|
||||||
|
$value = intval($value);
|
||||||
|
$this->data['ttl_default'] = $value >= -1 ? $value : 3600;
|
||||||
|
}
|
||||||
|
public function _shortcuts ($values) {
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
if (isset($this->data['shortcuts'][$key])) {
|
||||||
|
$this->data['shortcuts'][$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _passwordHash ($value) {
|
||||||
|
$this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
|
||||||
|
}
|
||||||
|
public function _apiPasswordHash ($value) {
|
||||||
|
$this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
|
||||||
|
}
|
||||||
|
public function _mail_login ($value) {
|
||||||
|
$value = filter_var($value, FILTER_VALIDATE_EMAIL);
|
||||||
|
if ($value) {
|
||||||
|
$this->data['mail_login'] = $value;
|
||||||
|
} else {
|
||||||
|
$this->data['mail_login'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _anon_access ($value) {
|
||||||
|
$this->data['anon_access'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _mark_when ($values) {
|
||||||
|
foreach ($values as $key => $value) {
|
||||||
|
if (isset($this->data['mark_when'][$key])) {
|
||||||
|
$this->data['mark_when'][$key] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _sharing ($values) {
|
||||||
|
$this->data['sharing'] = array();
|
||||||
|
foreach ($values as $value) {
|
||||||
|
if (!is_array($value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify URL and add default value when needed
|
||||||
|
if (isset($value['url'])) {
|
||||||
|
$is_url = (
|
||||||
|
filter_var ($value['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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a default name
|
||||||
|
if (empty($value['name'])) {
|
||||||
|
$value['name'] = $value['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data['sharing'][] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _queries ($values) {
|
||||||
|
$this->data['queries'] = array();
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$value = array_filter($value);
|
||||||
|
$params = $value;
|
||||||
|
unset($params['name']);
|
||||||
|
unset($params['url']);
|
||||||
|
$value['url'] = Minz_Url::display(array('params' => $params));
|
||||||
|
|
||||||
|
$this->data['queries'][] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _theme($value) {
|
||||||
|
$this->data['theme'] = $value;
|
||||||
|
}
|
||||||
|
public function _content_width($value) {
|
||||||
|
if ($value === 'medium' ||
|
||||||
|
$value === 'large' ||
|
||||||
|
$value === 'no_limit') {
|
||||||
|
$this->data['content_width'] = $value;
|
||||||
|
} else {
|
||||||
|
$this->data['content_width'] = 'thin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function _token($value) {
|
||||||
|
$this->data['token'] = $value;
|
||||||
|
}
|
||||||
|
public function _auto_load_more($value) {
|
||||||
|
$this->data['auto_load_more'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _topline_read($value) {
|
||||||
|
$this->data['topline_read'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _topline_favorite($value) {
|
||||||
|
$this->data['topline_favorite'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _topline_date($value) {
|
||||||
|
$this->data['topline_date'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _topline_link($value) {
|
||||||
|
$this->data['topline_link'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_read($value) {
|
||||||
|
$this->data['bottomline_read'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_favorite($value) {
|
||||||
|
$this->data['bottomline_favorite'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_sharing($value) {
|
||||||
|
$this->data['bottomline_sharing'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_tags($value) {
|
||||||
|
$this->data['bottomline_tags'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_date($value) {
|
||||||
|
$this->data['bottomline_date'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
public function _bottomline_link($value) {
|
||||||
|
$this->data['bottomline_link'] = ((bool)$value) && $value !== 'no';
|
||||||
|
}
|
||||||
|
}
|
7
sources/app/Models/Days.php
Executable file
7
sources/app/Models/Days.php
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Days {
|
||||||
|
const TODAY = 0;
|
||||||
|
const YESTERDAY = 1;
|
||||||
|
const BEFORE_YESTERDAY = 2;
|
||||||
|
}
|
191
sources/app/Models/Entry.php
Executable file
191
sources/app/Models/Entry.php
Executable file
|
@ -0,0 +1,191 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Entry extends Minz_Model {
|
||||||
|
const STATE_ALL = 0;
|
||||||
|
const STATE_READ = 1;
|
||||||
|
const STATE_NOT_READ = 2;
|
||||||
|
const STATE_FAVORITE = 4;
|
||||||
|
const STATE_NOT_FAVORITE = 8;
|
||||||
|
|
||||||
|
private $id = 0;
|
||||||
|
private $guid;
|
||||||
|
private $title;
|
||||||
|
private $author;
|
||||||
|
private $content;
|
||||||
|
private $link;
|
||||||
|
private $date;
|
||||||
|
private $is_read;
|
||||||
|
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 _id ($value) {
|
||||||
|
$this->id = $value;
|
||||||
|
}
|
||||||
|
public function _guid ($value) {
|
||||||
|
$this->guid = $value;
|
||||||
|
}
|
||||||
|
public function _title ($value) {
|
||||||
|
$this->title = $value;
|
||||||
|
}
|
||||||
|
public function _author ($value) {
|
||||||
|
$this->author = $value;
|
||||||
|
}
|
||||||
|
public function _content ($value) {
|
||||||
|
$this->content = $value;
|
||||||
|
}
|
||||||
|
public function _link ($value) {
|
||||||
|
$this->link = $value;
|
||||||
|
}
|
||||||
|
public function _date ($value) {
|
||||||
|
$value = intval($value);
|
||||||
|
$this->date = $value > 1 ? $value : time();
|
||||||
|
}
|
||||||
|
public function _isRead ($value) {
|
||||||
|
$this->is_read = $value;
|
||||||
|
}
|
||||||
|
public function _isFavorite ($value) {
|
||||||
|
$this->is_favorite = $value;
|
||||||
|
}
|
||||||
|
public function _feed ($value) {
|
||||||
|
$this->feed = $value;
|
||||||
|
}
|
||||||
|
public function _tags ($value) {
|
||||||
|
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),
|
||||||
|
'is_read' => $this->isRead (),
|
||||||
|
'is_favorite' => $this->isFavorite (),
|
||||||
|
'id_feed' => $this->feed (),
|
||||||
|
'tags' => $this->tags (true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
562
sources/app/Models/EntryDAO.php
Executable file
562
sources/app/Models/EntryDAO.php
Executable file
|
@ -0,0 +1,562 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_EntryDAO extends Minz_ModelPdo {
|
||||||
|
|
||||||
|
public function isCompressed() {
|
||||||
|
return parent::$sharedDbType !== 'sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEntryPrepare() {
|
||||||
|
$sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, '
|
||||||
|
. ($this->isCompressed() ? 'content_bin' : 'content')
|
||||||
|
. ', link, date, is_read, is_favorite, id_feed, tags) '
|
||||||
|
. 'VALUES(?, ?, ?, ?, '
|
||||||
|
. ($this->isCompressed() ? 'COMPRESS(?)' : '?')
|
||||||
|
. ', ?, ?, ?, ?, ?, ?)';
|
||||||
|
return $this->bd->prepare($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEntry($valuesTmp, $preparedStatement = null) {
|
||||||
|
$stm = $preparedStatement === null ? addEntryPrepare() : $preparedStatement;
|
||||||
|
|
||||||
|
$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'],
|
||||||
|
$valuesTmp['is_read'] ? 1 : 0,
|
||||||
|
$valuesTmp['is_favorite'] ? 1 : 0,
|
||||||
|
$valuesTmp['id_feed'],
|
||||||
|
substr($valuesTmp['tags'], 0, 1023),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($stm && $stm->execute($values)) {
|
||||||
|
return $this->bd->lastInsertId();
|
||||||
|
} else {
|
||||||
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
||||||
|
if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
|
||||||
|
Minz_Log::record('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
||||||
|
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR);
|
||||||
|
} /*else {
|
||||||
|
Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
|
||||||
|
. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::DEBUG);
|
||||||
|
}*/
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addEntryObject($entry, $conf, $feedHistory) {
|
||||||
|
$existingGuids = array_fill_keys(
|
||||||
|
$this->listLastGuidsByFeed($entry->feed(), 20), 1
|
||||||
|
);
|
||||||
|
|
||||||
|
$nb_month_old = max($conf->old_entries, 1);
|
||||||
|
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
|
||||||
|
|
||||||
|
$eDate = $entry->date(true);
|
||||||
|
|
||||||
|
if ($feedHistory == -2) {
|
||||||
|
$feedHistory = $conf->keep_history_default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($existingGuids[$entry->guid()]) &&
|
||||||
|
($feedHistory != 0 || $eDate >= $date_min)) {
|
||||||
|
$values = $entry->toArray();
|
||||||
|
|
||||||
|
$useDeclaredDate = empty($existingGuids);
|
||||||
|
$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
|
||||||
|
min(time(), $eDate) . uSecString() :
|
||||||
|
uTimeString();
|
||||||
|
|
||||||
|
return $this->addEntry($values);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't return Entry object to avoid a research in DB
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markFavorite($ids, $is_favorite = true) {
|
||||||
|
if (!is_array($ids)) {
|
||||||
|
$ids = array($ids);
|
||||||
|
}
|
||||||
|
$sql = 'UPDATE `' . $this->prefix . 'entry` '
|
||||||
|
. '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::record('SQL error markFavorite: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
|
||||||
|
if ($idMax == 0) {
|
||||||
|
$idMax = time() . '000000';
|
||||||
|
Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$affected = $stm->rowCount();
|
||||||
|
if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markReadCat($id, $idMax = 0) {
|
||||||
|
if ($idMax == 0) {
|
||||||
|
$idMax = time() . '000000';
|
||||||
|
Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id '
|
||||||
|
. '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::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$affected = $stm->rowCount();
|
||||||
|
if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markReadFeed($id, $idMax = 0) {
|
||||||
|
if ($idMax == 0) {
|
||||||
|
$idMax = time() . '000000';
|
||||||
|
Minz_Log::record($nb . 'Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
$this->bd->beginTransaction();
|
||||||
|
|
||||||
|
$sql = 'UPDATE `' . $this->prefix . 'entry` '
|
||||||
|
. 'SET is_read=1 '
|
||||||
|
. 'WHERE id_feed=? AND is_read=0 AND 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::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$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);
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
if (!($stm && $stm->execute($values))) {
|
||||||
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
||||||
|
Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$this->bd->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bd->commit();
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchByGuid($feed_id, $id) {
|
||||||
|
// 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(
|
||||||
|
$feed_id,
|
||||||
|
$id
|
||||||
|
);
|
||||||
|
|
||||||
|
$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, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 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. Tested on MySQL 5.5 with 150k articles
|
||||||
|
}
|
||||||
|
if ($firstId !== '') {
|
||||||
|
$where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
|
||||||
|
}
|
||||||
|
if (($date_min > 0) && ($type !== 's')) {
|
||||||
|
$where .= 'AND (e1.id >= ' . $date_min . '000000';
|
||||||
|
if ($showOlderUnreadsorFavorites) { //Lax date constraint
|
||||||
|
$where .= ' OR e1.is_read=0 OR e1.is_favorite=1 OR (f.keep_history <> 0';
|
||||||
|
if (intval($keepHistoryDefault) === 0) {
|
||||||
|
$where .= ' AND f.keep_history <> -2'; //default
|
||||||
|
}
|
||||||
|
$where .= ')';
|
||||||
|
}
|
||||||
|
$where .= ') ';
|
||||||
|
$joinFeed = true;
|
||||||
|
}
|
||||||
|
$search = '';
|
||||||
|
if ($filter !== '') {
|
||||||
|
require_once(LIB_PATH . '/lib_date.php');
|
||||||
|
$filter = trim($filter);
|
||||||
|
$filter = addcslashes($filter, '\\%_');
|
||||||
|
$terms = array_unique(explode(' ', $filter));
|
||||||
|
//sort($terms); //Put #tags first //TODO: Put the cheapest filters first
|
||||||
|
foreach ($terms as $word) {
|
||||||
|
$word = trim($word);
|
||||||
|
if (stripos($word, 'intitle:') === 0) {
|
||||||
|
$word = substr($word, strlen('intitle:'));
|
||||||
|
$search .= 'AND e1.title LIKE ? ';
|
||||||
|
$values[] = '%' . $word .'%';
|
||||||
|
} elseif (stripos($word, 'inurl:') === 0) {
|
||||||
|
$word = substr($word, strlen('inurl:'));
|
||||||
|
$search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
|
||||||
|
$values[] = '%' . $word .'%';
|
||||||
|
} elseif (stripos($word, 'author:') === 0) {
|
||||||
|
$word = substr($word, strlen('author:'));
|
||||||
|
$search .= 'AND e1.author LIKE ? ';
|
||||||
|
$values[] = '%' . $word .'%';
|
||||||
|
} elseif (stripos($word, 'date:') === 0) {
|
||||||
|
$word = substr($word, strlen('date:'));
|
||||||
|
list($minDate, $maxDate) = parseDateInterval($word);
|
||||||
|
if ($minDate) {
|
||||||
|
$search .= 'AND e1.id >= ' . $minDate . '000000 ';
|
||||||
|
}
|
||||||
|
if ($maxDate) {
|
||||||
|
$search .= 'AND e1.id <= ' . $maxDate . '000000 ';
|
||||||
|
}
|
||||||
|
} elseif (stripos($word, 'pubdate:') === 0) {
|
||||||
|
$word = substr($word, strlen('pubdate:'));
|
||||||
|
list($minDate, $maxDate) = parseDateInterval($word);
|
||||||
|
if ($minDate) {
|
||||||
|
$search .= 'AND e1.date >= ' . $minDate . ' ';
|
||||||
|
}
|
||||||
|
if ($maxDate) {
|
||||||
|
$search .= 'AND e1.date <= ' . $maxDate . ' ';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($word[0] === '#' && isset($word[1])) {
|
||||||
|
$search .= 'AND e1.tags LIKE ? ';
|
||||||
|
$values[] = '%' . $word .'%';
|
||||||
|
} else {
|
||||||
|
$search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? ';
|
||||||
|
$values[] = '%' . $word .'%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array($values,
|
||||||
|
'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
|
||||||
|
. ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '')
|
||||||
|
. 'WHERE ' . $where
|
||||||
|
. $search
|
||||||
|
. 'ORDER BY e1.id ' . $order
|
||||||
|
. ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) {
|
||||||
|
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault);
|
||||||
|
|
||||||
|
$sql = 'SELECT e.id, e.guid, e.title, e.author, '
|
||||||
|
. ($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, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API
|
||||||
|
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault);
|
||||||
|
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
$stm->execute($values);
|
||||||
|
|
||||||
|
return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function listLastGuidsByFeed($id, $n) {
|
||||||
|
$sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
$values = array($id);
|
||||||
|
$stm->execute($values);
|
||||||
|
return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = Minz_Configuration::dataBase();
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
129
sources/app/Models/EntryDAOSQLite.php
Executable file
129
sources/app/Models/EntryDAOSQLite.php
Executable file
|
@ -0,0 +1,129 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO {
|
||||||
|
|
||||||
|
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::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::record('SQL error markRead 1: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$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::record('SQL error markRead 2: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$this->bd->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->bd->commit();
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) {
|
||||||
|
if ($idMax == 0) {
|
||||||
|
$idMax = time() . '000000';
|
||||||
|
Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
$affected = $stm->rowCount();
|
||||||
|
if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markReadCat($id, $idMax = 0) {
|
||||||
|
if ($idMax == 0) {
|
||||||
|
$idMax = time() . '000000';
|
||||||
|
Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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(DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite');
|
||||||
|
}
|
||||||
|
}
|
32
sources/app/Models/Factory.php
Executable file
32
sources/app/Models/Factory.php
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Factory {
|
||||||
|
|
||||||
|
public static function createFeedDao() {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
if ($db['type'] === 'sqlite') {
|
||||||
|
return new FreshRSS_FeedDAOSQLite();
|
||||||
|
} else {
|
||||||
|
return new FreshRSS_FeedDAO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createEntryDao() {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
if ($db['type'] === 'sqlite') {
|
||||||
|
return new FreshRSS_EntryDAOSQLite();
|
||||||
|
} else {
|
||||||
|
return new FreshRSS_EntryDAO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createStatsDAO() {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
if ($db['type'] === 'sqlite') {
|
||||||
|
return new FreshRSS_StatsDAOSQLite();
|
||||||
|
} else {
|
||||||
|
return new FreshRSS_StatsDAO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
325
sources/app/Models/Feed.php
Executable file
325
sources/app/Models/Feed.php
Executable file
|
@ -0,0 +1,325 @@
|
||||||
|
<?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 = '';
|
||||||
|
|
||||||
|
public function __construct($url, $validate=true) {
|
||||||
|
if ($validate) {
|
||||||
|
$this->_url($url);
|
||||||
|
} else {
|
||||||
|
$this->url = $url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function id() {
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hash() {
|
||||||
|
if ($this->hash === null) {
|
||||||
|
$this->hash = hash('crc32b', Minz_Configuration::salt() . $this->url);
|
||||||
|
}
|
||||||
|
return $this->hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function url() {
|
||||||
|
return $this->url;
|
||||||
|
}
|
||||||
|
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 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();
|
||||||
|
$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()) {
|
||||||
|
throw new FreshRSS_Feed_Exception($feed->error() . ' [' . $url . ']');
|
||||||
|
}
|
||||||
|
|
||||||
|
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('<' => '<', '>' => '>', '"' => '"')); //HTML to HTML-PRE //ENT_COMPAT except &
|
||||||
|
$this->_name($title == '' ? $this->url : $title);
|
||||||
|
|
||||||
|
$this->_website(html_only_entity_decode($feed->get_link()));
|
||||||
|
$this->_description(html_only_entity_decode($feed->get_description()));
|
||||||
|
} else {
|
||||||
|
//The case of HTTP 301 Moved Permanently
|
||||||
|
$subscribe_url = $feed->subscribe_url(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($subscribe_url !== null && $subscribe_url !== $this->url) {
|
||||||
|
if ($this->httpAuth != '') {
|
||||||
|
// on enlève les id si authentification HTTP
|
||||||
|
$subscribe_url = preg_replace('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
|
||||||
|
}
|
||||||
|
$this->_url($subscribe_url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (($mtime === true) ||($mtime > $this->lastUpdate)) {
|
||||||
|
syslog(LOG_DEBUG, 'FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $subscribe_url);
|
||||||
|
$this->loadEntries($feed); // et on charge les articles du flux
|
||||||
|
} else {
|
||||||
|
syslog(LOG_DEBUG, 'FreshRSS use cache for ' . $subscribe_url);
|
||||||
|
$this->entries = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
|
||||||
|
unset($feed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadEntries($feed) {
|
||||||
|
$entries = array();
|
||||||
|
|
||||||
|
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 src="' . $elink . '" alt="" />';
|
||||||
|
} elseif (strpos($mime, 'audio/') === 0) {
|
||||||
|
$content .= '<br /><audio src="' . $elink . '" controls="controls" />';
|
||||||
|
} elseif (strpos($mime, 'video/') === 0) {
|
||||||
|
$content .= '<br /><video src="' . $elink . '" controls="controls" />';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
388
sources/app/Models/FeedDAO.php
Executable file
388
sources/app/Models/FeedDAO.php
Executable file
|
@ -0,0 +1,388 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_FeedDAO extends Minz_ModelPdo {
|
||||||
|
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::record('SQL error addFeed: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error updateFeed: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error updateLastUpdate: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error changeCategory: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error deleteFeed: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error deleteFeedByCategory: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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)) {
|
||||||
|
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::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$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::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
$this->bd->rollBack();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bd->commit();
|
||||||
|
return $affected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after
|
||||||
|
$sql = 'DELETE FROM `' . $this->prefix . 'entry` '
|
||||||
|
. 'WHERE id_feed = :id_feed AND id <= :id_max AND is_favorite=0 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 yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
|
||||||
|
$id_max = intval($date_min) . '000000';
|
||||||
|
|
||||||
|
$stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
|
||||||
|
$stm->bindParam(':id_max', $id_max, PDO::PARAM_INT);
|
||||||
|
$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::record('SQL error cleanOldEntries: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
19
sources/app/Models/FeedDAOSQLite.php
Executable file
19
sources/app/Models/FeedDAOSQLite.php
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
<?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::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
26
sources/app/Models/Log.php
Executable file
26
sources/app/Models/Log.php
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
<?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;
|
||||||
|
}
|
||||||
|
}
|
25
sources/app/Models/LogDAO.php
Executable file
25
sources/app/Models/LogDAO.php
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_LogDAO {
|
||||||
|
public static function lines() {
|
||||||
|
$logs = array ();
|
||||||
|
$handle = @fopen(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', '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(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', '');
|
||||||
|
}
|
||||||
|
}
|
44
sources/app/Models/Share.php
Executable file
44
sources/app/Models/Share.php
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_Share {
|
||||||
|
|
||||||
|
static public function generateUrl($options, $selected, $link, $title) {
|
||||||
|
$share = $options[$selected['type']];
|
||||||
|
$matches = array(
|
||||||
|
'~URL~',
|
||||||
|
'~TITLE~',
|
||||||
|
'~LINK~',
|
||||||
|
);
|
||||||
|
$replaces = array(
|
||||||
|
$selected['url'],
|
||||||
|
self::transformData($title, self::getTransform($share, 'title')),
|
||||||
|
self::transformData($link, self::getTransform($share, 'link')),
|
||||||
|
);
|
||||||
|
$url = str_replace($matches, $replaces, $share['url']);
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private function transformData($data, $transform) {
|
||||||
|
if (!is_array($transform)) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
if (count($transform) === 0) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
foreach ($transform as $action) {
|
||||||
|
$data = call_user_func($action, $data);
|
||||||
|
}
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
static private function getTransform($options, $type) {
|
||||||
|
$transform = $options['transform'];
|
||||||
|
|
||||||
|
if (array_key_exists($type, $transform)) {
|
||||||
|
return $transform[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
207
sources/app/Models/StatsDAO.php
Executable file
207
sources/app/Models/StatsDAO.php
Executable file
|
@ -0,0 +1,207 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_StatsDAO extends Minz_ModelPdo {
|
||||||
|
|
||||||
|
const ENTRY_COUNT_PERIOD = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates entry repartition for all feeds and for main stream.
|
||||||
|
* The repartition includes:
|
||||||
|
* - total entries
|
||||||
|
* - read entries
|
||||||
|
* - unread entries
|
||||||
|
* - favorite entries
|
||||||
|
*
|
||||||
|
* @return type
|
||||||
|
*/
|
||||||
|
public function calculateEntryRepartition() {
|
||||||
|
$repartition = array();
|
||||||
|
|
||||||
|
// Generates the repartition for the main stream of entry
|
||||||
|
$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
|
||||||
|
AND f.priority = 10
|
||||||
|
SQL;
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
$stm->execute();
|
||||||
|
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$repartition['main_stream'] = $res[0];
|
||||||
|
|
||||||
|
// Generates the repartition for all entries
|
||||||
|
$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
|
||||||
|
SQL;
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
$stm->execute();
|
||||||
|
$res = $stm->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$repartition['all_feeds'] = $res[0];
|
||||||
|
|
||||||
|
return $repartition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates entry count per day on a 30 days period.
|
||||||
|
* Returns the result as a JSON string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize an array for the entry count.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function initEntryCountArray() {
|
||||||
|
return array_map(function () {
|
||||||
|
return 0;
|
||||||
|
}, array_flip(range(-self::ENTRY_COUNT_PERIOD, -1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates feed count per category.
|
||||||
|
* Returns the result as a JSON string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
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 string
|
||||||
|
*/
|
||||||
|
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.name) AS name
|
||||||
|
, MAX(date) AS last_date
|
||||||
|
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 json_encode($serie);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function convertToPieSerie($data) {
|
||||||
|
$serie = array();
|
||||||
|
|
||||||
|
foreach ($data as $value) {
|
||||||
|
$value['data'] = array(array(0, (int) $value['data']));
|
||||||
|
$serie[] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json_encode($serie);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
sources/app/Models/StatsDAOSQLite.php
Executable file
37
sources/app/Models/StatsDAOSQLite.php
Executable file
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates entry count per day on a 30 days period.
|
||||||
|
* Returns the result as a JSON string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
120
sources/app/Models/Themes.php
Executable file
120
sources/app/Models/Themes.php
Executable file
|
@ -0,0 +1,120 @@
|
||||||
|
<?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' => '⊚',
|
||||||
|
'key' => '⚿',
|
||||||
|
'link' => '↗',
|
||||||
|
'login' => '🔒',
|
||||||
|
'logout' => '🔓',
|
||||||
|
'next' => '⏩',
|
||||||
|
'non-starred' => '☆',
|
||||||
|
'prev' => '⏪',
|
||||||
|
'read' => '☑',
|
||||||
|
'rss' => '☄',
|
||||||
|
'unread' => '☐',
|
||||||
|
'refresh' => '🔃', //↻
|
||||||
|
'search' => '🔍',
|
||||||
|
'share' => '♺',
|
||||||
|
'starred' => '★',
|
||||||
|
'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] . '" />';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _i($icon, $url_only = false) {
|
||||||
|
return FreshRSS_Themes::icon($icon, $url_only);
|
||||||
|
}
|
47
sources/app/Models/UserDAO.php
Executable file
47
sources/app/Models/UserDAO.php
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class FreshRSS_UserDAO extends Minz_ModelPdo {
|
||||||
|
public function createUser($username) {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
|
||||||
|
|
||||||
|
if (defined('SQL_CREATE_TABLES')) {
|
||||||
|
$sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category'));
|
||||||
|
$stm = $c->prepare($sql);
|
||||||
|
$ok = $stm && $stm->execute();
|
||||||
|
} else {
|
||||||
|
global $SQL_CREATE_TABLES;
|
||||||
|
if (is_array($SQL_CREATE_TABLES)) {
|
||||||
|
$ok = true;
|
||||||
|
foreach ($SQL_CREATE_TABLES as $instruction) {
|
||||||
|
$sql = sprintf($instruction, '', Minz_Translate::t('default_category'));
|
||||||
|
$stm = $c->prepare($sql);
|
||||||
|
$ok &= ($stm && $stm->execute());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ok) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
||||||
|
Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteUser($username) {
|
||||||
|
$db = Minz_Configuration::dataBase();
|
||||||
|
require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php');
|
||||||
|
|
||||||
|
$sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
|
||||||
|
$stm = $this->bd->prepare($sql);
|
||||||
|
if ($stm && $stm->execute()) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
|
||||||
|
Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
sources/app/SQL/install.sql.mysql.php
Executable file
61
sources/app/SQL/install.sql.mysql.php
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
<?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,
|
||||||
|
`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),
|
||||||
|
`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
|
||||||
|
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
|
||||||
|
ENGINE = INNODB;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
|
||||||
|
');
|
||||||
|
|
||||||
|
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
|
||||||
|
|
||||||
|
define('SQL_SHOW_TABLES', 'SHOW tables;');
|
58
sources/app/SQL/install.sql.sqlite.php
Executable file
58
sources/app/SQL/install.sql.sqlite.php
Executable file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
$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,
|
||||||
|
`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),
|
||||||
|
`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`);',
|
||||||
|
|
||||||
|
'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
|
||||||
|
);
|
||||||
|
|
||||||
|
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
|
||||||
|
|
||||||
|
define('SQL_SHOW_TABLES', 'SELECT name FROM sqlite_master WHERE type="table"');
|
54
sources/app/SQL/install_ynh.sql
Normal file
54
sources/app/SQL/install_ynh.sql
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS `YnoUser_category` (
|
||||||
|
`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 `YnoUser_feed` (
|
||||||
|
`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,
|
||||||
|
`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 `YnoUser_category`(`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 `YnoUser_entry` (
|
||||||
|
`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),
|
||||||
|
`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 `YnoUser_feed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
UNIQUE KEY (`id_feed`,`guid`), -- v0.7
|
||||||
|
INDEX (`is_favorite`), -- v0.7
|
||||||
|
INDEX (`is_read`) -- v0.7
|
||||||
|
) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
|
||||||
|
ENGINE = INNODB;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `YnoUser_category` (id, name) VALUES(1, "Uncategorized");
|
54
sources/app/actualize_script.php
Executable file
54
sources/app/actualize_script.php
Executable file
|
@ -0,0 +1,54 @@
|
||||||
|
<?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
|
||||||
|
|
||||||
|
Minz_Configuration::init();
|
||||||
|
|
||||||
|
$users = listUsers();
|
||||||
|
shuffle($users); //Process users in random order
|
||||||
|
array_unshift($users, Minz_Configuration::defaultUser()); //But always start with admin
|
||||||
|
$users = array_unique($users);
|
||||||
|
|
||||||
|
foreach ($users as $myUser) {
|
||||||
|
syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser);
|
||||||
|
if (defined('STDOUT')) {
|
||||||
|
fwrite(STDOUT, 'Actualize ' . $myUser . "...\n"); //Unbuffered
|
||||||
|
}
|
||||||
|
echo $myUser, ' '; //Buffered
|
||||||
|
|
||||||
|
$_GET['c'] = 'feed';
|
||||||
|
$_GET['a'] = 'actualize';
|
||||||
|
$_GET['ajax'] = 1;
|
||||||
|
$_GET['force'] = true;
|
||||||
|
$_SERVER['HTTP_HOST'] = '';
|
||||||
|
|
||||||
|
$freshRSS = new FreshRSS();
|
||||||
|
|
||||||
|
Minz_Configuration::_authType('none');
|
||||||
|
|
||||||
|
Minz_Session::init('FreshRSS');
|
||||||
|
Minz_Session::_param('currentUser', $myUser);
|
||||||
|
|
||||||
|
$freshRSS->init();
|
||||||
|
$freshRSS->run();
|
||||||
|
|
||||||
|
if (!invalidateHttpCache()) {
|
||||||
|
syslog(LOG_NOTICE, 'FreshRSS write access problem in ' . LOG_PATH . '/*.log!');
|
||||||
|
if (defined('STDERR')) {
|
||||||
|
fwrite(STDERR, 'Write access problem in ' . LOG_PATH . '/*.log!' . "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Minz_Session::unset_session(true);
|
||||||
|
Minz_ModelPdo::clean();
|
||||||
|
}
|
||||||
|
syslog(LOG_INFO, 'FreshRSS actualize done.');
|
||||||
|
if (defined('STDOUT')) {
|
||||||
|
fwrite(STDOUT, 'Done.' . "\n");
|
||||||
|
}
|
||||||
|
echo 'End.', "\n";
|
||||||
|
ob_end_flush();
|
384
sources/app/i18n/en.php
Executable file
384
sources/app/i18n/en.php
Executable file
|
@ -0,0 +1,384 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return array (
|
||||||
|
// LAYOUT
|
||||||
|
'login' => 'Login',
|
||||||
|
'login_with_persona' => 'Login with Persona',
|
||||||
|
'logout' => 'Logout',
|
||||||
|
'search' => 'Search words or #tags',
|
||||||
|
'search_short' => 'Search',
|
||||||
|
|
||||||
|
'configuration' => 'Configuration',
|
||||||
|
'users' => 'Users',
|
||||||
|
'categories' => 'Categories',
|
||||||
|
'category' => 'Category',
|
||||||
|
'feed' => 'Feed',
|
||||||
|
'feeds' => 'Feeds',
|
||||||
|
'shortcuts' => 'Shortcuts',
|
||||||
|
'queries' => 'User queries',
|
||||||
|
'query_search' => 'Search for "%s"',
|
||||||
|
'query_order_asc' => 'Display oldest articles first',
|
||||||
|
'query_order_desc' => 'Display newest articles first',
|
||||||
|
'query_get_category' => 'Display "%s" category',
|
||||||
|
'query_get_feed' => 'Display "%s" feed',
|
||||||
|
'query_get_all' => 'Display all articles',
|
||||||
|
'query_get_favorite' => 'Display favorite articles',
|
||||||
|
'query_state_0' => 'Display all articles',
|
||||||
|
'query_state_1' => 'Display read articles',
|
||||||
|
'query_state_2' => 'Display unread articles',
|
||||||
|
'query_state_3' => 'Display all articles',
|
||||||
|
'query_state_4' => 'Display favorite articles',
|
||||||
|
'query_state_5' => 'Display read favorite articles',
|
||||||
|
'query_state_6' => 'Display unread favorite articles',
|
||||||
|
'query_state_7' => 'Display favorite articles',
|
||||||
|
'query_state_8' => 'Display not favorite articles',
|
||||||
|
'query_state_9' => 'Display read not favorite articles',
|
||||||
|
'query_state_10' => 'Display unread not favorite articles',
|
||||||
|
'query_state_11' => 'Display not favorite articles',
|
||||||
|
'query_state_12' => 'Display all articles',
|
||||||
|
'query_state_13' => 'Display read articles',
|
||||||
|
'query_state_14' => 'Display unread articles',
|
||||||
|
'query_state_15' => 'Display all articles',
|
||||||
|
'query_number' => 'Query n°%d',
|
||||||
|
'add_query' => 'Add a query',
|
||||||
|
'no_query' => 'You haven’t created any user query yet.',
|
||||||
|
'query_filter' => 'Filter applied:',
|
||||||
|
'no_query_filter' => 'No filter',
|
||||||
|
'about' => 'About',
|
||||||
|
'stats' => 'Statistics',
|
||||||
|
'stats_idle' => 'Idle feeds',
|
||||||
|
'stats_main' => 'Main statistics',
|
||||||
|
|
||||||
|
'last_week' => 'Last week',
|
||||||
|
'last_month' => 'Last month',
|
||||||
|
'last_3_month' => 'Last three months',
|
||||||
|
'last_6_month' => 'Last six months',
|
||||||
|
'last_year' => 'Last year',
|
||||||
|
|
||||||
|
'your_rss_feeds' => 'Your RSS feeds',
|
||||||
|
'add_rss_feed' => 'Add a RSS feed',
|
||||||
|
'no_rss_feed' => 'No RSS feed',
|
||||||
|
'import_export' => 'Import / export',
|
||||||
|
'bookmark' => 'Subscribe (FreshRSS bookmark)',
|
||||||
|
|
||||||
|
'subscription_management' => 'Subscriptions management',
|
||||||
|
'main_stream' => 'Main stream',
|
||||||
|
'all_feeds' => 'All feeds',
|
||||||
|
'favorite_feeds' => 'Favourites (%s)',
|
||||||
|
'not_read' => '%d unread',
|
||||||
|
'not_reads' => '%d unread',
|
||||||
|
|
||||||
|
'filter' => 'Filter',
|
||||||
|
'see_website' => 'See website',
|
||||||
|
'administration' => 'Manage',
|
||||||
|
'actualize' => 'Actualize',
|
||||||
|
|
||||||
|
'mark_read' => 'Mark as read',
|
||||||
|
'mark_favorite' => 'Mark as favourite',
|
||||||
|
'mark_all_read' => 'Mark all as read',
|
||||||
|
'mark_feed_read' => 'Mark feed as read',
|
||||||
|
'mark_cat_read' => 'Mark category as read',
|
||||||
|
'before_one_day' => 'Before one day',
|
||||||
|
'before_one_week' => 'Before one week',
|
||||||
|
'display' => 'Display',
|
||||||
|
'normal_view' => 'Normal view',
|
||||||
|
'reader_view' => 'Reading view',
|
||||||
|
'global_view' => 'Global view',
|
||||||
|
'rss_view' => 'RSS feed',
|
||||||
|
'show_all_articles' => 'Show all articles',
|
||||||
|
'show_not_reads' => 'Show only unread',
|
||||||
|
'show_read' => 'Show only read',
|
||||||
|
'show_favorite' => 'Show only favorites',
|
||||||
|
'show_not_favorite' => 'Show all but favorites',
|
||||||
|
'older_first' => 'Oldest first',
|
||||||
|
'newer_first' => 'Newer first',
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
'first' => 'First',
|
||||||
|
'previous' => 'Previous',
|
||||||
|
'next' => 'Next',
|
||||||
|
'last' => 'Last',
|
||||||
|
|
||||||
|
// CONTROLLERS
|
||||||
|
'article_published_on' => 'This article originally appeared on <a href="%s">%s</a>',
|
||||||
|
'article_published_on_author' => 'This article originally appeared on <a href="%s">%s</a> by %s',
|
||||||
|
|
||||||
|
'access_denied' => 'You don’t have permission to access this page',
|
||||||
|
'page_not_found' => 'You are looking for a page which doesn’t exist',
|
||||||
|
'error_occurred' => 'An error occurred',
|
||||||
|
'error_occurred_update' => 'Nothing was changed',
|
||||||
|
|
||||||
|
'default_category' => 'Uncategorized',
|
||||||
|
'categories_updated' => 'Categories have been updated',
|
||||||
|
'categories_management' => 'Categories management',
|
||||||
|
'feed_updated' => 'Feed has been updated',
|
||||||
|
'rss_feed_management' => 'RSS feeds management',
|
||||||
|
'configuration_updated' => 'Configuration has been updated',
|
||||||
|
'sharing_management' => 'Sharing options management',
|
||||||
|
'bad_opml_file' => 'Your OPML file is invalid',
|
||||||
|
'shortcuts_updated' => 'Shortcuts have been updated',
|
||||||
|
'shortcuts_navigation' => 'Navigation',
|
||||||
|
'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
|
||||||
|
'shortcuts_article_action' => 'Article actions',
|
||||||
|
'shortcuts_other_action' => 'Other actions',
|
||||||
|
'feeds_marked_read' => 'Feeds have been marked as read',
|
||||||
|
'updated' => 'Modifications have been updated',
|
||||||
|
|
||||||
|
'already_subscribed' => 'You have already subscribed to <em>%s</em>',
|
||||||
|
'feed_added' => 'RSS feed <em>%s</em> has been added',
|
||||||
|
'feed_not_added' => '<em>%s</em> could not be added',
|
||||||
|
'internal_problem_feed' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.',
|
||||||
|
'invalid_url' => 'URL <em>%s</em> is invalid',
|
||||||
|
'feed_actualized' => '<em>%s</em> has been updated',
|
||||||
|
'n_feeds_actualized' => '%d feeds have been updated',
|
||||||
|
'feeds_actualized' => 'RSS feeds have been updated',
|
||||||
|
'no_feed_actualized' => 'No RSS feed has been updated',
|
||||||
|
'n_entries_deleted' => '%d articles have been deleted',
|
||||||
|
'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred',
|
||||||
|
'feeds_imported' => 'Your feeds have been imported and will now be updated',
|
||||||
|
'category_emptied' => 'Category has been emptied',
|
||||||
|
'feed_deleted' => 'Feed has been deleted',
|
||||||
|
'feed_validator' => 'Check the validity of the feed',
|
||||||
|
|
||||||
|
'optimization_complete' => 'Optimization complete',
|
||||||
|
|
||||||
|
'your_rss_feeds' => 'Your RSS feeds',
|
||||||
|
'your_favorites' => 'Your favourites',
|
||||||
|
'public' => 'Public',
|
||||||
|
'invalid_login' => 'Login is invalid',
|
||||||
|
|
||||||
|
// VIEWS
|
||||||
|
'save' => 'Save',
|
||||||
|
'delete' => 'Delete',
|
||||||
|
'cancel' => 'Cancel',
|
||||||
|
|
||||||
|
'back_to_rss_feeds' => '← Go back to your RSS feeds',
|
||||||
|
'feeds_moved_category_deleted' => 'When you delete a category, their feeds are automatically classified under <em>%s</em>.',
|
||||||
|
'category_number' => 'Category n°%d',
|
||||||
|
'ask_empty' => 'Clear?',
|
||||||
|
'number_feeds' => '%d feeds',
|
||||||
|
'can_not_be_deleted' => 'Cannot be deleted',
|
||||||
|
'add_category' => 'Add a category',
|
||||||
|
'new_category' => 'New category',
|
||||||
|
|
||||||
|
'javascript_for_shortcuts' => 'JavaScript must be enabled in order to use shortcuts',
|
||||||
|
'javascript_should_be_activated'=> 'JavaScript must be enabled',
|
||||||
|
'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
|
||||||
|
'see_on_website' => 'See on original website',
|
||||||
|
'next_article' => 'Skip to the next article',
|
||||||
|
'last_article' => 'Skip to the last article',
|
||||||
|
'previous_article' => 'Skip to the previous article',
|
||||||
|
'first_article' => 'Skip to the first article',
|
||||||
|
'next_page' => 'Skip to the next page',
|
||||||
|
'previous_page' => 'Skip to the previous page',
|
||||||
|
'collapse_article' => 'Collapse',
|
||||||
|
'auto_share' => 'Share',
|
||||||
|
'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
|
||||||
|
'focus_search' => 'Access search box',
|
||||||
|
|
||||||
|
'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
|
||||||
|
'import' => 'Import',
|
||||||
|
'export' => 'Export',
|
||||||
|
'export_opml' => 'Export list of feeds (OPML)',
|
||||||
|
'export_starred' => 'Export your favourites',
|
||||||
|
'starred_list' => 'List of favourite articles',
|
||||||
|
'feed_list' => 'List of %s articles',
|
||||||
|
'or' => 'or',
|
||||||
|
|
||||||
|
'informations' => 'Information',
|
||||||
|
'damn' => 'Damn!',
|
||||||
|
'feed_in_error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
|
||||||
|
'feed_empty' => 'This feed is empty. Please verify that it is still maintained.',
|
||||||
|
'feed_description' => 'Description',
|
||||||
|
'website_url' => 'Website URL',
|
||||||
|
'feed_url' => 'Feed URL',
|
||||||
|
'articles' => 'articles',
|
||||||
|
'number_articles' => 'Number of articles',
|
||||||
|
'by_feed' => 'by feed',
|
||||||
|
'by_default' => 'By default',
|
||||||
|
'keep_history' => 'Minimum number of articles to keep',
|
||||||
|
'ttl' => 'Do not automatically refresh more often than',
|
||||||
|
'categorize' => 'Store in a category',
|
||||||
|
'truncate' => 'Delete all articles',
|
||||||
|
'advanced' => 'Advanced',
|
||||||
|
'show_in_all_flux' => 'Show in main stream',
|
||||||
|
'yes' => 'Yes',
|
||||||
|
'no' => 'No',
|
||||||
|
'css_path_on_website' => 'Articles CSS path on original website',
|
||||||
|
'retrieve_truncated_feeds' => 'Retrieves truncated RSS feeds (attention, requires more time!)',
|
||||||
|
'http_authentication' => 'HTTP Authentication',
|
||||||
|
'http_username' => 'HTTP username',
|
||||||
|
'http_password' => 'HTTP password',
|
||||||
|
'blank_to_disable' => 'Leave blank to disable',
|
||||||
|
'share_name' => 'Share name to display',
|
||||||
|
'share_url' => 'Share URL to use',
|
||||||
|
'not_yet_implemented' => 'Not yet implemented',
|
||||||
|
'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds',
|
||||||
|
'no_selected_feed' => 'No feed selected.',
|
||||||
|
'think_to_add' => '<a href="./?c=configure&a=feed">You may add some feeds</a>.',
|
||||||
|
|
||||||
|
'current_user' => 'Current user',
|
||||||
|
'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>',
|
||||||
|
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
|
||||||
|
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>',
|
||||||
|
'persona_connection_email' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
|
||||||
|
'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)',
|
||||||
|
'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles',
|
||||||
|
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ',
|
||||||
|
'api_enabled' => 'Allow <abbr>API</abbr> access <small>(required for mobile apps)</small>',
|
||||||
|
'auth_token' => 'Authentication token',
|
||||||
|
'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>',
|
||||||
|
'login_configuration' => 'Login',
|
||||||
|
'is_admin' => 'is administrator',
|
||||||
|
'auth_type' => 'Authentication method',
|
||||||
|
'auth_none' => 'None (dangerous)',
|
||||||
|
'auth_form' => 'Web form (traditional, requires JavaScript)',
|
||||||
|
'http_auth' => 'HTTP (for advanced users with HTTPS)',
|
||||||
|
'auth_persona' => 'Mozilla Persona (modern, requires JavaScript)',
|
||||||
|
'users_list' => 'List of users',
|
||||||
|
'create_user' => 'Create new user',
|
||||||
|
'username' => 'Username',
|
||||||
|
'password' => 'Password',
|
||||||
|
'create' => 'Create',
|
||||||
|
'user_created' => 'User %s has been created',
|
||||||
|
'user_deleted' => 'User %s has been deleted',
|
||||||
|
|
||||||
|
'language' => 'Language',
|
||||||
|
'month' => 'months',
|
||||||
|
'archiving_configuration' => 'Archiving',
|
||||||
|
'delete_articles_every' => 'Remove articles after',
|
||||||
|
'purge_now' => 'Purge now',
|
||||||
|
'purge_completed' => 'Purge completed (%d articles deleted)',
|
||||||
|
'archiving_configuration_help' => 'More options are available in the individual stream settings',
|
||||||
|
'reading_configuration' => 'Reading',
|
||||||
|
'display_configuration' => 'Display',
|
||||||
|
'articles_per_page' => 'Number of articles per page',
|
||||||
|
'default_view' => 'Default view',
|
||||||
|
'sort_order' => 'Sort order',
|
||||||
|
'auto_load_more' => 'Load next articles at the page bottom',
|
||||||
|
'display_articles_unfolded' => 'Show articles unfolded by default',
|
||||||
|
'after_onread' => 'After “mark all as read”,',
|
||||||
|
'jump_next' => 'jump to next unread sibling (feed or category)',
|
||||||
|
'article_icons' => 'Article icons',
|
||||||
|
'top_line' => 'Top line',
|
||||||
|
'bottom_line' => 'Bottom line',
|
||||||
|
'img_with_lazyload' => 'Use "lazy load" mode to load pictures',
|
||||||
|
'sticky_post' => 'Stick the article to the top when opened',
|
||||||
|
'reading_confirm' => 'Display a confirmation dialog on “mark all as read” actions',
|
||||||
|
'auto_read_when' => 'Mark article as read…',
|
||||||
|
'article_viewed' => 'when article is viewed',
|
||||||
|
'article_open_on_website' => 'when article is opened on its original website',
|
||||||
|
'scroll' => 'during page scrolls',
|
||||||
|
'upon_reception' => 'upon reception of the article',
|
||||||
|
'your_shaarli' => 'Your Shaarli',
|
||||||
|
'your_wallabag' => 'Your wallabag',
|
||||||
|
'your_diaspora_pod' => 'Your Diaspora* pod',
|
||||||
|
'sharing' => 'Sharing',
|
||||||
|
'share' => 'Share',
|
||||||
|
'by_email' => 'By email',
|
||||||
|
'optimize_bdd' => 'Optimize database',
|
||||||
|
'optimize_todo_sometimes' => 'To do occasionally to reduce the size of the database',
|
||||||
|
'theme' => 'Theme',
|
||||||
|
'content_width' => 'Content width',
|
||||||
|
'width_thin' => 'Thin',
|
||||||
|
'width_medium' => 'Medium',
|
||||||
|
'width_large' => 'Large',
|
||||||
|
'width_no_limit' => 'No limit',
|
||||||
|
'more_information' => 'More information',
|
||||||
|
'activate_sharing' => 'Activate sharing',
|
||||||
|
'shaarli' => 'Shaarli',
|
||||||
|
'blogotext' => 'Blogotext',
|
||||||
|
'wallabag' => 'wallabag',
|
||||||
|
'diaspora' => 'Diaspora*',
|
||||||
|
'twitter' => 'Twitter',
|
||||||
|
'g+' => 'Google+',
|
||||||
|
'facebook' => 'Facebook',
|
||||||
|
'email' => 'Email',
|
||||||
|
'print' => 'Print',
|
||||||
|
|
||||||
|
'article' => 'Article',
|
||||||
|
'title' => 'Title',
|
||||||
|
'author' => 'Author',
|
||||||
|
'publication_date' => 'Date of publication',
|
||||||
|
'by' => 'by',
|
||||||
|
|
||||||
|
'load_more' => 'Load more articles',
|
||||||
|
'nothing_to_load' => 'There are no more articles',
|
||||||
|
|
||||||
|
'rss_feeds_of' => 'RSS feed of %s',
|
||||||
|
|
||||||
|
'refresh' => 'Refresh',
|
||||||
|
'no_feed_to_refresh' => 'There is no feed to refresh…',
|
||||||
|
|
||||||
|
'today' => 'Today',
|
||||||
|
'yesterday' => 'Yesterday',
|
||||||
|
'before_yesterday' => 'Before yesterday',
|
||||||
|
'new_article' => 'There are new available articles, click to refresh the page.',
|
||||||
|
'by_author' => 'By <em>%s</em>',
|
||||||
|
'related_tags' => 'Related tags',
|
||||||
|
'no_feed_to_display' => 'There is no article to show.',
|
||||||
|
|
||||||
|
'about_freshrss' => 'About FreshRSS',
|
||||||
|
'project_website' => 'Project website',
|
||||||
|
'lead_developer' => 'Lead developer',
|
||||||
|
'website' => 'Website',
|
||||||
|
'bugs_reports' => 'Bugs reports',
|
||||||
|
'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">on Github</a> or <a href="mailto:dev@marienfressinaud.fr">by mail</a>',
|
||||||
|
'license' => 'License',
|
||||||
|
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
|
||||||
|
'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.',
|
||||||
|
'credits' => 'Credits',
|
||||||
|
'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesn’t 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 used 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.',
|
||||||
|
'version' => 'Version',
|
||||||
|
|
||||||
|
'logs' => 'Logs',
|
||||||
|
'logs_empty' => 'Log file is empty',
|
||||||
|
'clear_logs' => 'Clear the logs',
|
||||||
|
|
||||||
|
'forbidden_access' => 'Access is forbidden!',
|
||||||
|
'login_required' => 'Login required:',
|
||||||
|
|
||||||
|
'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!',
|
||||||
|
|
||||||
|
// DATE
|
||||||
|
'january' => 'january',
|
||||||
|
'february' => 'february',
|
||||||
|
'march' => 'march',
|
||||||
|
'april' => 'april',
|
||||||
|
'may' => 'may',
|
||||||
|
'june' => 'june',
|
||||||
|
'july' => 'july',
|
||||||
|
'august' => 'august',
|
||||||
|
'september' => 'september',
|
||||||
|
'october' => 'october',
|
||||||
|
'november' => 'november',
|
||||||
|
'december' => 'december',
|
||||||
|
// special format for date() function
|
||||||
|
'Jan' => '\J\a\n\u\a\r\y',
|
||||||
|
'Feb' => '\F\e\b\r\u\a\r\y',
|
||||||
|
'Mar' => '\M\a\r\c\h',
|
||||||
|
'Apr' => '\A\p\r\i\l',
|
||||||
|
'May' => '\M\a\y',
|
||||||
|
'Jun' => '\J\u\n\e',
|
||||||
|
'Jul' => '\J\u\l\y',
|
||||||
|
'Aug' => '\A\u\g\u\s\t',
|
||||||
|
'Sep' => '\S\e\p\t\e\m\b\e\r',
|
||||||
|
'Oct' => '\O\c\t\o\b\e\r',
|
||||||
|
'Nov' => '\N\o\v\e\m\b\e\r',
|
||||||
|
'Dec' => '\D\e\c\e\m\b\e\r',
|
||||||
|
// format for date() function, %s allows to indicate month in letter
|
||||||
|
'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',
|
||||||
|
|
||||||
|
'status_favorites' => 'Favourites',
|
||||||
|
'status_read' => 'Read',
|
||||||
|
'status_unread' => 'Unread',
|
||||||
|
'status_total' => 'Total',
|
||||||
|
|
||||||
|
'stats_entry_repartition' => 'Entries repartition',
|
||||||
|
'stats_entry_per_day' => 'Entries per day (last 30 days)',
|
||||||
|
'stats_feed_per_category' => 'Feeds per category',
|
||||||
|
'stats_entry_per_category' => 'Entries per category',
|
||||||
|
'stats_top_feed' => 'Top ten feeds',
|
||||||
|
'stats_entry_count' => 'Entry count',
|
||||||
|
);
|
384
sources/app/i18n/fr.php
Executable file
384
sources/app/i18n/fr.php
Executable file
|
@ -0,0 +1,384 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return array (
|
||||||
|
// LAYOUT
|
||||||
|
'login' => 'Connexion',
|
||||||
|
'login_with_persona' => 'Connexion avec Persona',
|
||||||
|
'logout' => 'Déconnexion',
|
||||||
|
'search' => 'Rechercher des mots ou des #tags',
|
||||||
|
'search_short' => 'Rechercher',
|
||||||
|
|
||||||
|
'configuration' => 'Configuration',
|
||||||
|
'users' => 'Utilisateurs',
|
||||||
|
'categories' => 'Catégories',
|
||||||
|
'category' => 'Catégorie',
|
||||||
|
'feed' => 'Flux',
|
||||||
|
'feeds' => 'Flux',
|
||||||
|
'shortcuts' => 'Raccourcis',
|
||||||
|
'queries' => 'Filtres utilisateurs',
|
||||||
|
'query_search' => 'Recherche de "%s"',
|
||||||
|
'query_order_asc' => 'Afficher les articles les plus anciens en premier',
|
||||||
|
'query_order_desc' => 'Afficher les articles les plus récents en premier',
|
||||||
|
'query_get_category' => 'Afficher la catégorie "%s"',
|
||||||
|
'query_get_feed' => 'Afficher le flux "%s"',
|
||||||
|
'query_get_all' => 'Afficher tous les articles',
|
||||||
|
'query_get_favorite' => 'Afficher les articles favoris',
|
||||||
|
'query_state_0' => 'Afficher tous les articles',
|
||||||
|
'query_state_1' => 'Afficher les articles lus',
|
||||||
|
'query_state_2' => 'Afficher les articles non lus',
|
||||||
|
'query_state_3' => 'Afficher tous les articles',
|
||||||
|
'query_state_4' => 'Afficher les articles favoris',
|
||||||
|
'query_state_5' => 'Afficher les articles lus et favoris',
|
||||||
|
'query_state_6' => 'Afficher les articles non lus et favoris',
|
||||||
|
'query_state_7' => 'Afficher les articles favoris',
|
||||||
|
'query_state_8' => 'Afficher les articles non favoris',
|
||||||
|
'query_state_9' => 'Afficher les articles lus et non favoris',
|
||||||
|
'query_state_10' => 'Afficher les articles non lus et non favoris',
|
||||||
|
'query_state_11' => 'Afficher les articles non favoris',
|
||||||
|
'query_state_12' => 'Afficher tous les articles',
|
||||||
|
'query_state_13' => 'Afficher les articles lus',
|
||||||
|
'query_state_14' => 'Afficher les articles non lus',
|
||||||
|
'query_state_15' => 'Afficher tous les articles',
|
||||||
|
'query_number' => 'Filtre n°%d',
|
||||||
|
'add_query' => 'Créer un filtre',
|
||||||
|
'no_query' => 'Vous n’avez pas encore créé de filtre.',
|
||||||
|
'query_filter' => 'Filtres appliqués :',
|
||||||
|
'no_query_filter' => 'Aucun filtre appliqué',
|
||||||
|
'about' => 'À propos',
|
||||||
|
'stats' => 'Statistiques',
|
||||||
|
'stats_idle' => 'Flux inactifs',
|
||||||
|
'stats_main' => 'Statistiques principales',
|
||||||
|
|
||||||
|
'last_week' => 'La dernière semaine',
|
||||||
|
'last_month' => 'Le dernier mois',
|
||||||
|
'last_3_month' => 'Les derniers trois mois',
|
||||||
|
'last_6_month' => 'Les derniers six mois',
|
||||||
|
'last_year' => 'La dernière année',
|
||||||
|
|
||||||
|
'your_rss_feeds' => 'Vos flux RSS',
|
||||||
|
'add_rss_feed' => 'Ajouter un flux RSS',
|
||||||
|
'no_rss_feed' => 'Aucun flux RSS',
|
||||||
|
'import_export' => 'Importer / exporter',
|
||||||
|
'bookmark' => 'S’abonner (bookmark FreshRSS)',
|
||||||
|
|
||||||
|
'subscription_management' => 'Gestion des abonnements',
|
||||||
|
'main_stream' => 'Flux principal',
|
||||||
|
'all_feeds' => 'Tous les flux',
|
||||||
|
'favorite_feeds' => 'Favoris (%s)',
|
||||||
|
'not_read' => '%d non lu',
|
||||||
|
'not_reads' => '%d non lus',
|
||||||
|
|
||||||
|
'filter' => 'Filtrer',
|
||||||
|
'see_website' => 'Voir le site',
|
||||||
|
'administration' => 'Gérer',
|
||||||
|
'actualize' => 'Actualiser',
|
||||||
|
|
||||||
|
'mark_read' => 'Marquer comme lu',
|
||||||
|
'mark_favorite' => 'Mettre en favori',
|
||||||
|
'mark_all_read' => 'Tout marquer comme lu',
|
||||||
|
'mark_feed_read' => 'Marquer le flux comme lu',
|
||||||
|
'mark_cat_read' => 'Marquer la catégorie comme lue',
|
||||||
|
'before_one_day' => 'Antérieurs à 1 jour',
|
||||||
|
'before_one_week' => 'Antérieurs à 1 semaine',
|
||||||
|
'display' => 'Affichage',
|
||||||
|
'normal_view' => 'Vue normale',
|
||||||
|
'reader_view' => 'Vue lecture',
|
||||||
|
'global_view' => 'Vue globale',
|
||||||
|
'rss_view' => 'Flux RSS',
|
||||||
|
'show_all_articles' => 'Afficher tous les articles',
|
||||||
|
'show_not_reads' => 'Afficher les non lus',
|
||||||
|
'show_read' => 'Afficher les lus',
|
||||||
|
'show_favorite' => 'Afficher les favoris',
|
||||||
|
'show_not_favorite' => 'Afficher tout sauf les favoris',
|
||||||
|
'older_first' => 'Plus anciens en premier',
|
||||||
|
'newer_first' => 'Plus récents en premier',
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
'first' => 'Début',
|
||||||
|
'previous' => 'Précédent',
|
||||||
|
'next' => 'Suivant',
|
||||||
|
'last' => 'Fin',
|
||||||
|
|
||||||
|
// CONTROLLERS
|
||||||
|
'article_published_on' => 'Article publié initialement sur <a href="%s">%s</a>',
|
||||||
|
'article_published_on_author' => 'Article publié initialement sur <a href="%s">%s</a> par %s',
|
||||||
|
|
||||||
|
'access_denied' => 'Vous n’avez pas le droit d’accéder à cette page !',
|
||||||
|
'page_not_found' => 'La page que vous cherchez n’existe pas !',
|
||||||
|
'error_occurred' => 'Une erreur est survenue !',
|
||||||
|
'error_occurred_update' => 'Rien n’a été modifié !',
|
||||||
|
|
||||||
|
'default_category' => 'Sans catégorie',
|
||||||
|
'categories_updated' => 'Les catégories ont été mises à jour.',
|
||||||
|
'categories_management' => 'Gestion des catégories',
|
||||||
|
'feed_updated' => 'Le flux a été mis à jour.',
|
||||||
|
'rss_feed_management' => 'Gestion des flux RSS',
|
||||||
|
'configuration_updated' => 'La configuration a été mise à jour.',
|
||||||
|
'sharing_management' => 'Gestion des options de partage',
|
||||||
|
'bad_opml_file' => 'Votre fichier OPML n’est pas valide.',
|
||||||
|
'shortcuts_updated' => 'Les raccourcis ont été mis à jour.',
|
||||||
|
'shortcuts_navigation' => 'Navigation',
|
||||||
|
'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.',
|
||||||
|
'shortcuts_article_action' => 'Actions associées à l’article courant',
|
||||||
|
'shortcuts_other_action' => 'Autres actions',
|
||||||
|
'feeds_marked_read' => 'Les flux ont été marqués comme lus.',
|
||||||
|
'updated' => 'Modifications enregistrées.',
|
||||||
|
|
||||||
|
'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>',
|
||||||
|
'feed_added' => 'Le flux <em>%s</em> a bien été ajouté.',
|
||||||
|
'feed_not_added' => '<em>%s</em> n’a pas pu être ajouté.',
|
||||||
|
'internal_problem_feed' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.',
|
||||||
|
'invalid_url' => 'L’url <em>%s</em> est invalide.',
|
||||||
|
'feed_actualized' => '<em>%s</em> a été mis à jour.',
|
||||||
|
'n_feeds_actualized' => '%d flux ont été mis à jour.',
|
||||||
|
'feeds_actualized' => 'Les flux ont été mis à jour.',
|
||||||
|
'no_feed_actualized' => 'Aucun flux n’a pu être mis à jour.',
|
||||||
|
'n_entries_deleted' => '%d articles ont été supprimés.',
|
||||||
|
'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.',
|
||||||
|
'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.',
|
||||||
|
'category_emptied' => 'La catégorie a été vidée.',
|
||||||
|
'feed_deleted' => 'Le flux a été supprimé.',
|
||||||
|
'feed_validator' => 'Vérifier la valididé du flux',
|
||||||
|
|
||||||
|
'optimization_complete' => 'Optimisation terminée.',
|
||||||
|
|
||||||
|
'your_rss_feeds' => 'Vos flux RSS',
|
||||||
|
'your_favorites' => 'Vos favoris',
|
||||||
|
'public' => 'Public',
|
||||||
|
'invalid_login' => 'L’identifiant est invalide !',
|
||||||
|
|
||||||
|
// VIEWS
|
||||||
|
'save' => 'Enregistrer',
|
||||||
|
'delete' => 'Supprimer',
|
||||||
|
'cancel' => 'Annuler',
|
||||||
|
|
||||||
|
'back_to_rss_feeds' => '← Retour à vos flux RSS',
|
||||||
|
'feeds_moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans <em>%s</em>.',
|
||||||
|
'category_number' => 'Catégorie n°%d',
|
||||||
|
'ask_empty' => 'Vider ?',
|
||||||
|
'number_feeds' => '%d flux',
|
||||||
|
'can_not_be_deleted' => 'Ne peut pas être supprimée.',
|
||||||
|
'add_category' => 'Ajouter une catégorie',
|
||||||
|
'new_category' => 'Nouvelle catégorie',
|
||||||
|
|
||||||
|
'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.',
|
||||||
|
'javascript_should_be_activated'=> 'Le JavaScript doit être activé.',
|
||||||
|
'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
|
||||||
|
'see_on_website' => 'Voir sur le site d’origine',
|
||||||
|
'next_article' => 'Passer à l’article suivant',
|
||||||
|
'last_article' => 'Passer au dernier article',
|
||||||
|
'previous_article' => 'Passer à l’article précédent',
|
||||||
|
'first_article' => 'Passer au premier article',
|
||||||
|
'next_page' => 'Passer à la page suivante',
|
||||||
|
'previous_page' => 'Passer à la page précédente',
|
||||||
|
'collapse_article' => 'Refermer',
|
||||||
|
'auto_share' => 'Partager',
|
||||||
|
'auto_share_help' => 'S’il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
|
||||||
|
'focus_search' => 'Accéder à la recherche',
|
||||||
|
|
||||||
|
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
|
||||||
|
'import' => 'Importer',
|
||||||
|
'export' => 'Exporter',
|
||||||
|
'export_opml' => 'Exporter la liste des flux (OPML)',
|
||||||
|
'export_starred' => 'Exporter les favoris',
|
||||||
|
'starred_list' => 'Liste des articles favoris',
|
||||||
|
'feed_list' => 'Liste des articles de %s',
|
||||||
|
'or' => 'ou',
|
||||||
|
|
||||||
|
'informations' => 'Informations',
|
||||||
|
'damn' => 'Arf !',
|
||||||
|
'feed_in_error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.',
|
||||||
|
'feed_empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.',
|
||||||
|
'feed_description' => 'Description',
|
||||||
|
'website_url' => 'URL du site',
|
||||||
|
'feed_url' => 'URL du flux',
|
||||||
|
'articles' => 'articles',
|
||||||
|
'number_articles' => 'Nombre d’articles',
|
||||||
|
'by_feed' => 'par flux',
|
||||||
|
'by_default' => 'Par défaut',
|
||||||
|
'keep_history' => 'Nombre minimum d’articles à conserver',
|
||||||
|
'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que',
|
||||||
|
'categorize' => 'Ranger dans une catégorie',
|
||||||
|
'truncate' => 'Supprimer tous les articles',
|
||||||
|
'advanced' => 'Avancé',
|
||||||
|
'show_in_all_flux' => 'Afficher dans le flux principal',
|
||||||
|
'yes' => 'Oui',
|
||||||
|
'no' => 'Non',
|
||||||
|
'css_path_on_website' => 'Sélecteur CSS des articles sur le site d’origine',
|
||||||
|
'retrieve_truncated_feeds' => 'Permet de récupérer les flux tronqués (attention, demande plus de temps !)',
|
||||||
|
'http_authentication' => 'Authentification HTTP',
|
||||||
|
'http_username' => 'Identifiant HTTP',
|
||||||
|
'http_password' => 'Mot de passe HTTP',
|
||||||
|
'blank_to_disable' => 'Laissez vide pour désactiver',
|
||||||
|
'share_name' => 'Nom du partage à afficher',
|
||||||
|
'share_url' => 'URL du partage à utiliser',
|
||||||
|
'not_yet_implemented' => 'Pas encore implémenté',
|
||||||
|
'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP.',
|
||||||
|
'no_selected_feed' => 'Aucun flux sélectionné.',
|
||||||
|
'think_to_add' => '<a href="./?c=configure&a=feed">Vous pouvez ajouter des flux</a>.',
|
||||||
|
|
||||||
|
'current_user' => 'Utilisateur actuel',
|
||||||
|
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
|
||||||
|
'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
|
||||||
|
'default_user' => 'Nom de l’utilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>',
|
||||||
|
'persona_connection_email' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
|
||||||
|
'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)',
|
||||||
|
'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux',
|
||||||
|
'unsafe_autologin' => 'Autoriser les connexion automatiques non-sûres au format : ',
|
||||||
|
'api_enabled' => 'Autoriser l’accès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>',
|
||||||
|
'auth_token' => 'Jeton d’identification',
|
||||||
|
'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>',
|
||||||
|
'login_configuration' => 'Identification',
|
||||||
|
'is_admin' => 'est administrateur',
|
||||||
|
'auth_type' => 'Méthode d’authentification',
|
||||||
|
'auth_none' => 'Aucune (dangereux)',
|
||||||
|
'auth_form' => 'Formulaire (traditionnel, requiert JavaScript)',
|
||||||
|
'http_auth' => 'HTTP (pour utilisateurs avancés avec HTTPS)',
|
||||||
|
'auth_persona' => 'Mozilla Persona (moderne, requiert JavaScript)',
|
||||||
|
'users_list' => 'Liste des utilisateurs',
|
||||||
|
'create_user' => 'Créer un nouvel utilisateur',
|
||||||
|
'username' => 'Nom d’utilisateur',
|
||||||
|
'password' => 'Mot de passe',
|
||||||
|
'create' => 'Créer',
|
||||||
|
'user_created' => 'L’utilisateur %s a été créé.',
|
||||||
|
'user_deleted' => 'L’utilisateur %s a été supprimé.',
|
||||||
|
|
||||||
|
'language' => 'Langue',
|
||||||
|
'month' => 'mois',
|
||||||
|
'archiving_configuration' => 'Archivage',
|
||||||
|
'delete_articles_every' => 'Supprimer les articles après',
|
||||||
|
'purge_now' => 'Purger maintenant',
|
||||||
|
'purge_completed' => 'Purge effectuée (%d articles supprimés).',
|
||||||
|
'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.',
|
||||||
|
'reading_configuration' => 'Lecture',
|
||||||
|
'display_configuration' => 'Affichage',
|
||||||
|
'articles_per_page' => 'Nombre d’articles par page',
|
||||||
|
'default_view' => 'Vue par défaut',
|
||||||
|
'sort_order' => 'Ordre de tri',
|
||||||
|
'auto_load_more' => 'Charger les articles suivants en bas de page',
|
||||||
|
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
|
||||||
|
'after_onread' => 'Après “marquer tout comme lu”,',
|
||||||
|
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
|
||||||
|
'article_icons' => 'Icônes d’article',
|
||||||
|
'top_line' => 'Ligne du haut',
|
||||||
|
'bottom_line' => 'Ligne du bas',
|
||||||
|
'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images',
|
||||||
|
'sticky_post' => 'Aligner l’article en haut quand il est ouvert',
|
||||||
|
'reading_confirm' => 'Afficher une confirmation lors des actions “marquer tout comme lu”',
|
||||||
|
'auto_read_when' => 'Marquer un article comme lu…',
|
||||||
|
'article_viewed' => 'lorsque l’article est affiché',
|
||||||
|
'article_open_on_website' => 'lorsque l’article est ouvert sur le site d’origine',
|
||||||
|
'scroll' => 'au défilement de la page',
|
||||||
|
'upon_reception' => 'dès la réception du nouvel article',
|
||||||
|
'your_shaarli' => 'Votre Shaarli',
|
||||||
|
'your_wallabag' => 'Votre wallabag',
|
||||||
|
'your_diaspora_pod' => 'Votre pod Diaspora*',
|
||||||
|
'sharing' => 'Partage',
|
||||||
|
'share' => 'Partager',
|
||||||
|
'by_email' => 'Par courriel',
|
||||||
|
'optimize_bdd' => 'Optimiser la base de données',
|
||||||
|
'optimize_todo_sometimes' => 'À faire de temps en temps pour réduire la taille de la BDD',
|
||||||
|
'theme' => 'Thème',
|
||||||
|
'content_width' => 'Largeur du contenu',
|
||||||
|
'width_thin' => 'Fine',
|
||||||
|
'width_medium' => 'Moyenne',
|
||||||
|
'width_large' => 'Large',
|
||||||
|
'width_no_limit' => 'Pas de limite',
|
||||||
|
'more_information' => 'Plus d’informations',
|
||||||
|
'activate_sharing' => 'Activer le partage',
|
||||||
|
'shaarli' => 'Shaarli',
|
||||||
|
'blogotext' => 'Blogotext',
|
||||||
|
'wallabag' => 'wallabag',
|
||||||
|
'diaspora' => 'Diaspora*',
|
||||||
|
'twitter' => 'Twitter',
|
||||||
|
'g+' => 'Google+',
|
||||||
|
'facebook' => 'Facebook',
|
||||||
|
'email' => 'Courriel',
|
||||||
|
'print' => 'Imprimer',
|
||||||
|
|
||||||
|
'article' => 'Article',
|
||||||
|
'title' => 'Titre',
|
||||||
|
'author' => 'Auteur',
|
||||||
|
'publication_date' => 'Date de publication',
|
||||||
|
'by' => 'par',
|
||||||
|
|
||||||
|
'load_more' => 'Charger plus d’articles',
|
||||||
|
'nothing_to_load' => 'Fin des articles',
|
||||||
|
|
||||||
|
'rss_feeds_of' => 'Flux RSS de %s',
|
||||||
|
|
||||||
|
'refresh' => 'Actualisation',
|
||||||
|
'no_feed_to_refresh' => 'Il n’y a aucun flux à actualiser…',
|
||||||
|
|
||||||
|
'today' => 'Aujourd’hui',
|
||||||
|
'yesterday' => 'Hier',
|
||||||
|
'before_yesterday' => 'À partir d’avant-hier',
|
||||||
|
'new_article' => 'Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.',
|
||||||
|
'by_author' => 'Par <em>%s</em>',
|
||||||
|
'related_tags' => 'Tags associés',
|
||||||
|
'no_feed_to_display' => 'Il n’y a aucun article à afficher.',
|
||||||
|
|
||||||
|
'about_freshrss' => 'À propos de FreshRSS',
|
||||||
|
'project_website' => 'Site du projet',
|
||||||
|
'lead_developer' => 'Développeur principal',
|
||||||
|
'website' => 'Site Internet',
|
||||||
|
'bugs_reports' => 'Rapports de bugs',
|
||||||
|
'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">sur Github</a> ou <a href="mailto:dev@marienfressinaud.fr">par courriel</a>',
|
||||||
|
'license' => 'Licence',
|
||||||
|
'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
|
||||||
|
'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image 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.',
|
||||||
|
'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 n’utilise 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.',
|
||||||
|
'version' => 'Version',
|
||||||
|
|
||||||
|
'logs' => 'Logs',
|
||||||
|
'logs_empty' => 'Les logs sont vides.',
|
||||||
|
'clear_logs' => 'Effacer les logs',
|
||||||
|
|
||||||
|
'forbidden_access' => 'L’accès vous est interdit !',
|
||||||
|
'login_required' => 'Accès protégé par mot de passe :',
|
||||||
|
|
||||||
|
'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !',
|
||||||
|
|
||||||
|
// DATE
|
||||||
|
'january' => 'janvier',
|
||||||
|
'february' => 'février',
|
||||||
|
'march' => 'mars',
|
||||||
|
'april' => 'avril',
|
||||||
|
'may' => 'mai',
|
||||||
|
'june' => 'juin',
|
||||||
|
'july' => 'juillet',
|
||||||
|
'august' => 'août',
|
||||||
|
'september' => 'septembre',
|
||||||
|
'october' => 'octobre',
|
||||||
|
'november' => 'novembre',
|
||||||
|
'december' => 'décembre',
|
||||||
|
// format spécial pour la fonction date()
|
||||||
|
'Jan' => '\j\a\n\v\i\e\r',
|
||||||
|
'Feb' => '\f\é\v\r\i\e\r',
|
||||||
|
'Mar' => '\m\a\r\s',
|
||||||
|
'Apr' => '\a\v\r\i\l',
|
||||||
|
'May' => '\m\a\i',
|
||||||
|
'Jun' => '\j\u\i\n',
|
||||||
|
'Jul' => '\j\u\i\l\l\e\t',
|
||||||
|
'Aug' => '\a\o\û\t',
|
||||||
|
'Sep' => '\s\e\p\t\e\m\b\r\e',
|
||||||
|
'Oct' => '\o\c\t\o\b\r\e',
|
||||||
|
'Nov' => '\n\o\v\e\m\b\r\e',
|
||||||
|
'Dec' => '\d\é\c\e\m\b\r\e',
|
||||||
|
// format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres
|
||||||
|
'format_date' => 'j %s Y',
|
||||||
|
'format_date_hour' => 'j %s Y \à H\:i',
|
||||||
|
|
||||||
|
'status_favorites' => 'favoris',
|
||||||
|
'status_read' => 'lus',
|
||||||
|
'status_unread' => 'non lus',
|
||||||
|
'status_total' => 'total',
|
||||||
|
|
||||||
|
'stats_entry_repartition' => 'Répartition des articles',
|
||||||
|
'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)',
|
||||||
|
'stats_feed_per_category' => 'Flux par catégorie',
|
||||||
|
'stats_entry_per_category' => 'Articles par catégorie',
|
||||||
|
'stats_top_feed' => 'Les dix plus gros flux',
|
||||||
|
'stats_entry_count' => 'Nombre d’articles',
|
||||||
|
);
|
67
sources/app/i18n/install.en.php
Executable file
67
sources/app/i18n/install.en.php
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
return array (
|
||||||
|
'freshrss_installation' => 'Installation · FreshRSS',
|
||||||
|
'freshrss' => 'FreshRSS',
|
||||||
|
'installation_step' => 'Installation — step %d · FreshRSS',
|
||||||
|
'steps' => 'Steps',
|
||||||
|
'checks' => 'Checks',
|
||||||
|
'general_configuration' => 'General configuration',
|
||||||
|
'bdd_configuration' => 'Database configuration',
|
||||||
|
'bdd_type' => 'Type of database',
|
||||||
|
'version_update' => 'Update',
|
||||||
|
'this_is_the_end' => 'This is the end',
|
||||||
|
|
||||||
|
'ok' => 'Ok!',
|
||||||
|
'congratulations' => 'Congratulations!',
|
||||||
|
'attention' => 'Attention!',
|
||||||
|
'damn' => 'Damn!',
|
||||||
|
'oops' => 'Oops!',
|
||||||
|
'next_step' => 'Go to the next step',
|
||||||
|
|
||||||
|
'language_defined' => 'Language has been defined.',
|
||||||
|
'choose_language' => 'Choose a language for FreshRSS',
|
||||||
|
|
||||||
|
'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled',
|
||||||
|
'php_is_ok' => 'Your PHP version is %s, which is compatible with FreshRSS',
|
||||||
|
'php_is_nok' => 'Your PHP version is %s but FreshRSS requires at least version %s',
|
||||||
|
'minz_is_ok' => 'You have the Minz framework',
|
||||||
|
'minz_is_nok' => 'You lack the Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.',
|
||||||
|
'curl_is_ok' => 'You have version %s of cURL',
|
||||||
|
'curl_is_nok' => 'You lack cURL (php5-curl package)',
|
||||||
|
'pdomysql_is_ok' => 'You have PDO and its driver for MySQL',
|
||||||
|
'pdomysql_is_nok' => 'You lack PDO or its driver for MySQL (php5-mysql package)',
|
||||||
|
'dom_is_ok' => 'You have the required library to browse the DOM',
|
||||||
|
'dom_is_nok' => 'You lack a required library to browse the DOM (php-xml package)',
|
||||||
|
'pcre_is_ok' => 'You have the required library for regular expressions (PCRE)',
|
||||||
|
'pcre_is_nok' => 'You lack a required library for regular expressions (php-pcre)',
|
||||||
|
'ctype_is_ok' => 'You have the required library for character type checking (ctype)',
|
||||||
|
'ctype_is_nok' => 'You lack a required library for character type checking (php-ctype)',
|
||||||
|
'cache_is_ok' => 'Permissions on cache directory are good',
|
||||||
|
'log_is_ok' => 'Permissions on logs directory are good',
|
||||||
|
'favicons_is_ok' => 'Permissions on favicons directory are good',
|
||||||
|
'data_is_ok' => 'Permissions on data directory are good',
|
||||||
|
'persona_is_ok' => 'Permissions on Mozilla Persona directory are good',
|
||||||
|
'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
|
||||||
|
'fix_errors_before' => 'Fix errors before skip to the next step.',
|
||||||
|
|
||||||
|
'general_conf_is_ok' => 'General configuration has been saved.',
|
||||||
|
'random_string' => 'Random string',
|
||||||
|
'change_value' => 'You should change this value by any other',
|
||||||
|
'base_url' => 'Base URL',
|
||||||
|
'do_not_change_if_doubt' => 'Don’t change if you doubt about it',
|
||||||
|
|
||||||
|
'bdd_conf_is_ok' => 'Database configuration has been saved.',
|
||||||
|
'bdd_conf_is_ko' => 'Verify your database information.',
|
||||||
|
'host' => 'Host',
|
||||||
|
'bdd' => 'Database',
|
||||||
|
'prefix' => 'Table prefix',
|
||||||
|
|
||||||
|
'update_start' => 'Start update process',
|
||||||
|
'update_long' => 'This can take a long time, depending on the size of your database. You may have to wait for this page to time out (~5 minutes) and then refresh this page.',
|
||||||
|
'update_end' => 'Update process is completed, now you can go to the final step.',
|
||||||
|
|
||||||
|
|
||||||
|
'installation_is_ok' => 'The installation process was successful.<br />The final step will now attempt to delete any file and database backup created during the update process.<br />You may choose to skip this step by deleting <kbd>./data/do-install.txt</kbd> manually.',
|
||||||
|
'finish_installation' => 'Complete installation',
|
||||||
|
'install_not_deleted' => 'Something went wrong; you must delete the file <em>%s</em> manually.',
|
||||||
|
);
|
66
sources/app/i18n/install.fr.php
Executable file
66
sources/app/i18n/install.fr.php
Executable file
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
return array (
|
||||||
|
'freshrss_installation' => 'Installation · FreshRSS',
|
||||||
|
'freshrss' => 'FreshRSS',
|
||||||
|
'installation_step' => 'Installation — étape %d · FreshRSS',
|
||||||
|
'steps' => 'Étapes',
|
||||||
|
'checks' => 'Vérifications',
|
||||||
|
'general_configuration' => 'Configuration générale',
|
||||||
|
'bdd_configuration' => 'Base de données',
|
||||||
|
'bdd_type' => 'Type de base de données',
|
||||||
|
'version_update' => 'Mise à jour',
|
||||||
|
'this_is_the_end' => 'This is the end',
|
||||||
|
|
||||||
|
'ok' => 'Ok !',
|
||||||
|
'congratulations' => 'Félicitations !',
|
||||||
|
'attention' => 'Attention !',
|
||||||
|
'damn' => 'Arf !',
|
||||||
|
'oops' => 'Oups !',
|
||||||
|
'next_step' => 'Passer à l’étape suivante',
|
||||||
|
|
||||||
|
'language_defined' => 'La langue a bien été définie.',
|
||||||
|
'choose_language' => 'Choisissez la langue pour FreshRSS',
|
||||||
|
|
||||||
|
'javascript_is_better' => 'FreshRSS est plus agréable à utiliser avec JavaScript activé',
|
||||||
|
'php_is_ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS',
|
||||||
|
'php_is_nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s',
|
||||||
|
'minz_is_ok' => 'Vous disposez du framework Minz',
|
||||||
|
'minz_is_nok' => 'Vous ne disposez pas de la librairie Minz. Vous devriez exécuter le script <em>build.sh</em> ou bien <a href="https://github.com/marienfressinaud/MINZ">la télécharger sur Github</a> et installer dans le répertoire <em>%s</em> le contenu de son répertoire <em>/lib</em>.',
|
||||||
|
'curl_is_ok' => 'Vous disposez de cURL dans sa version %s',
|
||||||
|
'curl_is_nok' => 'Vous ne disposez pas de cURL (paquet php5-curl)',
|
||||||
|
'pdomysql_is_ok' => 'Vous disposez de PDO et de son driver pour MySQL (paquet php5-mysql)',
|
||||||
|
'pdomysql_is_nok' => 'Vous ne disposez pas de PDO ou de son driver pour MySQL',
|
||||||
|
'dom_is_ok' => 'Vous disposez du nécessaire pour parcourir le DOM',
|
||||||
|
'dom_is_nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml)',
|
||||||
|
'pcre_is_ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE)',
|
||||||
|
'pcre_is_nok' => 'Il manque une librairie pour les expressions régulières (php-pcre)',
|
||||||
|
'ctype_is_ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype)',
|
||||||
|
'ctype_is_nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype)',
|
||||||
|
'cache_is_ok' => 'Les droits sur le répertoire de cache sont bons',
|
||||||
|
'log_is_ok' => 'Les droits sur le répertoire des logs sont bons',
|
||||||
|
'favicons_is_ok' => 'Les droits sur le répertoire des favicons sont bons',
|
||||||
|
'data_is_ok' => 'Les droits sur le répertoire de data sont bons',
|
||||||
|
'persona_is_ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons',
|
||||||
|
'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans',
|
||||||
|
'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.',
|
||||||
|
|
||||||
|
'general_conf_is_ok' => 'La configuration générale a été enregistrée.',
|
||||||
|
'random_string' => 'Chaîne aléatoire',
|
||||||
|
'change_value' => 'Vous devriez changer cette valeur par n’importe quelle autre',
|
||||||
|
'base_url' => 'Base de l’URL',
|
||||||
|
'do_not_change_if_doubt' => 'Laissez tel quel dans le doute',
|
||||||
|
|
||||||
|
'bdd_conf_is_ok' => 'La configuration de la base de données a été enregistrée.',
|
||||||
|
'bdd_conf_is_ko' => 'Vérifiez les informations d’accès à la base de données.',
|
||||||
|
'host' => 'Hôte',
|
||||||
|
'bdd' => 'Base de données',
|
||||||
|
'prefix' => 'Préfixe des tables',
|
||||||
|
|
||||||
|
'update_start' => 'Lancer la mise à jour',
|
||||||
|
'update_long' => 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.',
|
||||||
|
'update_end' => 'La mise à jour est terminée, vous pouvez maintenant passer à l’étape finale.',
|
||||||
|
|
||||||
|
'installation_is_ok' => 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer les fichiers ainsi que d’éventuelles copies de base de données créés durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape en supprimant <kbd>./data/do-install.txt</kbd> manuellement.',
|
||||||
|
'finish_installation' => 'Terminer l’installation',
|
||||||
|
'install_not_deleted' => 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
|
||||||
|
);
|
13
sources/app/index.html
Executable file
13
sources/app/index.html
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="Refresh" content="0; url=/" />
|
||||||
|
<title>Redirection</title>
|
||||||
|
<meta name="robots" content="noindex" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<p><a href="/">Redirection</a></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
1124
sources/app/install.php
Executable file
1124
sources/app/install.php
Executable file
File diff suppressed because it is too large
Load diff
25
sources/app/layout/aside_configure.phtml
Executable file
25
sources/app/layout/aside_configure.phtml
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
<ul class="nav nav-list aside">
|
||||||
|
<li class="nav-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'display' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('display_configuration'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'reading' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'reading'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'archiving' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'sharing' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'shortcut' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'queries' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'queries'); ?>"><?php echo Minz_Translate::t ('queries'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'users' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
75
sources/app/layout/aside_feed.phtml
Executable file
75
sources/app/layout/aside_feed.phtml
Executable file
|
@ -0,0 +1,75 @@
|
||||||
|
<ul class="nav nav-list aside aside_feed">
|
||||||
|
<li class="nav-header"><?php echo Minz_Translate::t ('your_rss_feeds'); ?></li>
|
||||||
|
|
||||||
|
<li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>" autocomplete="off">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="url" name="url_rss" placeholder="<?php echo Minz_Translate::t ('add_rss_feed'); ?>" />
|
||||||
|
<div class="dropdown">
|
||||||
|
<div id="dropdown-cat" class="dropdown-target"></div>
|
||||||
|
|
||||||
|
<a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo FreshRSS_Themes::icon('down'); ?></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
|
||||||
|
<li class="dropdown-header"><?php echo Minz_Translate::t ('category'); ?></li>
|
||||||
|
|
||||||
|
<li class="input">
|
||||||
|
<select name="category" id="category">
|
||||||
|
<?php foreach ($this->categories as $cat) { ?>
|
||||||
|
<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id () == 1 ? ' selected="selected"' : ''; ?>>
|
||||||
|
<?php echo $cat->name (); ?>
|
||||||
|
</option>
|
||||||
|
<?php } ?>
|
||||||
|
<option value="nc"><?php echo Minz_Translate::t ('new_category'); ?></option>
|
||||||
|
</select>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="input" style="display:none">
|
||||||
|
<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="separator"></li>
|
||||||
|
|
||||||
|
<li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li>
|
||||||
|
<li class="input">
|
||||||
|
<input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" />
|
||||||
|
</li>
|
||||||
|
<li class="input">
|
||||||
|
<input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('add'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form></li>
|
||||||
|
|
||||||
|
<li class="item">
|
||||||
|
<a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&url_rss='+encodeURIComponent(url), '_blank');})();">
|
||||||
|
<?php echo Minz_Translate::t('bookmark'); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="item<?php echo Minz_Request::controllerName () == 'importExport' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('importExport', 'index'); ?>"><?php echo Minz_Translate::t ('import_export'); ?></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'categorize' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Minz_Translate::t ('categories_management'); ?></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="separator"></li>
|
||||||
|
|
||||||
|
<?php if (!empty ($this->feeds)) { ?>
|
||||||
|
<?php foreach ($this->feeds as $feed) { ?>
|
||||||
|
<?php $nbEntries = $feed->nbEntries (); ?>
|
||||||
|
<li class="item<?php echo (isset($this->flux) && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>">
|
||||||
|
<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
|
||||||
|
<?php echo $feed->name (); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } else { ?>
|
||||||
|
<li class="item disable"><?php echo Minz_Translate::t ('no_rss_feed'); ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
88
sources/app/layout/aside_flux.phtml
Executable file
88
sources/app/layout/aside_flux.phtml
Executable file
|
@ -0,0 +1,88 @@
|
||||||
|
<div class="aside aside_flux" id="aside_flux">
|
||||||
|
<a class="toggle_aside" href="#close"><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
|
||||||
|
<ul class="categories">
|
||||||
|
<?php if ($this->loginOk) { ?>
|
||||||
|
<li>
|
||||||
|
<div class="stick">
|
||||||
|
<a class="btn btn-important" href="<?php echo _url ('configure', 'feed'); ?>"><?php echo Minz_Translate::t ('subscription_management'); ?></a>
|
||||||
|
<a class="btn btn-important" href="<?php echo _url ('configure', 'categorize'); ?>" title="<?php echo Minz_Translate::t ('categories_management'); ?>"><?php echo FreshRSS_Themes::icon('category-white'); ?></a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<?php } elseif (Minz_Configuration::needsLogin()) { ?>
|
||||||
|
<li><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$arUrl = array('c' => 'index', 'a' => 'index', 'params' => array());
|
||||||
|
if ($this->conf->view_mode !== Minz_Request::param('output', 'normal')) {
|
||||||
|
$arUrl['params']['output'] = 'normal';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<li>
|
||||||
|
<div class="category all<?php echo $this->get_c == 'a' ? ' active' : ''; ?>">
|
||||||
|
<a data-unread="<?php echo formatNumber($this->nb_not_read); ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('all'); ?>
|
||||||
|
<?php echo Minz_Translate::t ('main_stream'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<div class="category favorites<?php echo $this->get_c == 's' ? ' active' : ''; ?>">
|
||||||
|
<a data-unread="<?php echo formatNumber($this->nb_favorites['unread']); ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('bookmark'); ?>
|
||||||
|
<?php echo Minz_Translate::t('favorite_feeds', formatNumber($this->nb_favorites['all'])); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
foreach ($this->cat_aside as $cat) {
|
||||||
|
$feeds = $cat->feeds ();
|
||||||
|
if (!empty ($feeds)) {
|
||||||
|
?><li><?php
|
||||||
|
$c_active = false;
|
||||||
|
if ($this->get_c == $cat->id ()) {
|
||||||
|
$c_active = true;
|
||||||
|
}
|
||||||
|
?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php
|
||||||
|
?><a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name (); ?></a><?php
|
||||||
|
?><a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a><?php
|
||||||
|
?></div><?php
|
||||||
|
?><ul class="feeds<?php echo $c_active ? ' active' : ''; ?>"><?php
|
||||||
|
foreach ($feeds as $feed) {
|
||||||
|
$feed_id = $feed->id ();
|
||||||
|
$nbEntries = $feed->nbEntries ();
|
||||||
|
$f_active = ($this->get_f == $feed_id);
|
||||||
|
?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"><?php
|
||||||
|
?><div class="dropdown"><?php
|
||||||
|
?><div class="dropdown-target"></div><?php
|
||||||
|
?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a><?php
|
||||||
|
/* feed_config_template */
|
||||||
|
?></div><?php
|
||||||
|
?> <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <?php
|
||||||
|
?><a class="feed" data-unread="<?php echo formatNumber($feed->nbNotRead()); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed_id; echo Minz_Url::display($arUrl); ?>"><?php echo $feed->name(); ?></a><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
?></ul><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
</ul>
|
||||||
|
<span class="aside_flux_ender"><!-- For fixed menu --></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="feed_config_template" type="text/html">
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('filter'); ?></a></li>
|
||||||
|
<li class="item"><a target="_blank" href="http://example.net/"><?php echo Minz_Translate::t ('see_website'); ?></a></li>
|
||||||
|
<?php if ($this->loginOk) { ?>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('administration'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('actualize'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url ('entry', 'read', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a></li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</script>
|
9
sources/app/layout/aside_stats.phtml
Executable file
9
sources/app/layout/aside_stats.phtml
Executable file
|
@ -0,0 +1,9 @@
|
||||||
|
<ul class="nav nav-list aside">
|
||||||
|
<li class="nav-header"><?php echo Minz_Translate::t ('stats'); ?></li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'index' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('stats', 'index'); ?>"><?php echo Minz_Translate::t ('stats_main'); ?></a>
|
||||||
|
</li>
|
||||||
|
<li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>">
|
||||||
|
<a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
109
sources/app/layout/header.phtml
Executable file
109
sources/app/layout/header.phtml
Executable file
|
@ -0,0 +1,109 @@
|
||||||
|
<?php
|
||||||
|
if (Minz_Configuration::canLogIn()) {
|
||||||
|
?><ul class="nav nav-head nav-login"><?php
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
if ($this->loginOk) {
|
||||||
|
?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _t('logout'); ?></a></li><?php
|
||||||
|
} else {
|
||||||
|
?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
if ($this->loginOk) {
|
||||||
|
?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="#"><?php echo _t('logout'); ?></a></li><?php
|
||||||
|
} else {
|
||||||
|
?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?></ul><?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="header">
|
||||||
|
<div class="item title">
|
||||||
|
<h1>
|
||||||
|
<a href="<?php echo _url('index', 'index'); ?>">
|
||||||
|
<img class="logo" src="<?php echo _i('icon', true); ?>" alt="⊚" />
|
||||||
|
<?php echo Minz_Configuration::title(); ?>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item search">
|
||||||
|
<?php if ($this->loginOk || Minz_Configuration::allowAnonymous()) { ?>
|
||||||
|
<form action="<?php echo _url('index', 'index'); ?>" method="get">
|
||||||
|
<div class="stick">
|
||||||
|
<?php $search = Minz_Request::param('search', ''); ?>
|
||||||
|
<input type="search" name="search" id="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo _t('search'); ?>" />
|
||||||
|
|
||||||
|
<?php $get = Minz_Request::param('get', ''); ?>
|
||||||
|
<?php if ($get != '') { ?>
|
||||||
|
<input type="hidden" name="get" value="<?php echo $get; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $order = Minz_Request::param('order', ''); ?>
|
||||||
|
<?php if ($order != '') { ?>
|
||||||
|
<input type="hidden" name="order" value="<?php echo $order; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $state = Minz_Request::param('state', ''); ?>
|
||||||
|
<?php if ($state != '') { ?>
|
||||||
|
<input type="hidden" name="state" value="<?php echo $state; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<button class="btn" type="submit"><?php echo _i('search'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($this->loginOk) { ?>
|
||||||
|
<div class="item configure">
|
||||||
|
<div class="dropdown">
|
||||||
|
<div id="dropdown-configure" class="dropdown-target"></div>
|
||||||
|
<a class="btn dropdown-toggle" href="#dropdown-configure"><?php echo _i('configure'); ?></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
<li class="dropdown-header"><?php echo _t('configuration'); ?></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'display'); ?>"><?php echo _t('display_configuration'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'reading'); ?>"><?php echo _t('reading_configuration'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'archiving'); ?>"><?php echo _t('archiving_configuration'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'sharing'); ?>"><?php echo _t('sharing'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'shortcut'); ?>"><?php echo _t('shortcuts'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'queries'); ?>"><?php echo _t('queries'); ?></a></li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('configure', 'users'); ?>"><?php echo _t('users'); ?></a></li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('stats', 'index'); ?>"><?php echo _t('stats'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('index', 'logs'); ?>"><?php echo _t('logs'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about'); ?></a></li>
|
||||||
|
<?php
|
||||||
|
if (Minz_Configuration::canLogIn()) {
|
||||||
|
?><li class="separator"></li><?php
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
?><li class="item"><a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
?><li class="item"><a class="signout" href="#"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } elseif (Minz_Configuration::canLogIn()) {
|
||||||
|
?><div class="item configure"><?php
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
echo _i('login'); ?><a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php
|
||||||
|
break;
|
||||||
|
case 'persona':
|
||||||
|
echo _i('login'); ?><a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
?></div><?php
|
||||||
|
} ?>
|
||||||
|
</div>
|
61
sources/app/layout/layout.phtml
Executable file
61
sources/app/layout/layout.phtml
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="<?php echo $this->conf->language; ?>" xml:lang="<?php echo $this->conf->language; ?>">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="initial-scale=1.0" />
|
||||||
|
<?php echo self::headTitle(); ?>
|
||||||
|
<?php echo self::headStyle(); ?>
|
||||||
|
<?php echo self::headScript(); ?>
|
||||||
|
<script>//<![CDATA[
|
||||||
|
<?php $this->renderHelper('javascript_vars'); ?>
|
||||||
|
//]]></script>
|
||||||
|
<?php
|
||||||
|
if (!empty($this->nextId)) {
|
||||||
|
$params = Minz_Request::params();
|
||||||
|
$params['next'] = $this->nextId;
|
||||||
|
?>
|
||||||
|
<link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display(array('c' => Minz_Request::controllerName(), 'a' => Minz_Request::actionName(), 'params' => $params)); ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
<link rel="shortcut icon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
|
||||||
|
<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" />
|
||||||
|
<?php
|
||||||
|
if (isset($this->url)) {
|
||||||
|
$rss_url = $this->url;
|
||||||
|
$rss_url['params']['output'] = 'rss';
|
||||||
|
?>
|
||||||
|
<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($rss_url); ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('starred', true); ?>">
|
||||||
|
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('non-starred', true); ?>">
|
||||||
|
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('read', true); ?>">
|
||||||
|
<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('unread', true); ?>">
|
||||||
|
<link rel="apple-touch-icon" href="<?php echo Minz_Url::display('/themes/icons/apple-touch-icon.png'); ?>">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
|
||||||
|
<meta name="apple-mobile-web-app-title" content="<?php echo Minz_Configuration::title(); ?>">
|
||||||
|
<meta name="msapplication-TileColor" content="#FFF" />
|
||||||
|
<meta name="robots" content="noindex,nofollow" />
|
||||||
|
</head>
|
||||||
|
<body class="<?php echo Minz_Request::param('output', 'normal'); ?>">
|
||||||
|
<?php $this->partial('header'); ?>
|
||||||
|
|
||||||
|
<div id="global">
|
||||||
|
<?php $this->render(); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$msg = '';
|
||||||
|
$status = 'closed';
|
||||||
|
if (isset($this->notification)) {
|
||||||
|
$msg = $this->notification['content'];
|
||||||
|
$status = $this->notification['type'];
|
||||||
|
|
||||||
|
invalidateHttpCache();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div id="notification" class="notification <?php echo $status; ?>">
|
||||||
|
<span class="msg"><?php echo $msg; ?></span>
|
||||||
|
<a class="close" href=""><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
5
sources/app/layout/nav_entries.phtml
Executable file
5
sources/app/layout/nav_entries.phtml
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
<ul id="nav_entries">
|
||||||
|
<li class="item"><a class="previous_entry" href="#"><?php echo FreshRSS_Themes::icon('prev'); ?></a></li>
|
||||||
|
<li class="item"><a class="up" href="#"><?php echo FreshRSS_Themes::icon('up'); ?></a></li>
|
||||||
|
<li class="item"><a class="next_entry" href="#"><?php echo FreshRSS_Themes::icon('next'); ?></a></li>
|
||||||
|
</ul>
|
273
sources/app/layout/nav_menu.phtml
Executable file
273
sources/app/layout/nav_menu.phtml
Executable file
|
@ -0,0 +1,273 @@
|
||||||
|
<?php
|
||||||
|
$actual_view = Minz_Request::param('output', 'normal');
|
||||||
|
?>
|
||||||
|
<div class="nav_menu">
|
||||||
|
<?php if ($actual_view === 'normal') { ?>
|
||||||
|
<a class="btn toggle_aside" href="#aside_flux"><?php echo FreshRSS_Themes::icon('category'); ?></a>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if ($this->loginOk) { ?>
|
||||||
|
<div class="stick">
|
||||||
|
<?php
|
||||||
|
$url_state = $this->url;
|
||||||
|
|
||||||
|
if ($this->state & FreshRSS_Entry::STATE_READ) {
|
||||||
|
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_READ;
|
||||||
|
$checked = 'true';
|
||||||
|
$class = 'active';
|
||||||
|
} else {
|
||||||
|
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_READ;
|
||||||
|
$checked = 'false';
|
||||||
|
$class = '';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<a id="toggle-read"
|
||||||
|
class="btn <?php echo $class; ?>"
|
||||||
|
aria-checked="<?php echo $checked; ?>"
|
||||||
|
href="<?php echo Minz_Url::display ($url_state); ?>"
|
||||||
|
title="<?php echo Minz_Translate::t ('show_read'); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('read'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($this->state & FreshRSS_Entry::STATE_NOT_READ) {
|
||||||
|
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_READ;
|
||||||
|
$checked = 'true';
|
||||||
|
$class = 'active';
|
||||||
|
} else {
|
||||||
|
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_READ;
|
||||||
|
$checked = 'false';
|
||||||
|
$class = '';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<a id="toggle-unread"
|
||||||
|
class="btn <?php echo $class; ?>"
|
||||||
|
aria-checked="<?php echo $checked; ?>"
|
||||||
|
href="<?php echo Minz_Url::display ($url_state); ?>"
|
||||||
|
title="<?php echo Minz_Translate::t ('show_not_reads'); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('unread'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($this->state & FreshRSS_Entry::STATE_FAVORITE) {
|
||||||
|
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_FAVORITE;
|
||||||
|
$checked = 'true';
|
||||||
|
$class = 'active';
|
||||||
|
} else {
|
||||||
|
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_FAVORITE;
|
||||||
|
$checked = 'false';
|
||||||
|
$class = '';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<a id="toggle-favorite"
|
||||||
|
class="btn <?php echo $class; ?>"
|
||||||
|
aria-checked="<?php echo $checked; ?>"
|
||||||
|
href="<?php echo Minz_Url::display ($url_state); ?>"
|
||||||
|
title="<?php echo Minz_Translate::t ('show_favorite'); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('starred'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($this->state & FreshRSS_Entry::STATE_NOT_FAVORITE) {
|
||||||
|
$url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_FAVORITE;
|
||||||
|
$checked = 'true';
|
||||||
|
$class = 'active';
|
||||||
|
} else {
|
||||||
|
$url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_FAVORITE;
|
||||||
|
$checked = 'false';
|
||||||
|
$class = '';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<a id="toggle-not-favorite"
|
||||||
|
class="btn <?php echo $class; ?>"
|
||||||
|
aria-checked="<?php echo $checked; ?>"
|
||||||
|
href="<?php echo Minz_Url::display ($url_state); ?>"
|
||||||
|
title="<?php echo Minz_Translate::t ('show_not_favorite'); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('non-starred'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="dropdown">
|
||||||
|
<div id="dropdown-query" class="dropdown-target"></div>
|
||||||
|
|
||||||
|
<a class="dropdown-toggle btn" href="#dropdown-query"><?php echo FreshRSS_Themes::icon('down'); ?></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
|
||||||
|
<li class="dropdown-header"><?php echo Minz_Translate::t('queries'); ?> <a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a></li>
|
||||||
|
|
||||||
|
<?php foreach ($this->conf->queries as $query) { ?>
|
||||||
|
<li class="item">
|
||||||
|
<a href="<?php echo $query['url']; ?>"><?php echo $query['name']; ?></a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if (count($this->conf->queries) > 0) { ?>
|
||||||
|
<li class="separator no-mobile"></li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$url_query = $this->url;
|
||||||
|
$url_query['c'] = 'configure';
|
||||||
|
$url_query['a'] = 'addQuery';
|
||||||
|
?>
|
||||||
|
<li class="item no-mobile"><a href="<?php echo Minz_Url::display($url_query); ?>"><?php echo FreshRSS_Themes::icon('bookmark-add'); ?> <?php echo Minz_Translate::t('add_query'); ?></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
$get = false;
|
||||||
|
$string_mark = Minz_Translate::t ('mark_all_read');
|
||||||
|
if ($this->get_f) {
|
||||||
|
$get = 'f_' . $this->get_f;
|
||||||
|
$string_mark = Minz_Translate::t ('mark_feed_read');
|
||||||
|
} elseif ($this->get_c && $this->get_c != 'a') {
|
||||||
|
if ($this->get_c === 's') {
|
||||||
|
$get = 's';
|
||||||
|
} else {
|
||||||
|
$get = 'c_' . $this->get_c;
|
||||||
|
}
|
||||||
|
$string_mark = Minz_Translate::t ('mark_cat_read');
|
||||||
|
}
|
||||||
|
$nextGet = $get;
|
||||||
|
if ($this->conf->onread_jump_next && (strlen ($get) > 2)) {
|
||||||
|
$anotherUnreadId = '';
|
||||||
|
$foundCurrent = false;
|
||||||
|
switch ($get[0]) {
|
||||||
|
case 'c':
|
||||||
|
foreach ($this->cat_aside as $cat) {
|
||||||
|
if ($cat->id () == $this->get_c) {
|
||||||
|
$foundCurrent = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($cat->nbNotRead () <= 0) continue;
|
||||||
|
$anotherUnreadId = $cat->id ();
|
||||||
|
if ($foundCurrent) break;
|
||||||
|
}
|
||||||
|
$nextGet = empty ($anotherUnreadId) ? 'a' : 'c_' . $anotherUnreadId;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
foreach ($this->cat_aside as $cat) {
|
||||||
|
if ($cat->id () == $this->get_c) {
|
||||||
|
foreach ($cat->feeds () as $feed) {
|
||||||
|
if ($feed->id () == $this->get_f) {
|
||||||
|
$foundCurrent = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($feed->nbNotRead () <= 0) continue;
|
||||||
|
$anotherUnreadId = $feed->id ();
|
||||||
|
if ($foundCurrent) break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$nextGet = empty ($anotherUnreadId) ? 'c_' . $this->get_c : 'f_' . $anotherUnreadId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->order === 'ASC') {
|
||||||
|
$idMax = 0;
|
||||||
|
} else {
|
||||||
|
$p = isset($this->entries[0]) ? $this->entries[0] : null;
|
||||||
|
$idMax = $p === null ? '0' : $p->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax));
|
||||||
|
$output = Minz_Request::param('output', '');
|
||||||
|
if (($output != '') && ($this->conf->view_mode !== $output)) {
|
||||||
|
$arUrl['params']['output'] = $output;
|
||||||
|
}
|
||||||
|
$markReadUrl = Minz_Url::display($arUrl);
|
||||||
|
Minz_Session::_param ('markReadUrl', $markReadUrl);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="stick" id="nav_menu_read_all">
|
||||||
|
<a class="read_all btn<?php if ($this->conf->reading_confirm) {echo ' confirm';} ?>" href="<?php echo $markReadUrl; ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a>
|
||||||
|
<div class="dropdown">
|
||||||
|
<div id="dropdown-read" class="dropdown-target"></div>
|
||||||
|
|
||||||
|
<a class="dropdown-toggle btn" href="#dropdown-read"><?php echo FreshRSS_Themes::icon('down'); ?></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
|
||||||
|
<li class="item"><a href="<?php echo $markReadUrl; ?>"><?php echo $string_mark; ?></a></li>
|
||||||
|
<li class="separator"></li>
|
||||||
|
<?php
|
||||||
|
$today = $this->today;
|
||||||
|
$one_week = $today - 604800;
|
||||||
|
?>
|
||||||
|
<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'idMax', $today . '000000'); ?>"><?php echo Minz_Translate::t ('before_one_day'); ?></a></li>
|
||||||
|
<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'idMax', $one_week . '000000'); ?>"><?php echo Minz_Translate::t ('before_one_week'); ?></a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $url_output = $this->url; ?>
|
||||||
|
<div class="stick" id="nav_menu_views">
|
||||||
|
<?php $url_output['params']['output'] = 'normal'; ?>
|
||||||
|
<a class="view_normal btn <?php echo $actual_view == 'normal'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('normal_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon("view-normal"); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php $url_output['params']['output'] = 'global'; ?>
|
||||||
|
<a class="view_global btn <?php echo $actual_view == 'global'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon("view-global"); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php $url_output['params']['output'] = 'reader'; ?>
|
||||||
|
<a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo Minz_Translate::t('reader_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon("view-reader"); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$url_output['params']['output'] = 'rss';
|
||||||
|
$url_output['params']['token'] = $this->conf->token;
|
||||||
|
?>
|
||||||
|
<a class="view_rss btn" target="_blank" title="<?php echo Minz_Translate::t ('rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('rss'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item search">
|
||||||
|
<form action="<?php echo _url ('index', 'index'); ?>" method="get">
|
||||||
|
<?php $search = Minz_Request::param ('search', ''); ?>
|
||||||
|
<input type="search" name="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search_short'); ?>" />
|
||||||
|
|
||||||
|
<?php $get = Minz_Request::param ('get', ''); ?>
|
||||||
|
<?php if($get != '') { ?>
|
||||||
|
<input type="hidden" name="get" value="<?php echo $get; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $order = Minz_Request::param ('order', ''); ?>
|
||||||
|
<?php if($order != '') { ?>
|
||||||
|
<input type="hidden" name="order" value="<?php echo $order; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $state = Minz_Request::param ('state', ''); ?>
|
||||||
|
<?php if($state != '') { ?>
|
||||||
|
<input type="hidden" name="state" value="<?php echo $state; ?>" />
|
||||||
|
<?php } ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($this->order === 'DESC') {
|
||||||
|
$order = 'ASC';
|
||||||
|
$icon = 'up';
|
||||||
|
$title = 'older_first';
|
||||||
|
} else {
|
||||||
|
$order = 'DESC';
|
||||||
|
$icon = 'down';
|
||||||
|
$title = 'newer_first';
|
||||||
|
}
|
||||||
|
$url_order = $this->url;
|
||||||
|
$url_order['params']['order'] = $order;
|
||||||
|
?>
|
||||||
|
<a class="btn" href="<?php echo Minz_Url::display ($url_order); ?>" title="<?php echo Minz_Translate::t ($title); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon($icon); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<?php if ($this->loginOk || Minz_Configuration::allowAnonymousRefresh()) { ?>
|
||||||
|
<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
79
sources/app/views/configure/archiving.phtml
Executable file
79
sources/app/views/configure/archiving.phtml
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php $this->partial('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('configure', 'archiving'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t('archiving_configuration'); ?></legend>
|
||||||
|
<p><?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('archiving_configuration_help'); ?></p>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="old_entries"><?php echo Minz_Translate::t('delete_articles_every'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo $this->conf->old_entries; ?>" /> <?php echo Minz_Translate::t('month'); ?>
|
||||||
|
<a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo Minz_Translate::t('purge_now'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="keep_history_default"><?php echo Minz_Translate::t('keep_history'), ' ', Minz_Translate::t('by_feed'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select class="number" name="keep_history_default" id="keep_history_default" required="required"><?php
|
||||||
|
foreach (array('' => '', 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
|
||||||
|
echo '<option value="' . $v . ($this->conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
|
||||||
|
}
|
||||||
|
?></select> (<?php echo Minz_Translate::t('by_default'); ?>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="ttl_default"><?php echo Minz_Translate::t('ttl'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select class="number" name="ttl_default" id="ttl_default" required="required"><?php
|
||||||
|
$found = false;
|
||||||
|
foreach (array(1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
|
||||||
|
3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
|
||||||
|
36000 => '10h', 43200 => '12h', 64800 => '18h',
|
||||||
|
86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
|
||||||
|
604800 => '1wk', -1 => '∞') as $v => $t) {
|
||||||
|
echo '<option value="' . $v . ($this->conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>';
|
||||||
|
if ($this->conf->ttl_default == $v) {
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
echo '<option value="' . intval($this->conf->ttl_default) . '" selected="selected">' . intval($this->conf->ttl_default) . 's</option>';
|
||||||
|
}
|
||||||
|
?></select> (<?php echo Minz_Translate::t('by_default'); ?>)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('entry', 'optimize'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<p class="group-name"><?php echo Minz_Translate::t('current_user'); ?></p>
|
||||||
|
<div class="group-controls">
|
||||||
|
<p><?php echo formatNumber($this->nb_total), ' ', Minz_Translate::t('articles'), ', ', formatBytes($this->size_user); ?></p>
|
||||||
|
<input type="hidden" name="optimiseDatabase" value="1" />
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('optimize_bdd'); ?></button>
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('optimize_todo_sometimes'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<p class="group-name"><?php echo Minz_Translate::t('users'); ?></p>
|
||||||
|
<div class="group-controls">
|
||||||
|
<p><?php echo formatBytes($this->size_total); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
49
sources/app/views/configure/categorize.phtml
Executable file
49
sources/app/views/configure/categorize.phtml
Executable file
|
@ -0,0 +1,49 @@
|
||||||
|
<?php $this->partial ('aside_feed'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'categorize'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('categories_management'); ?></legend>
|
||||||
|
|
||||||
|
<p class="alert alert-warn"><?php echo Minz_Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p>
|
||||||
|
|
||||||
|
<?php $i = 0; foreach ($this->categories as $cat) { $i++; ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="cat_<?php echo $cat->id (); ?>">
|
||||||
|
<?php echo Minz_Translate::t ('category_number', $i); ?>
|
||||||
|
</label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
|
||||||
|
|
||||||
|
<?php if ($cat->nbFeed () > 0) { ?>
|
||||||
|
<button type="submit" class="btn btn-attention confirm" formaction="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Minz_Translate::t ('ask_empty'); ?></button>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
(<?php echo Minz_Translate::t ('number_feeds', $cat->nbFeed ()); ?>)
|
||||||
|
|
||||||
|
<?php if ($cat->id () === $this->defaultCategory->id ()) { ?>
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('can_not_be_deleted'); ?>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<input type="hidden" name="ids[]" value="<?php echo $cat->id (); ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="new_category"><?php echo Minz_Translate::t ('add_category'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="new_category" name="new_category" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
102
sources/app/views/configure/display.phtml
Executable file
102
sources/app/views/configure/display.phtml
Executable file
|
@ -0,0 +1,102 @@
|
||||||
|
<?php $this->partial ('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'display'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('display_configuration'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="language"><?php echo Minz_Translate::t ('language'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="language" id="language">
|
||||||
|
<?php $languages = $this->conf->availableLanguages (); ?>
|
||||||
|
<?php foreach ($languages as $short => $lib) { ?>
|
||||||
|
<option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="theme" id="theme" required=""><?php
|
||||||
|
$found = false;
|
||||||
|
foreach ($this->themes as $theme) {
|
||||||
|
?><option value="<?php echo $theme['id']; ?>"<?php if ($this->conf->theme === $theme['id']) { echo ' selected="selected"'; $found = true; } ?>><?php
|
||||||
|
echo $theme['name'] . ' — ' . Minz_Translate::t ('by') . ' ' . $theme['author'];
|
||||||
|
?></option><?php
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
?><option selected="selected"></option><?php
|
||||||
|
}
|
||||||
|
?></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $width = $this->conf->content_width; ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="content_width"><?php echo Minz_Translate::t('content_width'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="content_width" id="content_width" required="">
|
||||||
|
<option value="thin" <?php echo $width === 'thin'? 'selected="selected"' : ''; ?>>
|
||||||
|
<?php echo Minz_Translate::t('width_thin'); ?>
|
||||||
|
</option>
|
||||||
|
<option value="medium" <?php echo $width === 'medium'? 'selected="selected"' : ''; ?>>
|
||||||
|
<?php echo Minz_Translate::t('width_medium'); ?>
|
||||||
|
</option>
|
||||||
|
<option value="large" <?php echo $width === 'large'? 'selected="selected"' : ''; ?>>
|
||||||
|
<?php echo Minz_Translate::t('width_large'); ?>
|
||||||
|
</option>
|
||||||
|
<option value="no_limit" <?php echo $width === 'no_limit'? 'selected="selected"' : ''; ?>>
|
||||||
|
<?php echo Minz_Translate::t('width_no_limit'); ?>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="theme"><?php echo Minz_Translate::t ('article_icons'); ?></label>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th title="<?php echo Minz_Translate::t ('mark_read'); ?>"><?php echo FreshRSS_Themes::icon('read'); ?></th>
|
||||||
|
<th title="<?php echo Minz_Translate::t ('mark_favorite'); ?>"><?php echo FreshRSS_Themes::icon('bookmark'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('sharing'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('related_tags'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('publication_date'); ?></th>
|
||||||
|
<th><?php echo FreshRSS_Themes::icon('link'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('top_line'); ?></th>
|
||||||
|
<td><input type="checkbox" name="topline_read" value="1"<?php echo $this->conf->topline_read ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="topline_favorite" value="1"<?php echo $this->conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" disabled="disabled" /></td>
|
||||||
|
<td><input type="checkbox" disabled="disabled" /></td>
|
||||||
|
<td><input type="checkbox" name="topline_date" value="1"<?php echo $this->conf->topline_date ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="topline_link" value="1"<?php echo $this->conf->topline_link ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
</tr><tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('bottom_line'); ?></th>
|
||||||
|
<td><input type="checkbox" name="bottomline_read" value="1"<?php echo $this->conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo $this->conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo $this->conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="bottomline_tags" value="1"<?php echo $this->conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="bottomline_date" value="1"<?php echo $this->conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
<td><input type="checkbox" name="bottomline_link" value="1"<?php echo $this->conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table><br />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
176
sources/app/views/configure/feed.phtml
Executable file
176
sources/app/views/configure/feed.phtml
Executable file
|
@ -0,0 +1,176 @@
|
||||||
|
<?php $this->partial ('aside_feed'); ?>
|
||||||
|
|
||||||
|
<?php if ($this->flux) { ?>
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Minz_Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Minz_Translate::t ('filter'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo $this->flux->name (); ?></h1>
|
||||||
|
<?php echo $this->flux->description (); ?>
|
||||||
|
|
||||||
|
<?php $nbEntries = $this->flux->nbEntries (); ?>
|
||||||
|
|
||||||
|
<?php if ($this->flux->inError ()) { ?>
|
||||||
|
<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p>
|
||||||
|
<?php } elseif ($nbEntries === 0) { ?>
|
||||||
|
<p class="alert alert-warn"><?php echo Minz_Translate::t ('feed_empty'); ?></p>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off">
|
||||||
|
<legend><?php echo Minz_Translate::t ('informations'); ?></legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" name="name" id="name" class="extend" value="<?php echo $this->flux->name () ; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="description"><?php echo Minz_Translate::t ('feed_description'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<textarea name="description" id="description"><?php echo htmlspecialchars($this->flux->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" />
|
||||||
|
<a class="btn" target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" />
|
||||||
|
<a class="btn" target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="category"><?php echo Minz_Translate::t ('category'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="category" id="category">
|
||||||
|
<?php foreach ($this->categories as $cat) { ?>
|
||||||
|
<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->flux->category () ? ' selected="selected"' : ''; ?>>
|
||||||
|
<?php echo $cat->name (); ?>
|
||||||
|
</option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="priority"><?php echo Minz_Translate::t ('show_in_all_flux'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="priority">
|
||||||
|
<input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->flux->priority () > 0 ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('yes'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'delete', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('delete'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('archiving_configuration'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<a class="btn" href="<?php echo _url ('feed', 'actualize', 'id', $this->flux->id ()); ?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('refresh'); ?> <?php echo Minz_Translate::t('actualize'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t ('number_articles'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<span class="control"><?php echo $nbEntries; ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="keep_history"><?php echo Minz_Translate::t ('keep_history'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select class="number" name="keep_history" id="keep_history" required="required"><?php
|
||||||
|
foreach (array('' => '', -2 => Minz_Translate::t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
|
||||||
|
echo '<option value="' . $v . ($this->flux->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
|
||||||
|
}
|
||||||
|
?></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="ttl"><?php echo Minz_Translate::t('ttl'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select class="number" name="ttl" id="ttl" required="required"><?php
|
||||||
|
$found = false;
|
||||||
|
foreach (array(-2 => Minz_Translate::t('by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
|
||||||
|
3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
|
||||||
|
36000 => '10h', 43200 => '12h', 64800 => '18h',
|
||||||
|
86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
|
||||||
|
604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo', -1 => '∞') as $v => $t) {
|
||||||
|
echo '<option value="' . $v . ($this->flux->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
|
||||||
|
if ($this->flux->ttl() == $v) {
|
||||||
|
$found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$found) {
|
||||||
|
echo '<option value="' . intval($this->flux->ttl()) . '" selected="selected">' . intval($this->flux->ttl()) . 's</option>';
|
||||||
|
}
|
||||||
|
?></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'truncate', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('truncate'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('login_configuration'); ?></legend>
|
||||||
|
<?php $auth = $this->flux->httpAuth (false); ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="http_user"><?php echo Minz_Translate::t ('http_username'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('access_protected_feeds'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="group-name" for="http_pass"><?php echo Minz_Translate::t ('http_password'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="path_entries"><?php echo Minz_Translate::t ('css_path_on_website'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('retrieve_truncated_feeds'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php } else { ?>
|
||||||
|
<div class="alert alert-warn"><span class="alert-head"><?php echo Minz_Translate::t ('no_selected_feed'); ?></span> <?php echo Minz_Translate::t ('think_to_add'); ?></div>
|
||||||
|
<?php } ?>
|
90
sources/app/views/configure/queries.phtml
Executable file
90
sources/app/views/configure/queries.phtml
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php $this->partial('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('configure', 'queries'); ?>">
|
||||||
|
<legend><?php echo _t('queries'); ?></legend>
|
||||||
|
|
||||||
|
<?php foreach ($this->conf->queries as $key => $query) { ?>
|
||||||
|
<div class="form-group" id="query-group-<?php echo $key; ?>">
|
||||||
|
<label class="group-name" for="queries_<?php echo $key; ?>_name">
|
||||||
|
<?php echo _t('query_number', $key + 1); ?>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="hidden" id="queries_<?php echo $key; ?>_search" name="queries[<?php echo $key; ?>][search]" value="<?php echo isset($query['search']) ? $query['search'] : ""; ?>"/>
|
||||||
|
<input type="hidden" id="queries_<?php echo $key; ?>_state" name="queries[<?php echo $key; ?>][state]" value="<?php echo isset($query['state']) ? $query['state'] : ""; ?>"/>
|
||||||
|
<input type="hidden" id="queries_<?php echo $key; ?>_order" name="queries[<?php echo $key; ?>][order]" value="<?php echo isset($query['order']) ? $query['order'] : ""; ?>"/>
|
||||||
|
<input type="hidden" id="queries_<?php echo $key; ?>_get" name="queries[<?php echo $key; ?>][get]" value="<?php echo isset($query['get']) ? $query['get'] : ""; ?>"/>
|
||||||
|
|
||||||
|
<div class="stick">
|
||||||
|
<input class="extend"
|
||||||
|
type="text"
|
||||||
|
id="queries_<?php echo $key; ?>_name"
|
||||||
|
name="queries[<?php echo $key; ?>][name]"
|
||||||
|
value="<?php echo $query['name']; ?>"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a class="btn" href="<?php echo $query['url']; ?>">
|
||||||
|
<?php echo _i('link'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="btn btn-attention remove" href="#" data-remove="query-group-<?php echo $key; ?>">
|
||||||
|
<?php echo _i('close'); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$exist = (isset($query['search']) ? 1 : 0)
|
||||||
|
+ (isset($query['state']) ? 1 : 0)
|
||||||
|
+ (isset($query['order']) ? 1 : 0)
|
||||||
|
+ (isset($query['get']) ? 1 : 0);
|
||||||
|
// If the only filter is "all" articles, we consider there is no filter
|
||||||
|
$exist = ($exist === 1 && isset($query['get']) && $query['get'] === 'a') ? 0 : $exist;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($exist === 0) { ?>
|
||||||
|
<div class="alert alert-warn">
|
||||||
|
<div class="alert-head"><?php echo _t('no_query_filter'); ?></div>
|
||||||
|
</div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<div class="alert-head"><?php echo _t('query_filter'); ?></div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php if (isset($query['search'])) { $exist = true; ?>
|
||||||
|
<li class="item"><?php echo _t('query_search', $query['search']); ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if (isset($query['state'])) { $exist = true; ?>
|
||||||
|
<li class="item"><?php echo _t('query_state_' . $query['state']); ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if (isset($query['order'])) { $exist = true; ?>
|
||||||
|
<li class="item"><?php echo _t('query_order_' . strtolower($query['order'])); ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if (isset($query['get'])) { $exist = true; ?>
|
||||||
|
<li class="item"><?php echo _t('query_get_' . $this->query_get[$key]['type'], $this->query_get[$key]['name']); ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if (count($this->conf->queries) > 0) { ?>
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo _t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<p class="alert alert-warn"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('no_query'); ?></p>
|
||||||
|
<?php } ?>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
135
sources/app/views/configure/reading.phtml
Executable file
135
sources/app/views/configure/reading.phtml
Executable file
|
@ -0,0 +1,135 @@
|
||||||
|
<?php $this->partial ('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'reading'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('articles_per_page'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="sort_order"><?php echo Minz_Translate::t ('sort_order'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="sort_order" id="sort_order">
|
||||||
|
<option value="DESC"<?php echo $this->conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option>
|
||||||
|
<option value="ASC"<?php echo $this->conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="view_mode"><?php echo Minz_Translate::t ('default_view'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="view_mode" id="view_mode">
|
||||||
|
<option value="normal"<?php echo $this->conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option>
|
||||||
|
<option value="reader"<?php echo $this->conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option>
|
||||||
|
<option value="global"<?php echo $this->conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option>
|
||||||
|
</select>
|
||||||
|
<label class="radio" for="radio_all">
|
||||||
|
<input type="radio" name="default_view" id="radio_all" value="<?php echo FreshRSS_Entry::STATE_ALL; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_ALL ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('show_all_articles'); ?>
|
||||||
|
</label>
|
||||||
|
<label class="radio" for="radio_not_read">
|
||||||
|
<input type="radio" name="default_view" id="radio_not_read" value="<?php echo FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_NOT_READ ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('show_not_reads'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="auto_load_more">
|
||||||
|
<input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('auto_load_more'); ?>
|
||||||
|
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="display_posts">
|
||||||
|
<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('display_articles_unfolded'); ?>
|
||||||
|
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="lazyload">
|
||||||
|
<input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('img_with_lazyload'); ?>
|
||||||
|
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="sticky_post">
|
||||||
|
<input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo $this->conf->sticky_post ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('sticky_post'); ?>
|
||||||
|
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="reading_confirm">
|
||||||
|
<input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo $this->conf->reading_confirm ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('reading_confirm'); ?>
|
||||||
|
<noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t ('auto_read_when'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="check_open_article">
|
||||||
|
<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t('article_viewed'); ?>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox" for="check_open_site">
|
||||||
|
<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('article_open_on_website'); ?>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox" for="check_scroll">
|
||||||
|
<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo $this->conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('scroll'); ?>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox" for="check_reception">
|
||||||
|
<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo $this->conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('upon_reception'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t ('after_onread'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="onread_jump_next">
|
||||||
|
<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo $this->conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
|
||||||
|
<?php echo Minz_Translate::t ('jump_next'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
59
sources/app/views/configure/sharing.phtml
Executable file
59
sources/app/views/configure/sharing.phtml
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
<?php $this->partial ('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'sharing'); ?>"
|
||||||
|
data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>'
|
||||||
|
data-advanced='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls">
|
||||||
|
<input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" />
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
|
||||||
|
<input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
|
||||||
|
<a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a></div>
|
||||||
|
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="##help##"><?php echo FreshRSS_Themes::icon('help'); ?></a>
|
||||||
|
</div></div>'>
|
||||||
|
<legend><?php echo Minz_Translate::t ('sharing'); ?></legend>
|
||||||
|
<?php foreach ($this->conf->sharing as $key => $sharing): ?>
|
||||||
|
<?php $share = $this->conf->shares[$sharing['type']]; ?>
|
||||||
|
<div class="form-group" id="group-share-<?php echo $key; ?>">
|
||||||
|
<label class="group-name">
|
||||||
|
<?php echo Minz_Translate::t ($sharing['type']); ?>
|
||||||
|
</label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type='hidden' id='share_<?php echo $key;?>_type' name="share[<?php echo $key;?>][type]" value='<?php echo $sharing['type']?>' />
|
||||||
|
<?php if ($share['form'] === 'advanced') { ?>
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" />
|
||||||
|
<input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" />
|
||||||
|
<a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="<?php echo $share['help']?>"><?php echo FreshRSS_Themes::icon('help'); ?></a>
|
||||||
|
<?php } else { ?>
|
||||||
|
<a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach;?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<select>
|
||||||
|
<?php foreach($this->conf->shares as $key => $params):?>
|
||||||
|
<option value='<?php echo $key?>' data-form='<?php echo $params['form']?>' data-help='<?php if (!empty($params['help'])) {echo $params['help'];}?>'><?php echo Minz_Translate::t($key) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
<a href='#' class='share add btn'><?php echo FreshRSS_Themes::icon('add'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
113
sources/app/views/configure/shortcut.phtml
Executable file
113
sources/app/views/configure/shortcut.phtml
Executable file
|
@ -0,0 +1,113 @@
|
||||||
|
<?php $this->partial ('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<datalist id="keys">
|
||||||
|
<?php foreach ($this->list_keys as $key) { ?>
|
||||||
|
<option value="<?php echo $key; ?>">
|
||||||
|
<?php } ?>
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
<?php $s = $this->conf->shortcuts; ?>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('shortcuts'); ?></legend>
|
||||||
|
|
||||||
|
<noscript><p class="alert alert-error"><?php echo Minz_Translate::t ('javascript_for_shortcuts'); ?></p></noscript>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('shortcuts_navigation'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="first_entry"><?php echo Minz_Translate::t ('first_article'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="last_entry"><?php echo Minz_Translate::t ('last_article'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><?php echo Minz_Translate::t ('shortcuts_navigation_help');?></div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('shortcuts_article_action');?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" />
|
||||||
|
<?php echo Minz_Translate::t ('shift_for_all_read'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="mark_favorite"><?php echo Minz_Translate::t ('mark_favorite'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="go_website"><?php echo Minz_Translate::t ('see_on_website'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
|
||||||
|
<?php echo Minz_Translate::t ('auto_share_help'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="collapse_entry"><?php echo Minz_Translate::t ('collapse_article'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t ('shortcuts_other_action');?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="load_more_shortcut"><?php echo Minz_Translate::t ('load_more'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="focus_search_shortcut"><?php echo Minz_Translate::t ('focus_search'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="focus_search_shortcut" name="shortcuts[focus_search]" list="keys" value="<?php echo $s['focus_search']; ?>" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
211
sources/app/views/configure/users.phtml
Executable file
211
sources/app/views/configure/users.phtml
Executable file
|
@ -0,0 +1,211 @@
|
||||||
|
<?php $this->partial('aside_configure'); ?>
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('users', 'auth'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t('login_configuration'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="current_user"><?php echo Minz_Translate::t('current_user'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input id="current_user" type="text" disabled="disabled" value="<?php echo Minz_Session::param('currentUser', '_'); ?>" />
|
||||||
|
<label class="checkbox" for="is_admin">
|
||||||
|
<input type="checkbox" id="is_admin" disabled="disabled" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? 'checked="checked" ' : ''; ?>/>
|
||||||
|
<?php echo Minz_Translate::t('is_admin'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
|
||||||
|
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
|
||||||
|
</div>
|
||||||
|
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (Minz_Configuration::apiEnabled()) { ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="apiPasswordPlain"><?php echo Minz_Translate::t('password_api'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
|
||||||
|
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
|
||||||
|
<?php $mail = $this->conf->mail_login; ?>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
|
||||||
|
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t('auth_type'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="auth_type"><?php echo Minz_Translate::t('auth_type'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select id="auth_type" name="auth_type" required="required">
|
||||||
|
<?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?>
|
||||||
|
<option selected="selected"></option>
|
||||||
|
<?php } ?>
|
||||||
|
<option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo Minz_Translate::t('auth_form'); ?></option>
|
||||||
|
<option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_persona'); ?></option>
|
||||||
|
<option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
|
||||||
|
<option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t('auth_none'); ?></option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="anon_access">
|
||||||
|
<input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : '',
|
||||||
|
Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
|
||||||
|
<?php echo Minz_Translate::t('allow_anonymous', Minz_Configuration::defaultUser()); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="anon_refresh">
|
||||||
|
<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '',
|
||||||
|
Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
|
||||||
|
<?php echo Minz_Translate::t('allow_anonymous_refresh'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="unsafe_autologin">
|
||||||
|
<input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo Minz_Configuration::unsafeAutologinEnabled() ? ' checked="checked"' : '',
|
||||||
|
Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
|
||||||
|
<?php echo Minz_Translate::t('unsafe_autologin'); ?>
|
||||||
|
<kbd>p/i/?a=formLogin&u=Alice&p=1234</kbd>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (Minz_Configuration::canLogIn()) { ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label>
|
||||||
|
<?php $token = $this->conf->token; ?>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"<?php
|
||||||
|
echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="api_enabled">
|
||||||
|
<input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo Minz_Configuration::apiEnabled() ? ' checked="checked"' : '',
|
||||||
|
Minz_Configuration::needsLogin() ? '' : ' disabled="disabled"'; ?> />
|
||||||
|
<?php echo Minz_Translate::t('api_enabled'); ?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('users', 'delete'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t('users'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="users_list"><?php echo Minz_Translate::t('users_list'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select id="users_list" name="username"><?php
|
||||||
|
foreach (listUsers() as $user) {
|
||||||
|
echo '<option>', $user, '</option>';
|
||||||
|
}
|
||||||
|
?></select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-attention confirm"><?php echo Minz_Translate::t('delete'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('users', 'create'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t('create_user'); ?></legend>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="new_user_language"><?php echo Minz_Translate::t ('language'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="new_user_language" id="new_user_language">
|
||||||
|
<?php $languages = $this->conf->availableLanguages (); ?>
|
||||||
|
<?php foreach ($languages as $short => $lib) { ?>
|
||||||
|
<option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="new_user_name"><?php echo Minz_Translate::t('username'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="new_user_passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" />
|
||||||
|
<a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a>
|
||||||
|
</div>
|
||||||
|
<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
|
||||||
|
<?php $mail = $this->conf->mail_login; ?>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('create'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
16
sources/app/views/entry/bookmark.phtml
Executable file
16
sources/app/views/entry/bookmark.phtml
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
if (Minz_Request::param ('is_favorite', true)) {
|
||||||
|
Minz_Request::_param ('is_favorite', 0);
|
||||||
|
} else {
|
||||||
|
Minz_Request::_param ('is_favorite', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = Minz_Url::display (array (
|
||||||
|
'c' => Minz_Request::controllerName (),
|
||||||
|
'a' => Minz_Request::actionName (),
|
||||||
|
'params' => Minz_Request::params (),
|
||||||
|
));
|
||||||
|
|
||||||
|
echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_favorite') ? 'non-starred' : 'starred')));
|
16
sources/app/views/entry/read.phtml
Executable file
16
sources/app/views/entry/read.phtml
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
|
||||||
|
if (Minz_Request::param ('is_read', true)) {
|
||||||
|
Minz_Request::_param ('is_read', 0);
|
||||||
|
} else {
|
||||||
|
Minz_Request::_param ('is_read', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = Minz_Url::display (array (
|
||||||
|
'c' => Minz_Request::controllerName (),
|
||||||
|
'a' => Minz_Request::actionName (),
|
||||||
|
'params' => Minz_Request::params (),
|
||||||
|
));
|
||||||
|
|
||||||
|
echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_read') ? 'unread' : 'read')));
|
18
sources/app/views/error/index.phtml
Executable file
18
sources/app/views/error/index.phtml
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
<div class="post">
|
||||||
|
<div class="alert alert-error">
|
||||||
|
<h1 class="alert-head"><?php echo $this->code; ?></h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<?php
|
||||||
|
switch(Minz_Request::param ('code')) {
|
||||||
|
case 403:
|
||||||
|
echo Minz_Translate::t ('forbidden_access');
|
||||||
|
break;
|
||||||
|
case 404:
|
||||||
|
default:
|
||||||
|
echo Minz_Translate::t ('page_not_found');
|
||||||
|
} ?><br />
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
1
sources/app/views/feed/actualize.phtml
Executable file
1
sources/app/views/feed/actualize.phtml
Executable file
|
@ -0,0 +1 @@
|
||||||
|
OK
|
91
sources/app/views/feed/add.phtml
Executable file
91
sources/app/views/feed/add.phtml
Executable file
|
@ -0,0 +1,91 @@
|
||||||
|
<?php if ($this->feed) { ?>
|
||||||
|
<div class="post">
|
||||||
|
<h1><?php echo Minz_Translate::t ('add_rss_feed'); ?></h1>
|
||||||
|
|
||||||
|
<?php if (!$this->load_ok) { ?>
|
||||||
|
<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t('damn'); ?></span> <?php echo Minz_Translate::t('internal_problem_feed', _url('index', 'logs')); ?></p>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off">
|
||||||
|
<legend><?php echo Minz_Translate::t('informations'); ?></legend>
|
||||||
|
<?php if ($this->load_ok) { ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t('title'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<label><?php echo $this->feed->name() ; ?></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $desc = $this->feed->description(); if ($desc != '') { ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t('feed_description'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<label><?php echo htmlspecialchars($desc, ENT_NOQUOTES, 'UTF-8'); ?></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name"><?php echo Minz_Translate::t('website_url'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<?php echo $this->feed->website(); ?>
|
||||||
|
<a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="url"><?php echo Minz_Translate::t('feed_url'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<div class="stick">
|
||||||
|
<input type="text" name="url_rss" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" />
|
||||||
|
<a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
|
||||||
|
</div>
|
||||||
|
<a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo Minz_Translate::t('feed_validator'); ?></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="category"><?php echo Minz_Translate::t('category'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<select name="category" id="category">
|
||||||
|
<?php foreach ($this->categories as $cat) { ?>
|
||||||
|
<option value="<?php echo $cat->id(); ?>"<?php echo $cat->id() == 1 ? ' selected="selected"' : ''; ?>>
|
||||||
|
<?php echo $cat->name(); ?>
|
||||||
|
</option>
|
||||||
|
<?php } ?>
|
||||||
|
<option value="nc"><?php echo Minz_Translate::t('new_category'); ?></option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<span style="display: none;">
|
||||||
|
<input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t('new_category'); ?>" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<legend><?php echo Minz_Translate::t('http_authentication'); ?></legend>
|
||||||
|
<?php $auth = $this->feed->httpAuth(false); ?>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="http_user"><?php echo Minz_Translate::t('http_username'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label class="group-name" for="http_pass"><?php echo Minz_Translate::t('http_password'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group-controls">
|
||||||
|
<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('access_protected_feeds'); ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
|
||||||
|
<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
47
sources/app/views/helpers/export/articles.phtml
Executable file
47
sources/app/views/helpers/export/articles.phtml
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
$username = Minz_Session::param('currentUser', '_');
|
||||||
|
|
||||||
|
$articles = array(
|
||||||
|
'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type,
|
||||||
|
'title' => $this->list_title,
|
||||||
|
'author' => $username,
|
||||||
|
'items' => array()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($this->entries as $entry) {
|
||||||
|
if (!isset($this->feed)) {
|
||||||
|
$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feed ());
|
||||||
|
} else {
|
||||||
|
$feed = $this->feed;
|
||||||
|
}
|
||||||
|
|
||||||
|
$articles['items'][] = array(
|
||||||
|
'id' => $entry->guid(),
|
||||||
|
'categories' => array_values($entry->tags()),
|
||||||
|
'title' => $entry->title(),
|
||||||
|
'author' => $entry->author(),
|
||||||
|
'published' => $entry->date(true),
|
||||||
|
'updated' => $entry->date(true),
|
||||||
|
'alternate' => array(array(
|
||||||
|
'href' => $entry->link(),
|
||||||
|
'type' => 'text/html'
|
||||||
|
)),
|
||||||
|
'content' => array(
|
||||||
|
'content' => $entry->content()
|
||||||
|
),
|
||||||
|
'origin' => array(
|
||||||
|
'streamId' => $feed->id(),
|
||||||
|
'title' => $feed->name(),
|
||||||
|
'htmlUrl' => $feed->website(),
|
||||||
|
'feedUrl' => $feed->url()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$options = 0;
|
||||||
|
if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
|
||||||
|
$options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($articles, $options);
|
||||||
|
?>
|
28
sources/app/views/helpers/export/opml.phtml
Executable file
28
sources/app/views/helpers/export/opml.phtml
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$opml_array = array(
|
||||||
|
'head' => array(
|
||||||
|
'title' => Minz_Configuration::title(),
|
||||||
|
'dateCreated' => date('D, d M Y H:i:s')
|
||||||
|
),
|
||||||
|
'body' => array()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($this->categories as $key => $cat) {
|
||||||
|
$opml_array['body'][$key] = array(
|
||||||
|
'text' => $cat['name'],
|
||||||
|
'@outlines' => array()
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($cat['feeds'] as $feed) {
|
||||||
|
$opml_array['body'][$key]['@outlines'][] = array(
|
||||||
|
'text' => htmlspecialchars_decode($feed->name()),
|
||||||
|
'type' => 'rss',
|
||||||
|
'xmlUrl' => $feed->url(),
|
||||||
|
'htmlUrl' => $feed->website(),
|
||||||
|
'description' => $feed->description()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo libopml_render($opml_array);
|
55
sources/app/views/helpers/javascript_vars.phtml
Executable file
55
sources/app/views/helpers/javascript_vars.phtml
Executable file
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
echo '"use strict";', "\n";
|
||||||
|
|
||||||
|
$mark = $this->conf->mark_when;
|
||||||
|
echo 'var ',
|
||||||
|
'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true',
|
||||||
|
',display_order="', Minz_Request::param('order', $this->conf->sort_order), '"',
|
||||||
|
',auto_mark_article=', $mark['article'] ? 'true' : 'false',
|
||||||
|
',auto_mark_site=', $mark['site'] ? 'true' : 'false',
|
||||||
|
',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false',
|
||||||
|
',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false',
|
||||||
|
',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false',
|
||||||
|
',does_lazyload=', $this->conf->lazyload ? 'true' : 'false',
|
||||||
|
',sticky_post=', $this->conf->sticky_post ? 'true' : 'false';
|
||||||
|
|
||||||
|
$s = $this->conf->shortcuts;
|
||||||
|
echo ',shortcuts={',
|
||||||
|
'mark_read:"', $s['mark_read'], '",',
|
||||||
|
'mark_favorite:"', $s['mark_favorite'], '",',
|
||||||
|
'go_website:"', $s['go_website'], '",',
|
||||||
|
'prev_entry:"', $s['prev_entry'], '",',
|
||||||
|
'next_entry:"', $s['next_entry'], '",',
|
||||||
|
'first_entry:"', $s['first_entry'], '",',
|
||||||
|
'last_entry:"', $s['last_entry'], '",',
|
||||||
|
'collapse_entry:"', $s['collapse_entry'], '",',
|
||||||
|
'load_more:"', $s['load_more'], '",',
|
||||||
|
'auto_share:"', $s['auto_share'], '",',
|
||||||
|
'focus_search:"', $s['focus_search'], '"',
|
||||||
|
"},\n";
|
||||||
|
|
||||||
|
if (Minz_Request::param ('output') === 'global') {
|
||||||
|
echo "iconClose='", FreshRSS_Themes::icon('close'), "',\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$authType = Minz_Configuration::authType();
|
||||||
|
if ($authType === 'persona') {
|
||||||
|
// If user is disconnected, current_user_mail MUST be null
|
||||||
|
$mail = Minz_Session::param ('mail', false);
|
||||||
|
if ($mail) {
|
||||||
|
echo 'current_user_mail="' . $mail . '",';
|
||||||
|
} else {
|
||||||
|
echo 'current_user_mail=null,';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo 'authType="', $authType, '",',
|
||||||
|
'url_freshrss="', _url ('index', 'index'), '",',
|
||||||
|
'url_login="', _url ('index', 'login'), '",',
|
||||||
|
'url_logout="', _url ('index', 'logout'), '",';
|
||||||
|
|
||||||
|
echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n";
|
||||||
|
|
||||||
|
$autoActualise = Minz_Session::param('actualize_feeds', false);
|
||||||
|
echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n";
|
47
sources/app/views/helpers/logs_pagination.phtml
Executable file
47
sources/app/views/helpers/logs_pagination.phtml
Executable file
|
@ -0,0 +1,47 @@
|
||||||
|
<?php
|
||||||
|
$c = Minz_Request::controllerName ();
|
||||||
|
$a = Minz_Request::actionName ();
|
||||||
|
$params = Minz_Request::params ();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($this->nbPage > 1) { ?>
|
||||||
|
<ul class="pagination">
|
||||||
|
<?php $params[$getteur] = 1; ?>
|
||||||
|
<li class="item pager-first">
|
||||||
|
<?php if ($this->currentPage > 1) { ?>
|
||||||
|
<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo Minz_Translate::t('first'); ?></a>
|
||||||
|
<?php } ?>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php $params[$getteur] = $this->currentPage - 1; ?>
|
||||||
|
<li class="item pager-previous">
|
||||||
|
<?php if ($this->currentPage > 1) { ?>
|
||||||
|
<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Minz_Translate::t('previous'); ?></a>
|
||||||
|
<?php } ?>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<?php for ($i = $this->currentPage - 2; $i <= $this->currentPage + 2; $i++) { ?>
|
||||||
|
<?php if($i > 0 && $i <= $this->nbPage) { ?>
|
||||||
|
<?php if ($i != $this->currentPage) { ?>
|
||||||
|
<?php $params[$getteur] = $i; ?>
|
||||||
|
<li class="item pager-item"><a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li>
|
||||||
|
<?php } else { ?>
|
||||||
|
<li class="item pager-current"><?php echo $i; ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $params[$getteur] = $this->currentPage + 1; ?>
|
||||||
|
<li class="item pager-next">
|
||||||
|
<?php if ($this->currentPage < $this->nbPage) { ?>
|
||||||
|
<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('next'); ?> ›</a>
|
||||||
|
<?php } ?>
|
||||||
|
</li>
|
||||||
|
<?php $params[$getteur] = $this->nbPage; ?>
|
||||||
|
<li class="item pager-last">
|
||||||
|
<?php if ($this->currentPage < $this->nbPage) { ?>
|
||||||
|
<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('last'); ?> »</a>
|
||||||
|
<?php } ?>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<?php } ?>
|
26
sources/app/views/helpers/pagination.phtml
Executable file
26
sources/app/views/helpers/pagination.phtml
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
$c = Minz_Request::controllerName ();
|
||||||
|
$a = Minz_Request::actionName ();
|
||||||
|
$params = Minz_Request::params ();
|
||||||
|
$markReadUrl = Minz_Session::param ('markReadUrl');
|
||||||
|
Minz_Session::_param ('markReadUrl', false);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<ul class="pagination">
|
||||||
|
<li class="item pager-next">
|
||||||
|
<?php if (!empty($this->nextId)) { ?>
|
||||||
|
<?php $params['next'] = $this->nextId; ?>
|
||||||
|
<a id="load_more" href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t ('load_more'); ?></a>
|
||||||
|
<?php } elseif ($markReadUrl) { ?>
|
||||||
|
<a id="bigMarkAsRead" href="<?php echo $markReadUrl; ?>"<?php if ($this->conf->reading_confirm) { echo ' class="confirm"';} ?>>
|
||||||
|
<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
|
||||||
|
<span class="bigTick">✔</span><br />
|
||||||
|
<?php echo Minz_Translate::t ('mark_all_read'); ?>
|
||||||
|
</a>
|
||||||
|
<?php } else { ?>
|
||||||
|
<a id="bigMarkAsRead" href=".">
|
||||||
|
<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
|
||||||
|
</a>
|
||||||
|
<?php } ?>
|
||||||
|
</li>
|
||||||
|
</ul>
|
45
sources/app/views/helpers/view/global_view.phtml
Executable file
45
sources/app/views/helpers/view/global_view.phtml
Executable file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php $this->partial ('nav_menu'); ?>
|
||||||
|
|
||||||
|
<div id="stream" class="global categories">
|
||||||
|
<?php
|
||||||
|
$arUrl = array('c' => 'index', 'a' => 'index', 'params' => array());
|
||||||
|
if ($this->conf->view_mode !== 'normal') {
|
||||||
|
$arUrl['params']['output'] = 'normal';
|
||||||
|
}
|
||||||
|
$p = Minz_Request::param('state', '');
|
||||||
|
if (($p != '') && ($this->conf->default_view !== $p)) {
|
||||||
|
$arUrl['params']['state'] = $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->cat_aside as $cat) {
|
||||||
|
$feeds = $cat->feeds ();
|
||||||
|
if (!empty ($feeds)) {
|
||||||
|
?>
|
||||||
|
<div class="box-category">
|
||||||
|
<div class="category">
|
||||||
|
<a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id (); echo Minz_Url::display($arUrl); ?>">
|
||||||
|
<?php echo $cat->name(); ?>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<ul class="feeds">
|
||||||
|
<?php foreach ($feeds as $feed) { ?>
|
||||||
|
<?php $not_read = $feed->nbNotRead (); ?>
|
||||||
|
<li id="f_<?php echo $feed->id (); ?>" class="item<?php echo $feed->inError () ? ' error' : ''; ?><?php echo $feed->nbEntries () == 0 ? ' empty' : ''; ?>">
|
||||||
|
<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
|
||||||
|
<a class="feed" data-unread="<?php echo formatNumber($feed->nbNotRead()); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed->id(); echo Minz_Url::display($arUrl); ?>">
|
||||||
|
<?php echo $feed->name(); ?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<div id="panel"<?php echo $this->conf->display_posts ? '' : ' class="hide_posts"'; ?>>
|
||||||
|
<a class="close" href="#"><?php echo FreshRSS_Themes::icon('close'); ?></a>
|
||||||
|
</div>
|
189
sources/app/views/helpers/view/normal_view.phtml
Executable file
189
sources/app/views/helpers/view/normal_view.phtml
Executable file
|
@ -0,0 +1,189 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$this->partial ('aside_flux');
|
||||||
|
$this->partial ('nav_menu');
|
||||||
|
|
||||||
|
if (!empty($this->entries)) {
|
||||||
|
$display_today = true;
|
||||||
|
$display_yesterday = true;
|
||||||
|
$display_others = true;
|
||||||
|
if ($this->loginOk) {
|
||||||
|
$sharing = $this->conf->sharing;
|
||||||
|
} else {
|
||||||
|
$sharing = array();
|
||||||
|
}
|
||||||
|
$hidePosts = !$this->conf->display_posts;
|
||||||
|
$lazyload = $this->conf->lazyload;
|
||||||
|
$topline_read = $this->conf->topline_read;
|
||||||
|
$topline_favorite = $this->conf->topline_favorite;
|
||||||
|
$topline_date = $this->conf->topline_date;
|
||||||
|
$topline_link = $this->conf->topline_link;
|
||||||
|
$bottomline_read = $this->conf->bottomline_read;
|
||||||
|
$bottomline_favorite = $this->conf->bottomline_favorite;
|
||||||
|
$bottomline_sharing = $this->conf->bottomline_sharing && (count($sharing));
|
||||||
|
$bottomline_tags = $this->conf->bottomline_tags;
|
||||||
|
$bottomline_date = $this->conf->bottomline_date;
|
||||||
|
$bottomline_link = $this->conf->bottomline_link;
|
||||||
|
|
||||||
|
$content_width = $this->conf->content_width;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"><?php
|
||||||
|
?><div id="new-article">
|
||||||
|
<a href="<?php echo Minz_Url::display ($this->url); ?>"><?php echo Minz_Translate::t ('new_article'); ?></a>
|
||||||
|
</div><?php
|
||||||
|
foreach ($this->entries as $item) {
|
||||||
|
if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) {
|
||||||
|
?><div class="day" id="day_today"><?php
|
||||||
|
echo Minz_Translate::t ('today');
|
||||||
|
?><span class="date"> — <?php echo timestamptodate (time (), false); ?></span><?php
|
||||||
|
?><span class="name"><?php echo $this->currentName; ?></span><?php
|
||||||
|
?></div><?php
|
||||||
|
$display_today = false;
|
||||||
|
}
|
||||||
|
if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $this->today)) {
|
||||||
|
?><div class="day" id="day_yesterday"><?php
|
||||||
|
echo Minz_Translate::t ('yesterday');
|
||||||
|
?><span class="date"> — <?php echo timestamptodate (time () - 86400, false); ?></span><?php
|
||||||
|
?><span class="name"><?php echo $this->currentName; ?></span><?php
|
||||||
|
?></div><?php
|
||||||
|
$display_yesterday = false;
|
||||||
|
}
|
||||||
|
if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $this->today)) {
|
||||||
|
?><div class="day" id="day_before_yesterday"><?php
|
||||||
|
echo Minz_Translate::t ('before_yesterday');
|
||||||
|
?><span class="name"><?php echo $this->currentName; ?></span><?php
|
||||||
|
?></div><?php
|
||||||
|
$display_others = false;
|
||||||
|
}
|
||||||
|
?><div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
|
||||||
|
<ul class="horizontal-list flux_header"><?php
|
||||||
|
if ($this->loginOk) {
|
||||||
|
if ($topline_read) {
|
||||||
|
?><li class="item manage"><?php
|
||||||
|
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ()));
|
||||||
|
if ($item->isRead()) {
|
||||||
|
$arUrl['params']['is_read'] = 0;
|
||||||
|
}
|
||||||
|
?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php
|
||||||
|
echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
if ($topline_favorite) {
|
||||||
|
?><li class="item manage"><?php
|
||||||
|
$arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ()));
|
||||||
|
if ($item->isFavorite()) {
|
||||||
|
$arUrl['params']['is_favorite'] = 0;
|
||||||
|
}
|
||||||
|
?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php
|
||||||
|
echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache
|
||||||
|
if (empty($feed)) $feed = $item->feed (true);
|
||||||
|
?><li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li>
|
||||||
|
<li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li>
|
||||||
|
<?php if ($topline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
|
||||||
|
<?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="flux_content">
|
||||||
|
<div class="content <?php echo $content_width; ?>">
|
||||||
|
<h1 class="title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></h1>
|
||||||
|
<?php
|
||||||
|
$author = $item->author ();
|
||||||
|
echo $author != '' ? '<div class="author">' . Minz_Translate::t ('by_author', $author) . '</div>' : '';
|
||||||
|
if ($lazyload) {
|
||||||
|
echo $hidePosts ? lazyIframe(lazyimg($item->content())) : lazyimg($item->content());
|
||||||
|
} else {
|
||||||
|
echo $item->content();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
<ul class="horizontal-list bottom"><?php
|
||||||
|
if ($this->loginOk) {
|
||||||
|
if ($bottomline_read) {
|
||||||
|
?><li class="item manage"><?php
|
||||||
|
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ()));
|
||||||
|
if ($item->isRead()) {
|
||||||
|
$arUrl['params']['is_read'] = 0;
|
||||||
|
}
|
||||||
|
?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php
|
||||||
|
echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
if ($bottomline_favorite) {
|
||||||
|
?><li class="item manage"><?php
|
||||||
|
$arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ()));
|
||||||
|
if ($item->isFavorite()) {
|
||||||
|
$arUrl['params']['is_favorite'] = 0;
|
||||||
|
}
|
||||||
|
?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php
|
||||||
|
echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php
|
||||||
|
?></li><?php
|
||||||
|
}
|
||||||
|
} ?>
|
||||||
|
<li class="item"><?php
|
||||||
|
if ($bottomline_sharing) {
|
||||||
|
$link = urlencode ($item->link ());
|
||||||
|
$title = urlencode ($item->title () . ' · ' . $feed->name ());
|
||||||
|
?><div class="dropdown">
|
||||||
|
<div id="dropdown-share-<?php echo $item->id ();?>" class="dropdown-target"></div>
|
||||||
|
<a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>">
|
||||||
|
<?php echo FreshRSS_Themes::icon('share'); ?>
|
||||||
|
<?php echo Minz_Translate::t ('share'); ?>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li>
|
||||||
|
<?php foreach ($sharing as $share) :?>
|
||||||
|
<li class="item share">
|
||||||
|
<a target="_blank" href="<?php echo FreshRSS_Share::generateUrl($this->conf->shares, $share, $item->link(), $item->title() . ' . ' . $feed->name())?>">
|
||||||
|
<?php echo Minz_Translate::t ($share['name']);?>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<?php endforeach;?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</li><?php
|
||||||
|
$tags = $bottomline_tags ? $item->tags() : null;
|
||||||
|
if (!empty($tags)) {
|
||||||
|
?><li class="item">
|
||||||
|
<div class="dropdown">
|
||||||
|
<div id="dropdown-tags-<?php echo $item->id ();?>" class="dropdown-target"></div>
|
||||||
|
<a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>"><?php
|
||||||
|
echo FreshRSS_Themes::icon('tag'), Minz_Translate::t ('related_tags');
|
||||||
|
?></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-close"><a href="#close">❌</a></li><?php
|
||||||
|
foreach($tags as $tag) {
|
||||||
|
?><li class="item"><a href="<?php echo _url ('index', 'index', 'search', urlencode ('#' . $tag)); ?>"><?php echo $tag; ?></a></li><?php
|
||||||
|
} ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</li><?php
|
||||||
|
}
|
||||||
|
if ($bottomline_date) {
|
||||||
|
?><li class="item date"><?php echo $item->date (); ?></li><?php
|
||||||
|
}
|
||||||
|
if ($bottomline_link) {
|
||||||
|
?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php
|
||||||
|
} ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $this->renderHelper('pagination'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php $this->partial ('nav_entries'); ?>
|
||||||
|
|
||||||
|
<?php } else { ?>
|
||||||
|
<div id="stream" class="alert alert-warn normal">
|
||||||
|
<span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span>
|
||||||
|
<?php echo Minz_Translate::t ('think_to_add'); ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
50
sources/app/views/helpers/view/reader_view.phtml
Executable file
50
sources/app/views/helpers/view/reader_view.phtml
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
$this->partial ('nav_menu');
|
||||||
|
|
||||||
|
if (!empty($this->entries)) {
|
||||||
|
$lazyload = $this->conf->lazyload;
|
||||||
|
$content_width = $this->conf->content_width;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div id="stream" class="reader">
|
||||||
|
<?php foreach ($this->entries as $item) { ?>
|
||||||
|
|
||||||
|
<div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
|
||||||
|
<div class="flux_content">
|
||||||
|
<div class="content <?php echo $content_width; ?>">
|
||||||
|
<?php
|
||||||
|
$feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache
|
||||||
|
if (empty($feed)) $feed = $item->feed (true);
|
||||||
|
?>
|
||||||
|
<a href="<?php echo $item->link (); ?>">
|
||||||
|
<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span>
|
||||||
|
</a>
|
||||||
|
<h1 class="title"><?php echo $item->title (); ?></h1>
|
||||||
|
|
||||||
|
<div class="author">
|
||||||
|
<?php $author = $item->author (); ?>
|
||||||
|
<?php echo $author != '' ? Minz_Translate::t ('by_author', $author) . ' — ' : ''; ?>
|
||||||
|
<?php echo $item->date (); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($lazyload) {
|
||||||
|
echo lazyimg($item->content ());
|
||||||
|
} else {
|
||||||
|
echo $item->content();
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $this->renderHelper('pagination'); ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php } else { ?>
|
||||||
|
<div id="stream" class="alert alert-warn reader">
|
||||||
|
<span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span>
|
||||||
|
<?php echo Minz_Translate::t ('think_to_add'); ?>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
29
sources/app/views/helpers/view/rss_view.phtml
Executable file
29
sources/app/views/helpers/view/rss_view.phtml
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
<?php echo '<?xml version="1.0" encoding="UTF-8" ?>'; ?>
|
||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||||
|
<channel>
|
||||||
|
<title><?php echo $this->rss_title; ?></title>
|
||||||
|
<link><?php echo Minz_Url::display(null, 'html', true); ?></link>
|
||||||
|
<description><?php echo Minz_Translate::t ('rss_feeds_of', $this->rss_title); ?></description>
|
||||||
|
<pubDate><?php echo date('D, d M Y H:i:s O'); ?></pubDate>
|
||||||
|
<lastBuildDate><?php echo gmdate('D, d M Y H:i:s'); ?> GMT</lastBuildDate>
|
||||||
|
<atom:link href="<?php echo Minz_Url::display ($this->url, 'html', true); ?>" rel="self" type="application/rss+xml" />
|
||||||
|
<?php
|
||||||
|
foreach ($this->entries as $item) {
|
||||||
|
?>
|
||||||
|
<item>
|
||||||
|
<title><?php echo $item->title (); ?></title>
|
||||||
|
<link><?php echo $item->link (); ?></link>
|
||||||
|
<?php $author = $item->author (); ?>
|
||||||
|
<?php if ($author != '') { ?>
|
||||||
|
<dc:creator><?php echo $author; ?></dc:creator>
|
||||||
|
<?php } ?>
|
||||||
|
<description><![CDATA[<?php
|
||||||
|
echo $item->content ();
|
||||||
|
?>]]></description>
|
||||||
|
<pubDate><?php echo date('D, d M Y H:i:s O', $item->date (true)); ?></pubDate>
|
||||||
|
<guid isPermaLink="false"><?php echo $item->id (); ?></guid>
|
||||||
|
</item>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
</channel>
|
||||||
|
</rss>
|
52
sources/app/views/importExport/index.phtml
Executable file
52
sources/app/views/importExport/index.phtml
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
<?php $this->partial ('aside_feed'); ?>
|
||||||
|
|
||||||
|
<div class="post ">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<form method="post" action="<?php echo _url('importExport', 'import'); ?>" enctype="multipart/form-data">
|
||||||
|
<legend><?php echo Minz_Translate::t ('import'); ?></legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="group-name" for="file"><?php echo Minz_Translate::t ('file_to_import'); ?></label>
|
||||||
|
<div class="group-controls">
|
||||||
|
<input type="file" name="file" id="file" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('import'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<?php if (count($this->feeds) > 0) { ?>
|
||||||
|
<form method="post" action="<?php echo _url('importExport', 'export'); ?>">
|
||||||
|
<legend><?php echo Minz_Translate::t ('export'); ?></legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="group-controls">
|
||||||
|
<label class="checkbox" for="export_opml">
|
||||||
|
<input type="checkbox" name="export_opml" id="export_opml" value="1" checked="checked" />
|
||||||
|
<?php echo Minz_Translate::t ('export_opml'); ?>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="checkbox" for="export_starred">
|
||||||
|
<input type="checkbox" name="export_starred" id="export_starred" value="1" checked="checked" />
|
||||||
|
<?php echo Minz_Translate::t ('export_starred'); ?>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<select name="export_feeds[]" size="<?php echo min(10, count($this->feeds)); ?>" multiple="multiple">
|
||||||
|
<?php foreach ($this->feeds as $feed) { ?>
|
||||||
|
<option value="<?php echo $feed->id(); ?>"><?php echo $feed->name(); ?></option>
|
||||||
|
<?php } ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group form-actions">
|
||||||
|
<div class="group-controls">
|
||||||
|
<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('export'); ?></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
27
sources/app/views/index/about.phtml
Executable file
27
sources/app/views/index/about.phtml
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="post content">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo Minz_Translate::t ('about_freshrss'); ?></h1>
|
||||||
|
|
||||||
|
<dl class="infos">
|
||||||
|
<dt><?php echo Minz_Translate::t ('project_website'); ?></dt>
|
||||||
|
<dd><a href="<?php echo FRESHRSS_WEBSITE; ?>"><?php echo FRESHRSS_WEBSITE; ?></a></dd>
|
||||||
|
|
||||||
|
<dt><?php echo Minz_Translate::t ('lead_developer'); ?></dt>
|
||||||
|
<dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> — <a href="http://marienfressinaud.fr"><?php echo Minz_Translate::t ('website'); ?></a></dd>
|
||||||
|
|
||||||
|
<dt><?php echo Minz_Translate::t ('bugs_reports'); ?></dt>
|
||||||
|
<dd><?php echo Minz_Translate::t ('github_or_email'); ?></dd>
|
||||||
|
|
||||||
|
<dt><?php echo Minz_Translate::t ('license'); ?></dt>
|
||||||
|
<dd><?php echo Minz_Translate::t ('agpl3'); ?></dd>
|
||||||
|
|
||||||
|
<dt><?php echo Minz_Translate::t ('version'); ?></dt>
|
||||||
|
<dd><?php echo FRESHRSS_VERSION; ?></dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<p><?php echo Minz_Translate::t ('freshrss_description'); ?></p>
|
||||||
|
|
||||||
|
<h1><?php echo Minz_Translate::t ('credits'); ?></h1>
|
||||||
|
<p><?php echo Minz_Translate::t ('credits_content'); ?></p>
|
||||||
|
</div>
|
32
sources/app/views/index/formLogin.phtml
Executable file
32
sources/app/views/index/formLogin.phtml
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
<div class="prompt">
|
||||||
|
<h1><?php echo Minz_Translate::t('login'); ?></h1><?php
|
||||||
|
|
||||||
|
switch (Minz_Configuration::authType()) {
|
||||||
|
case 'form':
|
||||||
|
?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
|
||||||
|
<div>
|
||||||
|
<label for="username"><?php echo Minz_Translate::t('username'); ?></label>
|
||||||
|
<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
|
||||||
|
<input type="password" id="passwordPlain" required="required" />
|
||||||
|
<input type="hidden" id="challenge" name="challenge" /><br />
|
||||||
|
<noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
|
||||||
|
</div>
|
||||||
|
</form><?php
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'persona':
|
||||||
|
?><p>
|
||||||
|
<?php echo FreshRSS_Themes::icon('login'); ?>
|
||||||
|
<a class="signin" href="#"><?php echo Minz_Translate::t('login_with_persona'); ?></a>
|
||||||
|
</p><?php
|
||||||
|
break;
|
||||||
|
} ?>
|
||||||
|
|
||||||
|
<p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p>
|
||||||
|
</div>
|
25
sources/app/views/index/index.phtml
Executable file
25
sources/app/views/index/index.phtml
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$output = Minz_Request::param ('output', 'normal');
|
||||||
|
|
||||||
|
if ($this->loginOk || Minz_Configuration::allowAnonymous()) {
|
||||||
|
if ($output === 'normal') {
|
||||||
|
$this->renderHelper ('view/normal_view');
|
||||||
|
} elseif ($output === 'reader') {
|
||||||
|
$this->renderHelper ('view/reader_view');
|
||||||
|
} elseif ($output === 'global') {
|
||||||
|
$this->renderHelper ('view/global_view');
|
||||||
|
} elseif ($output === 'rss') {
|
||||||
|
$this->renderHelper ('view/rss_view');
|
||||||
|
} else {
|
||||||
|
Minz_Request::_param ('output', 'normal');
|
||||||
|
$output = 'normal';
|
||||||
|
$this->renderHelper ('view/normal_view');
|
||||||
|
}
|
||||||
|
} elseif ($output === 'rss') {
|
||||||
|
// token has already been checked in the controller so we can show the view
|
||||||
|
$this->renderHelper ('view/rss_view');
|
||||||
|
} else {
|
||||||
|
// Normally, it should not happen, but log it anyway
|
||||||
|
Minz_Log::record ('Something is wrong in ' . __FILE__ . ' line ' . __LINE__, Minz_Log::ERROR);
|
||||||
|
}
|
1
sources/app/views/index/login.phtml
Executable file
1
sources/app/views/index/login.phtml
Executable file
|
@ -0,0 +1 @@
|
||||||
|
<?php print_r ($this->res); ?>
|
1
sources/app/views/index/logout.phtml
Executable file
1
sources/app/views/index/logout.phtml
Executable file
|
@ -0,0 +1 @@
|
||||||
|
OK
|
25
sources/app/views/index/logs.phtml
Executable file
25
sources/app/views/index/logs.phtml
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
<div class="post content">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo Minz_Translate::t ('logs'); ?></h1>
|
||||||
|
<form method="post" action="<?php echo _url ('index', 'logs'); ?>"><p>
|
||||||
|
<input type="hidden" name="clearLogs" />
|
||||||
|
<button type="submit" class="btn"><?php echo Minz_Translate::t ('clear_logs'); ?></button>
|
||||||
|
</p></form>
|
||||||
|
|
||||||
|
<?php $items = $this->logsPaginator->items (); ?>
|
||||||
|
|
||||||
|
<?php if (!empty ($items)) { ?>
|
||||||
|
<div class="logs">
|
||||||
|
<?php $this->logsPaginator->render ('logs_pagination.phtml', 'page'); ?>
|
||||||
|
|
||||||
|
<?php foreach ($items as $log) { ?>
|
||||||
|
<div class="log <?php echo $log->level (); ?>"><span class="date"><?php echo @date ('Y-m-d H:i:s', @strtotime ($log->date ())); ?></span><?php echo htmlspecialchars ($log->info (), ENT_NOQUOTES, 'UTF-8'); ?></div>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php $this->logsPaginator->render ('logs_pagination.phtml','page'); ?>
|
||||||
|
</div>
|
||||||
|
<?php } else { ?>
|
||||||
|
<p class="alert alert-warn"><?php echo Minz_Translate::t ('logs_empty'); ?></p>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
57
sources/app/views/javascript/actualize.phtml
Executable file
57
sources/app/views/javascript/actualize.phtml
Executable file
|
@ -0,0 +1,57 @@
|
||||||
|
"use strict";
|
||||||
|
var feeds = [<?php
|
||||||
|
foreach ($this->feeds as $feed) {
|
||||||
|
echo "'", Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'), "',\n";
|
||||||
|
}
|
||||||
|
?>],
|
||||||
|
feed_processed = 0,
|
||||||
|
feed_count = feeds.length;
|
||||||
|
|
||||||
|
function initProgressBar(init) {
|
||||||
|
if (init) {
|
||||||
|
$("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\
|
||||||
|
<?php echo _t('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
|
||||||
|
<progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feed_count + "\"></progress>\
|
||||||
|
</div>");
|
||||||
|
} else {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function updateProgressBar(i) {
|
||||||
|
$("#actualizeProgressBar").val(i);
|
||||||
|
$("#actualizeProgress .progress").html(i + " / " + feed_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFeeds() {
|
||||||
|
if (feed_count === 0) {
|
||||||
|
openNotification("<?php echo _t('no_feed_to_refresh'); ?>", "good");
|
||||||
|
ajax_loading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initProgressBar(true);
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
updateFeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFeed() {
|
||||||
|
var feed = feeds.pop();
|
||||||
|
if (feed == undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: feed,
|
||||||
|
}).complete(function (data) {
|
||||||
|
feed_processed++;
|
||||||
|
updateProgressBar(feed_processed);
|
||||||
|
|
||||||
|
if (feed_processed === feed_count) {
|
||||||
|
initProgressBar(false);
|
||||||
|
} else {
|
||||||
|
updateFeed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
8
sources/app/views/javascript/nbUnreadsPerFeed.phtml
Executable file
8
sources/app/views/javascript/nbUnreadsPerFeed.phtml
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
<?php
|
||||||
|
$result = array();
|
||||||
|
foreach ($this->categories as $cat) {
|
||||||
|
foreach ($cat->feeds() as $feed) {
|
||||||
|
$result[$feed->id()] = $feed->nbNotRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo json_encode($result);
|
2
sources/app/views/javascript/nonce.phtml
Executable file
2
sources/app/views/javascript/nonce.phtml
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
<?php
|
||||||
|
echo json_encode(array('salt1' => $this->salt1, 'nonce' => $this->nonce));
|
19
sources/app/views/stats/idle.phtml
Executable file
19
sources/app/views/stats/idle.phtml
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
<?php $this->partial('aside_stats'); ?>
|
||||||
|
|
||||||
|
<div class="post content">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo _t ('stats_idle'); ?></h1>
|
||||||
|
|
||||||
|
<?php foreach ($this->idleFeeds as $period => $feeds){ ?>
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ($period); ?></h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($feeds as $feed){ ?>
|
||||||
|
<li><?php echo $feed; ?></li>
|
||||||
|
<?php } ?>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
</div>
|
127
sources/app/views/stats/index.phtml
Executable file
127
sources/app/views/stats/index.phtml
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
<?php $this->partial('aside_stats'); ?>
|
||||||
|
|
||||||
|
<div class="post content">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo _t ('stats_main'); ?></h1>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ('stats_entry_repartition'); ?></h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th><?php echo _t ('main_stream'); ?></th>
|
||||||
|
<th><?php echo _t ('all_feeds'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo _t ('status_total'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo _t ('status_read'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo _t ('status_unread'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo _t ('status_favorites'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ('stats_entry_per_day'); ?></h2>
|
||||||
|
<div id="statsEntryPerDay" style="height: 300px"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ('stats_feed_per_category'); ?></h2>
|
||||||
|
<div id="statsFeedPerCategory" style="height: 300px"></div>
|
||||||
|
<div id="statsFeedPerCategoryLegend"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ('stats_entry_per_category'); ?></h2>
|
||||||
|
<div id="statsEntryPerCategory" style="height: 300px"></div>
|
||||||
|
<div id="statsEntryPerCategoryLegend"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo _t ('stats_top_feed'); ?></h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo _t ('feed'); ?></th>
|
||||||
|
<th><?php echo _t ('category'); ?></th>
|
||||||
|
<th><?php echo _t ('stats_entry_count'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($this->topFeed as $feed): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $feed['name']; ?></td>
|
||||||
|
<td><?php echo $feed['category']; ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach;?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
function initStats() {
|
||||||
|
if (!window.Flotr) {
|
||||||
|
if (window.console) {
|
||||||
|
console.log('FreshRSS waiting for Flotr…');
|
||||||
|
}
|
||||||
|
window.setTimeout(initStats, 50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Entry per day
|
||||||
|
Flotr.draw(document.getElementById('statsEntryPerDay'),
|
||||||
|
[<?php echo $this->count ?>],
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false},
|
||||||
|
bars: {horizontal: false, show: true},
|
||||||
|
xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
|
||||||
|
yaxis: {min: 0},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
|
||||||
|
});
|
||||||
|
// Feed per category
|
||||||
|
Flotr.draw(document.getElementById('statsFeedPerCategory'),
|
||||||
|
<?php echo $this->feedByCategory ?>,
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false, horizontalLines: false},
|
||||||
|
pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
|
||||||
|
xaxis: {showLabels: false},
|
||||||
|
yaxis: {showLabels: false},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
|
||||||
|
legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
|
||||||
|
});
|
||||||
|
// Entry per category
|
||||||
|
Flotr.draw(document.getElementById('statsEntryPerCategory'),
|
||||||
|
<?php echo $this->entryByCategory ?>,
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false, horizontalLines: false},
|
||||||
|
pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
|
||||||
|
xaxis: {showLabels: false},
|
||||||
|
yaxis: {showLabels: false},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
|
||||||
|
legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initStats();
|
||||||
|
</script>
|
127
sources/app/views/stats/main.phtml
Executable file
127
sources/app/views/stats/main.phtml
Executable file
|
@ -0,0 +1,127 @@
|
||||||
|
<?php $this->partial('aside_stats'); ?>
|
||||||
|
|
||||||
|
<div class="post content">
|
||||||
|
<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
|
||||||
|
|
||||||
|
<h1><?php echo Minz_Translate::t ('stats_main'); ?></h1>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo Minz_Translate::t ('stats_entry_repartition'); ?></h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th><?php echo Minz_Translate::t ('main_stream'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('all_feeds'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('status_total'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('status_read'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('status_unread'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('status_favorites'); ?></th>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo Minz_Translate::t ('stats_entry_per_day'); ?></h2>
|
||||||
|
<div id="statsEntryPerDay" style="height: 300px"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo Minz_Translate::t ('stats_feed_per_category'); ?></h2>
|
||||||
|
<div id="statsFeedPerCategory" style="height: 300px"></div>
|
||||||
|
<div id="statsFeedPerCategoryLegend"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo Minz_Translate::t ('stats_entry_per_category'); ?></h2>
|
||||||
|
<div id="statsEntryPerCategory" style="height: 300px"></div>
|
||||||
|
<div id="statsEntryPerCategoryLegend"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stat">
|
||||||
|
<h2><?php echo Minz_Translate::t ('stats_top_feed'); ?></h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo Minz_Translate::t ('feed'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('category'); ?></th>
|
||||||
|
<th><?php echo Minz_Translate::t ('stats_entry_count'); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($this->topFeed as $feed): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo $feed['name']; ?></td>
|
||||||
|
<td><?php echo $feed['category']; ?></td>
|
||||||
|
<td class="numeric"><?php echo formatNumber($feed['count']); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach;?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
"use strict";
|
||||||
|
function initStats() {
|
||||||
|
if (!window.Flotr) {
|
||||||
|
if (window.console) {
|
||||||
|
console.log('FreshRSS waiting for Flotr…');
|
||||||
|
}
|
||||||
|
window.setTimeout(initStats, 50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Entry per day
|
||||||
|
Flotr.draw(document.getElementById('statsEntryPerDay'),
|
||||||
|
[<?php echo $this->count ?>],
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false},
|
||||||
|
bars: {horizontal: false, show: true},
|
||||||
|
xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
|
||||||
|
yaxis: {min: 0},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
|
||||||
|
});
|
||||||
|
// Feed per category
|
||||||
|
Flotr.draw(document.getElementById('statsFeedPerCategory'),
|
||||||
|
<?php echo $this->feedByCategory ?>,
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false, horizontalLines: false},
|
||||||
|
pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
|
||||||
|
xaxis: {showLabels: false},
|
||||||
|
yaxis: {showLabels: false},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
|
||||||
|
legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
|
||||||
|
});
|
||||||
|
// Entry per category
|
||||||
|
Flotr.draw(document.getElementById('statsEntryPerCategory'),
|
||||||
|
<?php echo $this->entryByCategory ?>,
|
||||||
|
{
|
||||||
|
grid: {verticalLines: false, horizontalLines: false},
|
||||||
|
pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
|
||||||
|
xaxis: {showLabels: false},
|
||||||
|
yaxis: {showLabels: false},
|
||||||
|
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
|
||||||
|
legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initStats();
|
||||||
|
</script>
|
22
sources/constants.php
Executable file
22
sources/constants.php
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
define('FRESHRSS_VERSION', '0.7.3');
|
||||||
|
define('FRESHRSS_WEBSITE', 'http://freshrss.org');
|
||||||
|
|
||||||
|
// PHP text output compression http://php.net/ob_gzhandler (better to do it at Web server level)
|
||||||
|
define('PHP_COMPRESSION', false);
|
||||||
|
|
||||||
|
// Constantes de chemins
|
||||||
|
define('FRESHRSS_PATH', dirname(__FILE__));
|
||||||
|
|
||||||
|
define('PUBLIC_PATH', FRESHRSS_PATH . '/p');
|
||||||
|
define('INDEX_PATH', PUBLIC_PATH . '/i');
|
||||||
|
define('PUBLIC_RELATIVE', '..');
|
||||||
|
|
||||||
|
define('DATA_PATH', FRESHRSS_PATH . '/data');
|
||||||
|
define('LOG_PATH', DATA_PATH . '/log');
|
||||||
|
define('CACHE_PATH', DATA_PATH . '/cache');
|
||||||
|
|
||||||
|
define('LIB_PATH', FRESHRSS_PATH . '/lib');
|
||||||
|
define('APP_PATH', FRESHRSS_PATH . '/app');
|
||||||
|
|
||||||
|
define('TMP_PATH', sys_get_temp_dir());
|
8
sources/data/.gitignore
vendored
Executable file
8
sources/data/.gitignore
vendored
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
application.ini
|
||||||
|
config.php
|
||||||
|
*_user.php
|
||||||
|
*.sqlite
|
||||||
|
touch.txt
|
||||||
|
no-cache.txt
|
||||||
|
*.bak.php
|
||||||
|
*.lock.txt
|
3
sources/data/.htaccess
Executable file
3
sources/data/.htaccess
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
Order Allow,Deny
|
||||||
|
Deny from all
|
||||||
|
Satisfy all
|
1
sources/data/cache/.gitignore
vendored
Executable file
1
sources/data/cache/.gitignore
vendored
Executable file
|
@ -0,0 +1 @@
|
||||||
|
*.spc
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue