diff options
| author | 2014-10-09 15:53:10 +0200 | |
|---|---|---|
| committer | 2014-10-09 15:53:10 +0200 | |
| commit | f97d4b3b6cca4a55636bbd50158f3c57666b0f08 (patch) | |
| tree | 3ca9dd42155228292f0842d65b9b6d90e9140639 | |
| parent | e51ceb6812e3736aa9b9ce1f2d5181f5b4b6aaa3 (diff) | |
| parent | 444b1552364b39761c3278c7da5152fd3998f216 (diff) | |
Merge branch 'master' into hotfixes
434 files changed, 24204 insertions, 12382 deletions
@@ -1,69 +1,307 @@ -# Changelog -## 2013-10-12 changes with 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 +# Journal des modifications + +## 2014-09-26 FreshRSS 0.8.0 / 0.9.0 (beta) + +* UI + * New interface for statistics + * Fix filter buttons + * Number of articles divided by 2 in reading view + * Redesign of bigMarkAsRead +* Features + * New automatic update system + * New reset auth system +* Security + * "Mark as read" requires POST requests for several articles + * Test HTTP REFERER in install.php +* Configuration + * New "Show all articles" / "Show only unread" / "Adjust viewing" option + * New notification timeout option +* Misc. + * Improve coding style + comments + * Fix SQLite bug "ON DELETE CASCADE" + * Improve performance when importing articles + + +## 2014-08-24 FreshRSS 0.7.4 + +* UI + * Hide categories/feeds with unread articles when showing only unread articles + * Dynamic favicon showing the number of unread articles + * New theme: Screwdriver by Mister aiR +* Statistics + * New page with article repartition + * Improvements +* Security + * Basic protection against XSRF (Cross-Site Request Forgery) based on HTTP Referer (POST requests only) +* API + * Compatible with lighttpd +* Misc. + * Changed lazyload implementation + * Support of HTML5 notifications for new upcoming articles + * Add option to stay logged in +* Bux fixes in export function, add/remove users, keyboard shortcuts, etc. + + +## 2014-07-21 FreshRSS 0.7.3 + +* New options + * Add system of user queries which are shortcuts to filter the view + * New TTL option to limit the frequency at which feeds are refreshed (by cron or manual refresh button). + It is still possible to manually refresh an individual feed at a higher frequency. +* SQL + * Add support for SQLite (beta) in addition to MySQL +* SimplePie + * Complies with HTTP "301 Moved Permanently" responses by automatically updating the URL of feeds that have changed address. +* Themes + * Flat and Dark designs are based on same template file as Origine +* Statistics + * Refactor code + * Add an idle feed page +* Misc + * Several bug fixes + * Add confirmation option when marking all articles as read + * Fix some typo + + +## 2014-06-13 FreshRSS 0.7.2 + +* API compatible with Google Reader API level 2 + * FreshRSS can now be used from e.g.: + * (Android) News+ https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader + * (Android) EasyRSS https://github.com/Alkarex/EasyRSS +* Basic support for audio and video podcasts +* Searching + * New search filters date: and pubdate: accepting ISO 8601 date intervals such as `date:2013-2014` or `pubdate:P1W` + * Possibility to combine search filters, e.g. `date:2014-05 intitle:FreshRSS intitle:Open great reader #Internet` +* Change nav menu with more buttons instead of dropdown menus and add some filters +* New system of import / export + * Support OPML, Json (like Google Reader) and Zip archives + * Can export and import articles (specific option for favorites) +* Refactor "Origine" theme + * Some improvements + * Based on a template file (other themes will use it too) + + +## 2014-02-19 FreshRSS 0.7.1 + +* Mise à jour des flux plus rapide grâce à une meilleure utilisation du cache + * Utilisation 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 +* 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 +* 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 +* 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 +* Amélioration du JavaScript * Correction bugs divers -## 2013-07-02 changes with FreshRSS 0.4.0 -* Correction bug et ajout notification lors de la phase d'installation -* Affichage d'erreur si fichier OPML invalide +## 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 +* 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 -* Possiblité 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) +* 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 toutes façons 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 +* 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) +* 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) +* 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 changes with FreshRSS 0.3.0 +## 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) +* 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* +* 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 +* Création d’un vrai script CRON permettant de mettre tous les flux à jour * Correction bugs divers -## 2013-04-17 changes with FreshRSS 0.2.0 -* Création d'un installateur +## 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 @@ -78,6 +316,7 @@ * Flux sans auteurs gérés normalement * Correction bugs divers -## 2013-04-08 changes with FreshRSS 0.1.0 -* "Première" version +## 2013-04-08 FreshRSS 0.1.0 + +* “Première” version diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 000000000..46f1e7c8f --- /dev/null +++ b/README.fr.md @@ -0,0 +1,101 @@ +* [English version](README.md) + +# 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.8.0 +* Date de publication 2014-09-26 +* 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 (recommandé), ou nginx, lighttpd (non testé sur les autres) +* PHP 5.2.1+ (PHP 5.3.7+ recommandé) + * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (seulement pour accès API sur platformes < 64 bits) + * Recommandés : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip) +* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ +* 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/) + +## 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) @@ -1,44 +1,101 @@ +* [Version française](README.fr.md) + # FreshRSS -FreshRSS est un agrégateur de flux RSS à auto-héberger à l'image de [Selfoss](http://selfoss.aditu.de/), [TinyTinyRSS](http://tt-rss.org/redmine/projects/tt-rss/wiki), [Leed](http://projet.idleman.fr/leed/) our encore [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. +FreshRSS is a self-hosted RSS feed agregator like [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/). + +It is at the same time light-weight, easy to work with, powerful and customizable. + +It is a multi-user application with an anonymous reading mode. + +* Official website: http://freshrss.org +* Demo: http://demo.freshrss.org/ +* Developer: Marien Fressinaud <dev@marienfressinaud.fr> +* Current version: 0.8.0 +* Publication date: 2014-09-26 +* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html) + + -* Site officiel : http://marienfressinaud.github.io/FreshRSS/ -* Démo : http://marienfressinaud.fr/projets/freshrss/ -* Développeur : Marien Fressinaud <dev@marienfressinaud.fr> -* Version actuelle : 0.5.0 -* Date de publication 2013-10-12 -* License AGPL3 +# Note on branches +**This application is still in development!** Please use the branch that suits your needs: - +* Use [the master branch](https://github.com/marienfressinaud/FreshRSS/tree/master/) if you need a stable version. +* [The beta branch](https://github.com/marienfressinaud/FreshRSS/tree/beta) is the default branch: new features are added on a monthly basis. +* For developers and tech savvy persons, [the dev branch](https://github.com/marienfressinaud/FreshRSS/tree/dev) is waiting for you! # Disclaimer -Cette application a été développée pour s'adapter à mes besoins personnels. -Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement -sur un autre serveur que le mien. 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 Apache ou Nginx (non testé sur les autres) -* PHP 5.3 (il me faudrait des retours sur d'autres versions antérieures) -* libxml pour PHP -* cURL -* PDO et MySQL - - +This application was developed to fulfill personal needs not professional needs. +There is no guarantee neither on its security nor its proper functioning. +If there is feature requests which I think are good for the project, I'll do my best to include them. +The best way is to open issues on GitHub +(https://github.com/marienfressinaud/FreshRSS/issues) or by email (dev@marienfressinaud.fr) + +# Requirements +* Light server running Linux or Windows + * It even works on Raspberry Pi with response time under a second (tested with 150 feeds, 22k articles, or 32Mo of compressed data) +* A web server: Apache2 (recommanded), nginx, lighttpd (not tested on others) +* PHP 5.2.1+ (PHP 5.3.7+ recommanded) + * Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (only for API access on platforms under 64 bits) + * Recommanded extensions : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip) +* MySQL 5.0.3+ (recommanded) ou SQLite 3.7.4+ +* A recent browser like Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+ + * Works on 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. Déplacez l'application où vous voulez sur votre serveur (attention, la partie accessible se trouve dans le répertoire `./public`) -3. Accédez à FreshRSS à travers votre navigateur web et suivez les instructions d'installation -4. Tout devrait fonctionner :) En cas de problème, n'hésitez pas à me contacter. - -# Sécurité et conseils -1. Pour une meilleure sécurité, faites en sorte que seul le répertoire `./public` soit accessible par le navigateur. Faites pointer un sous-domaine sur le répertoire `./public` par exemple -2. Dans tous les cas, assurez-vous que `./app/configuration/application.ini` ne puisse pas être téléchargé ! -3. Le fichier de log peut être utile à lire si vous avez des soucis -4. Le fichier `./public/index.php` défini les chemins d'accès aux répertoires clés de l'application. Si vous les bougez, tout se passe ici. -5. Vous pouvez ajouter une tâche CRON sur le script d'actualisation des flux. Il s'agit d'un script PHP à exécuter avec la commande `php`. Par exemple, pour exécuter le script toutes les heures : +1. Get FreshRSS with git or [by downloading the archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip) +2. Dump the application on your server (expose only the `./p/` folder) +3. Add write access on `./data/` folder to the webserver user +4. Access FreshRSS with your browser and follow the installation process +5. Every thing should be working :) If you encounter any problem, feel free to contact me. + +# Access control +It is needed for the multi-user mode to limit access to FreshRSS. You can: +* use form authentication (need JavaScript and PHP 5.3.7+, works with some PHP 5.3.3+) +* use [Mozilla Persona](https://login.persona.org/about) authentication included in FreshRSS +* use HTTP authentication supported by your web server + * See [Apache documentation](http://httpd.apache.org/docs/trunk/howto/auth.html) + * In that case, create a `./p/i/.htaccess` file with a matching `.htpasswd` file. + +# Automatic feed update +* You can add a Cron job to launch the update script. +Check the Cron documentation related to your distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron)…). +It’s a good idea to use the web server user . +For example, if you want to run the script every hour: + +``` +7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 ``` -0 * * * * php /chemin/vers/freshrss/actualize_script.php >/dev/null 2>&1 + +# Advices +* For a better security, expose only the `./p/` folder on the web. + * Be aware that the `./data/` folder contain all personal data, so it is a bad idea to expose it. +* The `./constants.php` file define access to application folder. If you want to customize your installation, every thing happens here. +* If you encounter some problem, logs are accessibles from the interface or manually in `./data/log/*.log` files. + +# Backup +* You need to keep `./data/config.php`, `./data/*_user.php` and `./data/persona/` files +* You can export your feed list in OPML format from FreshRSS +* To save articles, you can use [phpMyAdmin](http://www.phpmyadmin.net) or MySQL tools: + +```bash +mysqldump -u user -p --databases freshrss > freshrss.sql ``` + + +# Included libraries +* [SimplePie](http://simplepie.org/) +* [MINZ](https://github.com/marienfressinaud/MINZ) +* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/) +* [jQuery](http://jquery.com/) +* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) +* [flotr2](http://www.humblesoftware.com/flotr2) + +## Only for some options +* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) +* [phpQuery](http://code.google.com/p/phpquery/) + +## If native functions are not available +* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198) +* [password_compat](https://github.com/ircmaxell/password_compat) diff --git a/actualize_script.php b/actualize_script.php deleted file mode 100755 index 7f72e419e..000000000 --- a/actualize_script.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -// Constantes de chemins -define ('PUBLIC_PATH', realpath (dirname (__FILE__) . '/public')); -define ('LIB_PATH', realpath (PUBLIC_PATH . '/../lib')); -define ('APP_PATH', realpath (PUBLIC_PATH . '/../app')); -define ('LOG_PATH', realpath (PUBLIC_PATH . '/../log')); -define ('CACHE_PATH', realpath (PUBLIC_PATH . '/../cache')); - -$_GET['c'] = 'feed'; -$_GET['a'] = 'actualize'; -$_GET['force'] = true; -$_SERVER['HTTP_HOST'] = ''; - -set_include_path (get_include_path () - . PATH_SEPARATOR - . LIB_PATH - . PATH_SEPARATOR - . LIB_PATH . '/minz' - . PATH_SEPARATOR - . APP_PATH); - -require (APP_PATH . '/App_FrontController.php'); - -$front_controller = new App_FrontController (); -$front_controller->init (); -Session::_param('mail', true); // permet de se passer de la phase de connexion -$front_controller->run (); diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 000000000..9e768397d --- /dev/null +++ b/app/.htaccess @@ -0,0 +1,3 @@ +Order Allow,Deny +Deny from all +Satisfy all diff --git a/app/App_FrontController.php b/app/App_FrontController.php deleted file mode 100644 index 844956cf9..000000000 --- a/app/App_FrontController.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ -require ('FrontController.php'); - -class App_FrontController extends FrontController { - public function init () { - $this->loadLibs (); - $this->loadModels (); - - Session::init (); - RSSThemes::init (); - Translate::init (); - - $this->loadParamsView (); - $this->loadStylesAndScripts (); - $this->loadNotifications (); - } - - private function loadLibs () { - require (LIB_PATH . '/lib_phpQuery.php'); - require (LIB_PATH . '/lib_rss.php'); - require (LIB_PATH . '/SimplePie_autoloader.php'); - require (LIB_PATH . '/lib_text.php'); - } - - private function loadModels () { - include (APP_PATH . '/models/Exception/FeedException.php'); - include (APP_PATH . '/models/Exception/EntriesGetterException.php'); - include (APP_PATH . '/models/RSSConfiguration.php'); - include (APP_PATH . '/models/RSSThemes.php'); - include (APP_PATH . '/models/Days.php'); - include (APP_PATH . '/models/Category.php'); - include (APP_PATH . '/models/Feed.php'); - include (APP_PATH . '/models/Entry.php'); - include (APP_PATH . '/models/EntriesGetter.php'); - include (APP_PATH . '/models/RSSPaginator.php'); - include (APP_PATH . '/models/Log.php'); - } - - private function loadParamsView () { - $this->conf = Session::param ('conf', new RSSConfiguration ()); - View::_param ('conf', $this->conf); - - $entryDAO = new EntryDAO (); - View::_param ('nb_not_read', $entryDAO->countNotRead ()); - - Session::_param ('language', $this->conf->language ()); - } - - private function loadStylesAndScripts () { - $theme = RSSThemes::get_infos($this->conf->theme()); - if ($theme) { - foreach($theme["files"] as $file) { - View::appendStyle (Url::display ('/themes/' . $theme['path'] . '/' . $file)); - } - } - View::appendStyle (Url::display ('/themes/printer/style.css'), 'print'); - if (login_is_conf ($this->conf)) { - View::appendScript ('https://login.persona.org/include.js'); - } - View::appendScript (Url::display ('/scripts/jquery.min.js')); - if ($this->conf->lazyload () === 'yes') { - View::appendScript (Url::display ('/scripts/jquery.lazyload.min.js')); - } - View::appendScript (Url::display ('/scripts/notification.js')); - } - - private function loadNotifications () { - $notif = Session::param ('notification'); - if ($notif) { - View::_param ('notification', $notif); - Session::_param ('notification'); - } - } -} diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php new file mode 100755 index 000000000..231865bd7 --- /dev/null +++ b/app/Controllers/configureController.php @@ -0,0 +1,522 @@ +<?php + +/** + * Controller to handle every configuration options. + */ +class FreshRSS_configure_Controller extends Minz_ActionController { + /** + * This action is called before every other action in that class. It is + * the common boiler plate for every action. It is triggered by the + * underlying framework. + * + * @todo see if the category default configuration is needed here or if + * we can move it to the categorize action + */ + public function firstAction() { + if (!$this->view->loginOk) { + Minz_Error::error( + 403, + array('error' => array(_t('access_denied'))) + ); + } + + $catDAO = new FreshRSS_CategoryDAO(); + $catDAO->checkDefault(); + } + + /** + * This action handles the category configuration page + * + * It displays the category configuration page. + * If this action is reached through a POST request, it loops through + * every category to check for modification then add a new category if + * needed then sends a notification to the user. + * If a category name is emptied, the category is deleted and all + * related feeds are moved to the default category. Related user queries + * are deleted too. + * If a category name is changed, it is updated. + */ + 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]); + + // Remove related queries. + $this->view->conf->remove_query_by_get('c_' . $ids[$key]); + $this->view->conf->save(); + } + } + + if ($newCat != '') { + $cat = new FreshRSS_Category($newCat); + $values = array( + 'id' => $cat->id(), + 'name' => $cat->name(), + ); + + if ($catDAO->searchByName($newCat) == null) { + $catDAO->addCategory($values); + } + } + invalidateHttpCache(); + + Minz_Request::good(_t('categories_updated'), + array('c' => 'configure', 'a' => 'categorize')); + } + + $this->view->categories = $catDAO->listCategories(false); + $this->view->defaultCategory = $catDAO->getDefault(); + $this->view->feeds = $feedDAO->listFeeds(); + + Minz_View::prependTitle(_t('categories_management') . ' · '); + } + + /** + * This action handles the feed configuration page. + * + * It displays the feed configuration page. + * If this action is reached through a POST request, it stores all new + * configuraiton values then sends a notification to the user. + * + * The options available on the page are: + * - name + * - description + * - website URL + * - feed URL + * - category id (default: default category id) + * - CSS path to article on website + * - display in main stream (default: 0) + * - HTTP authentication + * - number of article to retain (default: -2) + * - refresh frequency (default: -2) + * Default values are empty strings unless specified. + */ + public function feedAction() { + $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(_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' => _t('feed_updated') + ); + } else { + $notif = array( + 'type' => 'bad', + 'content' => _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(_t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · '); + } + } else { + Minz_View::prependTitle(_t('rss_feed_management') . ' · '); + } + } + + /** + * This action handles the display configuration page. + * + * It displays the display configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on the page are: + * - language (default: en) + * - theme (default: Origin) + * - content width (default: thin) + * - display of read action in header + * - display of favorite action in header + * - display of date in header + * - display of open action in header + * - display of read action in footer + * - display of favorite action in footer + * - display of sharing action in footer + * - display of tags in footer + * - display of date in footer + * - display of open action in footer + * - html5 notification timeout (default: 0) + * Default values are false unless specified. + */ + public function displayAction() { + if (Minz_Request::isPost()) { + $this->view->conf->_language(Minz_Request::param('language', 'en')); + $this->view->conf->_theme(Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme)); + $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->_html5_notif_timeout(Minz_Request::param('html5_notif_timeout', 0)); + $this->view->conf->save(); + + Minz_Session::_param('language', $this->view->conf->language); + Minz_Translate::reset(); + invalidateHttpCache(); + + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'display')); + } + + $this->view->themes = FreshRSS_Themes::get(); + + Minz_View::prependTitle(_t('display_configuration') . ' · '); + } + + /** + * This action handles the reading configuration page. + * + * It displays the reading configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on the page are: + * - number of posts per page (default: 10) + * - view mode (default: normal) + * - default article view (default: all) + * - load automatically articles + * - display expanded articles + * - display expanded categories + * - hide categories and feeds without unread articles + * - jump on next category or feed when marked as read + * - image lazy loading + * - stick open articles to the top + * - display a confirmation when reading all articles + * - article order (default: DESC) + * - mark articles as read when: + * - displayed + * - opened on site + * - scrolled + * - received + * Default values are false unless specified. + */ + public function readingAction() { + if (Minz_Request::isPost()) { + $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->_display_categories(Minz_Request::param('display_categories', false)); + $this->view->conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false)); + $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); + $this->view->conf->_lazyload(Minz_Request::param('lazyload', false)); + $this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false)); + $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(); + + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'reading')); + } + + Minz_View::prependTitle(_t('reading_configuration') . ' · '); + } + + /** + * This action handles the sharing configuration page. + * + * It displays the sharing configuration page. + * If this action is reached through a POST request, it stores all + * configuration values then sends a notification to the user. + */ + public function sharingAction() { + if (Minz_Request::isPost()) { + $params = Minz_Request::params(); + $this->view->conf->_sharing($params['share']); + $this->view->conf->save(); + invalidateHttpCache(); + + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'sharing')); + } + + Minz_View::prependTitle(_t('sharing') . ' · '); + } + + /** + * This action handles the shortcut configuration page. + * + * It displays the shortcut configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The authorized values for shortcuts are letters (a to z), numbers (0 + * to 9), function keys (f1 to f12), backspace, delete, down, end, enter, + * escape, home, insert, left, page down, page up, return, right, space, + * tab and up. + */ + public function shortcutAction() { + $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', + 'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left', + 'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right', + 's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y', + 'z', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', + 'f10', 'f11', 'f12'); + $this->view->list_keys = $list_keys; + + if (Minz_Request::isPost()) { + $shortcuts = Minz_Request::param('shortcuts'); + $shortcuts_ok = array(); + + foreach ($shortcuts as $key => $value) { + if (in_array($value, $list_keys)) { + $shortcuts_ok[$key] = $value; + } + } + + $this->view->conf->_shortcuts($shortcuts_ok); + $this->view->conf->save(); + invalidateHttpCache(); + + Minz_Request::good(_t('shortcuts_updated'), + array('c' => 'configure', 'a' => 'shortcut')); + } + + Minz_View::prependTitle(_t('shortcuts') . ' · '); + } + + /** + * This action display the user configuration page + * + * @todo move that action in the user controller + */ + public function usersAction() { + Minz_View::prependTitle(_t('users') . ' · '); + } + + /** + * This action handles the archive configuration page. + * + * It displays the archive configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on that page are: + * - duration to retain old article (default: 3) + * - number of article to retain per feed (default: 0) + * - refresh frequency (default: -2) + * + * @todo explain why the default value is -2 but this value does not + * exist in the drop-down list + */ + public function archivingAction() { + if (Minz_Request::isPost()) { + $this->view->conf->_old_entries(Minz_Request::param('old_entries', 3)); + $this->view->conf->_keep_history_default(Minz_Request::param('keep_history_default', 0)); + $this->view->conf->_ttl_default(Minz_Request::param('ttl_default', -2)); + $this->view->conf->save(); + invalidateHttpCache(); + + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'archiving')); + } + + Minz_View::prependTitle(_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); + } + } + + /** + * This action handles the user queries configuration page. + * + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user then + * redirect to the same page. + * If this action is not reached through a POST request, it displays the + * configuration page and verifies that every user query is runable by + * checking if categories and feeds are still in use. + */ + public function queriesAction() { + if (Minz_Request::isPost()) { + $queries = Minz_Request::param('queries', array()); + + foreach ($queries as $key => $query) { + if (!$query['name']) { + $query['name'] = _t('query_number', $key + 1); + } + } + $this->view->conf->_queries($queries); + $this->view->conf->save(); + + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'queries')); + } else { + $this->view->query_get = array(); + $cat_dao = new FreshRSS_CategoryDAO(); + $feed_dao = FreshRSS_Factory::createFeedDao(); + foreach ($this->view->conf->queries as $key => $query) { + if (!isset($query['get'])) { + continue; + } + + switch ($query['get'][0]) { + case 'c': + $category = $cat_dao->searchById(substr($query['get'], 2)); + + $deprecated = true; + $cat_name = ''; + if ($category) { + $cat_name = $category->name(); + $deprecated = false; + } + + $this->view->query_get[$key] = array( + 'type' => 'category', + 'name' => $cat_name, + 'deprecated' => $deprecated, + ); + break; + case 'f': + $feed = $feed_dao->searchById(substr($query['get'], 2)); + + $deprecated = true; + $feed_name = ''; + if ($feed) { + $feed_name = $feed->name(); + $deprecated = false; + } + + $this->view->query_get[$key] = array( + 'type' => 'feed', + 'name' => $feed_name, + 'deprecated' => $deprecated, + ); + break; + case 's': + $this->view->query_get[$key] = array( + 'type' => 'favorite', + 'name' => 'favorite', + 'deprecated' => false, + ); + break; + case 'a': + $this->view->query_get[$key] = array( + 'type' => 'all', + 'name' => 'all', + 'deprecated' => false, + ); + break; + } + } + } + + Minz_View::prependTitle(_t('queries') . ' · '); + } + + /** + * This action handles the creation of a user query. + * + * It gets the GET parameters and stores them in the configuration query + * storage. Before it is saved, the unwanted parameters are unset to keep + * lean data. + */ + public function addQueryAction() { + $whitelist = array('get', 'order', 'name', 'search', 'state'); + $queries = $this->view->conf->queries; + $query = Minz_Request::params(); + $query['name'] = _t('query_number', count($queries) + 1); + foreach ($query as $key => $value) { + if (!in_array($key, $whitelist)) { + unset($query[$key]); + } + } + if (!empty($query['state']) && $query['state'] & FreshRSS_Entry::STATE_STRICT) { + $query['state'] -= FreshRSS_Entry::STATE_STRICT; + } + $queries[] = $query; + $this->view->conf->_queries($queries); + $this->view->conf->save(); + + Minz_Request::good(_t('query_created', $query['name']), + array('c' => 'configure', 'a' => 'queries')); + } +} diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php new file mode 100755 index 000000000..ab66d9198 --- /dev/null +++ b/app/Controllers/entryController.php @@ -0,0 +1,167 @@ +<?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 (!Minz_Request::isPost()) { + return; + } + + 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); + } +} diff --git a/app/Controllers/errorController.php b/app/Controllers/errorController.php new file mode 100644 index 000000000..922650b3d --- /dev/null +++ b/app/Controllers/errorController.php @@ -0,0 +1,38 @@ +<?php + +class FreshRSS_error_Controller extends Minz_ActionController { + public function indexAction() { + switch (Minz_Request::param('code')) { + case 403: + $this->view->code = 'Error 403 - Forbidden'; + break; + case 404: + $this->view->code = 'Error 404 - Not found'; + break; + case 500: + $this->view->code = 'Error 500 - Internal Server Error'; + break; + case 503: + $this->view->code = 'Error 503 - Service Unavailable'; + break; + default: + $this->view->code = 'Error 404 - Not found'; + } + + $errors = Minz_Request::param('logs', array()); + $this->view->errorMessage = trim(implode($errors)); + if ($this->view->errorMessage == '') { + switch(Minz_Request::param('code')) { + case 403: + $this->view->errorMessage = Minz_Translate::t('forbidden_access'); + break; + case 404: + default: + $this->view->errorMessage = Minz_Translate::t('page_not_found'); + break; + } + } + + Minz_View::prependTitle($this->view->code . ' · '); + } +} diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php new file mode 100755 index 000000000..c7cc25fbb --- /dev/null +++ b/app/Controllers/feedController.php @@ -0,0 +1,438 @@ +<?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') { + // List feeds to remove then related user queries. + $feeds = $feedDAO->listByCategory($id); + + if ($feedDAO->deleteFeedByCategory ($id)) { + // Remove related queries + foreach ($feeds as $feed) { + $this->view->conf->remove_query_by_get('f_' . $feed->id()); + } + $this->view->conf->save(); + + $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)) { + // Remove related queries + $this->view->conf->remove_query_by_get('f_' . $id); + $this->view->conf->save(); + + $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); + + $redirect_url = Minz_Request::param('r', false, true); + if ($redirect_url) { + Minz_Request::forward($redirect_url); + } elseif ($type == 'category') { + Minz_Request::forward(array ('c' => 'configure', 'a' => 'categorize'), true); + } else { + Minz_Request::forward(array ('c' => 'configure', 'a' => 'feed'), true); + } + } + } +} diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php new file mode 100644 index 000000000..f329766b8 --- /dev/null +++ b/app/Controllers/importExportController.php @@ -0,0 +1,447 @@ +<?php + +class FreshRSS_importExport_Controller extends Minz_ActionController { + public function firstAction() { + if (!$this->view->loginOk) { + Minz_Error::error( + 403, + array('error' => array(_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(_t('import_export') . ' · '); + } + + public function importAction() { + if (!Minz_Request::isPost()) { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } + + $file = $_FILES['file']; + $status_file = $file['error']; + + if ($status_file !== 0) { + Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file); + Minz_Request::bad(_t('file_cannot_be_uploaded'), + array('c' => 'importExport', 'a' => 'index')); + } + + @set_time_limit(300); + + $type_file = $this->guessFileType($file['name']); + + $list_files = array( + 'opml' => array(), + 'json_starred' => array(), + 'json_feed' => array() + ); + + // We try to list all files according to their type + $list = array(); + if ($type_file === 'zip' && extension_loaded('zip')) { + $zip = zip_open($file['tmp_name']); + + if (!is_resource($zip)) { + // zip_open cannot open file: something is wrong + Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip); + Minz_Request::bad(_t('zip_error'), + array('c' => 'importExport', 'a' => 'index')); + } + + while (($zipfile = zip_read($zip)) !== false) { + if (!is_resource($zipfile)) { + // zip_entry() can also return an error code! + Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile); + } else { + $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); + if ($type_file !== 'unknown') { + $list_files[$type_zipfile][] = zip_entry_read( + $zipfile, + zip_entry_filesize($zipfile) + ); + } + } + } + + zip_close($zip); + } elseif ($type_file === 'zip') { + // Zip extension is not loaded + Minz_Request::bad(_t('no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); + } elseif ($type_file !== 'unknown') { + $list_files[$type_file][] = file_get_contents($file['tmp_name']); + } + + // Import file contents. + // OPML first(so categories and feeds are imported) + // Starred articles then so the "favourite" status is already set + // And finally all other files. + $error = false; + foreach ($list_files['opml'] as $opml_file) { + $error = $this->importOpml($opml_file); + } + foreach ($list_files['json_starred'] as $article_file) { + $error = $this->importArticles($article_file, true); + } + foreach ($list_files['json_feed'] as $article_file) { + $error = $this->importArticles($article_file); + } + + // And finally, we get import status and redirect to the home page + Minz_Session::_param('actualize_feeds', true); + $content_notif = $error === true ? _t('feeds_imported_with_errors') : + _t('feeds_imported'); + Minz_Request::good($content_notif); + } + + private function guessFileType($filename) { + // 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. + + if (substr_compare($filename, '.zip', -4) === 0) { + return 'zip'; + } elseif (substr_compare($filename, '.opml', -5) === 0 || + substr_compare($filename, '.xml', -4) === 0) { + return 'opml'; + } elseif (substr_compare($filename, '.json', -5) === 0 && + strpos($filename, 'starred') !== false) { + return 'json_starred'; + } elseif (substr_compare($filename, '.json', -5) === 0) { + return 'json_feed'; + } else { + return 'unknown'; + } + } + + 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 = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']); + $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']); + $website = ''; + if (isset($feed_elt['htmlUrl'])) { + $website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']); + } + $description = ''; + if (isset($feed_elt['description'])) { + $description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description']); + } + + $error = false; + try { + // Create a Feed object and add it in DB + $feed = new FreshRSS_Feed($url); + $feed->_category($cat->id()); + $feed->_name($name); + $feed->_website($website); + $feed->_description($description); + + // 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(Minz_Helper::htmlspecialchars_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; + $article_to_feed = array(); + + // First, we check feeds of articles are in DB (and add them if needed). + foreach ($article_object['items'] as $item) { + $feed = $this->addFeedArticles($item['origin'], $google_compliant); + if (is_null($feed)) { + $error = true; + } else { + $article_to_feed[$item['id']] = $feed->id(); + } + } + + // Then, articles are imported. + $prepared_statement = $this->entryDAO->addEntryPrepare(); + $this->entryDAO->beginTransaction(); + foreach ($article_object['items'] as $item) { + if (!isset($article_to_feed[$item['id']])) { + continue; + } + + $feed_id = $article_to_feed[$item['id']]; + $author = isset($item['author']) ? $item['author'] : ''; + $key_content = ($google_compliant && !isset($item['content'])) ? + 'summary' : 'content'; + $tags = $item['categories']; + if ($google_compliant) { + $tags = array_filter($tags, function($var) { + return strpos($var, '/state/com.google') === false; + }); + } + + $entry = new FreshRSS_Entry( + $feed_id, $item['id'], $item['title'], $author, + $item[$key_content]['content'], $item['alternate'][0]['href'], + $item['published'], $is_read, $starred + ); + $entry->_id(min(time(), $entry->date(true)) . uSecString()); + $entry->_tags($tags); + + $values = $entry->toArray(); + $id = $this->entryDAO->addEntry($values, $prepared_statement); + + if (!$error && ($id === false)) { + $error = true; + } + } + $this->entryDAO->commit(); + + 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']; + + 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()) { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } + + $this->view->_useLayout(false); + + $export_opml = Minz_Request::param('export_opml', false); + $export_starred = Minz_Request::param('export_starred', false); + $export_feeds = Minz_Request::param('export_feeds', array()); + + $export_files = array(); + if ($export_opml) { + $export_files['feeds.opml'] = $this->generateOpml(); + } + + if ($export_starred) { + $export_files['starred.json'] = $this->generateArticles('starred'); + } + + foreach ($export_feeds as $feed_id) { + $feed = $this->feedDAO->searchById($feed_id); + if ($feed) { + $filename = 'feed_' . $feed->category() . '_' + . $feed->id() . '.json'; + $export_files[$filename] = $this->generateArticles( + 'feed', $feed + ); + } + } + + $nb_files = count($export_files); + if ($nb_files > 1) { + // If there are more than 1 file to export, we need a zip archive. + try { + $this->exportZip($export_files); + } catch (Exception $e) { + # Oops, there is no Zip extension! + Minz_Request::bad(_t('export_no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); + } + } elseif ($nb_files === 1) { + // Only one file? Guess its type and export it. + $filename = key($export_files); + $type = $this->guessFileType($filename); + $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type); + } else { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } + } + + 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 = _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 = _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'); + } + + private function exportZip($files) { + if (!extension_loaded('zip')) { + throw new Exception(); + } + + // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly + $zip_file = tempnam('tmp', 'zip'); + $zip = new ZipArchive(); + $zip->open($zip_file, ZipArchive::OVERWRITE); + + foreach ($files as $filename => $content) { + $zip->addFromString($filename, $content); + } + + // Close and send to user + $zip->close(); + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($zip_file)); + header('Content-Disposition: attachment; filename="freshrss_export.zip"'); + readfile($zip_file); + unlink($zip_file); + } + + private function exportFile($filename, $content, $type) { + if ($type === 'unknown') { + return; + } + + $content_type = ''; + if ($type === 'opml') { + $content_type = "text/opml"; + } elseif ($type === 'json_feed' || $type === 'json_starred') { + $content_type = "text/json"; + } + + header('Content-Type: ' . $content_type . '; charset=utf-8'); + header('Content-disposition: attachment; filename=' . $filename); + print($content); + } +} diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php new file mode 100755 index 000000000..e8e26b142 --- /dev/null +++ b/app/Controllers/indexController.php @@ -0,0 +1,498 @@ +<?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(); + 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 = 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', ''); + + $ajax_request = Minz_Request::param('ajax', false); + if ($output === 'reader') { + $nb = max(1, round($nb / 2)); + } + + if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all? + switch ($getType) { + case 'a': + $hasUnread = $this->view->nb_not_read > 0; + 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 = 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, $this->view->state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault); + + // Si on a récupéré aucun article "non lus" + // on essaye de récupérer tous les articles + if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) { + Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG); + $feedDAO = FreshRSS_Factory::createFeedDao(); + try { + $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); + } + Minz_Request::_param('state', $this->view->state); + + 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'); + } + + private static function makeLongTermCookie($username, $passwordHash) { + do { + $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true)); + $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; + } while (file_exists($tokenFile)); + if (@file_put_contents($tokenFile, $username . "\t" . $passwordHash) === false) { + return false; + } + $expire = time() + 2629744; //1 month //TODO: Use a configuration instead + Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); + Minz_Session::_param('token', $token); + return $token; + } + + private static function deleteLongTermCookie() { + Minz_Session::deleteLongTermCookie('FreshRSS_login'); + $token = Minz_Session::param('token', null); + if (ctype_alnum($token)) { + @unlink(DATA_PATH . '/tokens/' . $token . '.txt'); + } + Minz_Session::_param('token'); + if (rand(0, 10) === 1) { + self::purgeTokens(); + } + } + + private static function purgeTokens() { + $oldest = time() - 2629744; //1 month //TODO: Use a configuration instead + foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $fileInfo) { + if ($fileInfo->getExtension() === 'txt' && $fileInfo->getMTime() < $oldest) { + @unlink($fileInfo->getPathname()); + } + } + } + + public function formLoginAction () { + if ($this->view->loginOk) { + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + + 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); + if (Minz_Request::param('keep_logged_in', false)) { + self::makeLongTermCookie($username, $s); + } else { + self::deleteLongTermCookie(); + } + } else { + Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); + } + } 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'); + self::deleteLongTermCookie(); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + + public function resetAuthAction() { + Minz_View::prependTitle(_t('auth_reset') . ' · '); + Minz_View::appendScript(Minz_Url::display( + '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') + )); + + $this->view->no_form = false; + // Enable changement of auth only if Persona! + if (Minz_Configuration::authType() != 'persona') { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_not_persona') + ); + $this->view->no_form = true; + return; + } + + $conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser()); + // Admin user must have set its master password. + if (!$conf->passwordHash) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_no_password_set') + ); + $this->view->no_form = true; + return; + } + + invalidateHttpCache(); + + if (Minz_Request::isPost()) { + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $c = Minz_Request::param('challenge', ''); + if (!(ctype_alnum($username) && ctype_graph($c) && ctype_alnum($nonce))) { + Minz_Log::debug('Invalid credential parameters:' . + ' user=' . $username . + ' challenge=' . $c . + ' nonce=' . $nonce); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if ($ok) { + Minz_Configuration::_authType('form'); + $ok = Minz_Configuration::writeFile(); + + if ($ok) { + Minz_Request::good(_t('auth_form_set')); + } else { + Minz_Request::bad(_t('auth_form_not_set'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } else { + Minz_Log::debug('Password mismatch for user ' . $username . + ', nonce=' . $nonce . ', c=' . $c); + + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } + } +} diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php new file mode 100755 index 000000000..67148350f --- /dev/null +++ b/app/Controllers/javascriptController.php @@ -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 = ''; + } +} diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php new file mode 100644 index 000000000..256543f37 --- /dev/null +++ b/app/Controllers/statsController.php @@ -0,0 +1,129 @@ +<?php + +/** + * Controller to handle application statistics. + */ +class FreshRSS_stats_Controller extends Minz_ActionController { + + /** + * This action handles the statistic main page. + * + * It displays the statistic main page. + * The values computed to display the page are: + * - repartition of read/unread/favorite/not favorite + * - number of article per day + * - number of feed by category + * - number of article by category + * - list of most prolific feed + */ + public function indexAction() { + $statsDAO = FreshRSS_Factory::createStatsDAO(); + Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); + $this->view->repartition = $statsDAO->calculateEntryRepartition(); + $this->view->count = $statsDAO->calculateEntryCount(); + $this->view->feedByCategory = $statsDAO->calculateFeedByCategory(); + $this->view->entryByCategory = $statsDAO->calculateEntryByCategory(); + $this->view->topFeed = $statsDAO->calculateTopFeed(); + } + + /** + * This action handles the idle feed statistic page. + * + * It displays the list of idle feed for different period. The supported + * periods are: + * - last year + * - last 6 months + * - last 3 months + * - last month + * - last week + */ + public function idleAction() { + $statsDAO = FreshRSS_Factory::createStatsDAO(); + $feeds = $statsDAO->calculateFeedLastDate(); + $idleFeeds = array( + 'last_year' => array(), + 'last_6_month' => array(), + 'last_3_month' => array(), + 'last_month' => array(), + 'last_week' => array(), + ); + $now = new \DateTime(); + $feedDate = clone $now; + $lastWeek = clone $now; + $lastWeek->modify('-1 week'); + $lastMonth = clone $now; + $lastMonth->modify('-1 month'); + $last3Month = clone $now; + $last3Month->modify('-3 month'); + $last6Month = clone $now; + $last6Month->modify('-6 month'); + $lastYear = clone $now; + $lastYear->modify('-1 year'); + + foreach ($feeds as $feed) { + $feedDate->setTimestamp($feed['last_date']); + if ($feedDate >= $lastWeek) { + continue; + } + if ($feedDate < $lastYear) { + $idleFeeds['last_year'][] = $feed; + } elseif ($feedDate < $last6Month) { + $idleFeeds['last_6_month'][] = $feed; + } elseif ($feedDate < $last3Month) { + $idleFeeds['last_3_month'][] = $feed; + } elseif ($feedDate < $lastMonth) { + $idleFeeds['last_month'][] = $feed; + } elseif ($feedDate < $lastWeek) { + $idleFeeds['last_week'][] = $feed; + } + } + + $this->view->idleFeeds = $idleFeeds; + } + + /** + * This action handles the article repartition statistic page. + * + * It displays the number of article and the average of article for the + * following periods: + * - hour of the day + * - day of the week + * - month + * + * @todo verify that the metrics used here make some sense. Especially + * for the average. + */ + public function repartitionAction() { + $statsDAO = FreshRSS_Factory::createStatsDAO(); + $categoryDAO = new FreshRSS_CategoryDAO(); + $feedDAO = FreshRSS_Factory::createFeedDao(); + Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); + $id = Minz_Request::param ('id', null); + $this->view->categories = $categoryDAO->listCategories(); + $this->view->feed = $feedDAO->searchById($id); + $this->view->days = $statsDAO->getDays(); + $this->view->months = $statsDAO->getMonths(); + $this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id); + $this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id); + $this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id); + $this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id); + $this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id); + $this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id); + } + + /** + * This action is called before every other action in that class. It is + * the common boiler plate for every action. It is triggered by the + * underlying framework. + */ + public function firstAction() { + if (!$this->view->loginOk) { + Minz_Error::error( + 403, array('error' => array(Minz_Translate::t('access_denied'))) + ); + } + + Minz_View::prependTitle(Minz_Translate::t('stats') . ' · '); + } + +} diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php new file mode 100644 index 000000000..da5bddc65 --- /dev/null +++ b/app/Controllers/updateController.php @@ -0,0 +1,129 @@ +<?php + +class FreshRSS_update_Controller extends Minz_ActionController { + public function firstAction() { + $current_user = Minz_Session::param('currentUser', ''); + if (!$this->view->loginOk && Minz_Configuration::isAdmin($current_user)) { + Minz_Error::error( + 403, + array('error' => array(_t('access_denied'))) + ); + } + + invalidateHttpCache(); + + Minz_View::prependTitle(_t('update_system') . ' · '); + $this->view->update_to_apply = false; + $this->view->last_update_time = 'unknown'; + $this->view->check_last_hour = false; + $timestamp = (int)@file_get_contents(DATA_PATH . '/last_update.txt'); + if (is_numeric($timestamp) && $timestamp > 0) { + $this->view->last_update_time = timestamptodate($timestamp); + $this->view->check_last_hour = (time() - 3600) <= $timestamp; + } + } + + public function indexAction() { + if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('file_is_nok', FRESHRSS_PATH) + ); + } elseif (file_exists(UPDATE_FILENAME)) { + // There is an update file to apply! + $this->view->update_to_apply = true; + $this->view->message = array( + 'status' => 'good', + 'title' => _t('ok'), + 'body' => _t('update_can_apply') + ); + } + } + + public function checkAction() { + $this->view->change_view('update', 'index'); + + if (file_exists(UPDATE_FILENAME) || $this->view->check_last_hour) { + // There is already an update file to apply: we don't need to check + // the webserver! + // Or if already check during the last hour, do nothing. + Minz_Request::forward(array('c' => 'update')); + + return; + } + + $c = curl_init(FRESHRSS_UPDATE_WEBSITE); + curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2); + $result = curl_exec($c); + $c_status = curl_getinfo($c, CURLINFO_HTTP_CODE); + $c_error = curl_error($c); + curl_close($c); + + if ($c_status !== 200) { + Minz_Log::error( + 'Error during update (HTTP code ' . $c_status . '): ' . $c_error + ); + + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('update_server_not_found', FRESHRSS_UPDATE_WEBSITE) + ); + return; + } + + $res_array = explode("\n", $result, 2); + $status = $res_array[0]; + if (strpos($status, 'UPDATE') !== 0) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('no_update') + ); + + @file_put_contents(DATA_PATH . '/last_update.txt', time()); + + return; + } + + $script = $res_array[1]; + if (file_put_contents(UPDATE_FILENAME, $script) !== false) { + Minz_Request::forward(array('c' => 'update')); + } else { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('update_problem', 'Cannot save the update script') + ); + } + } + + public function applyAction() { + if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH)) { + Minz_Request::forward(array('c' => 'update'), true); + } + + require(UPDATE_FILENAME); + + if (Minz_Request::isPost()) { + save_info_update(); + } + + if (!need_info_update()) { + $res = apply_update(); + + if ($res === true) { + @unlink(UPDATE_FILENAME); + @file_put_contents(DATA_PATH . '/last_update.txt', time()); + + Minz_Request::good(_t('update_finished')); + } else { + Minz_Request::bad(_t('update_problem', $res), + array('c' => 'update', 'a' => 'index')); + } + } + } +} diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php new file mode 100644 index 000000000..a9e6c32bc --- /dev/null +++ b/app/Controllers/usersController.php @@ -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/install.sql.' . $db['type'] . '.php'); + + $new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language); + if (!in_array($new_user_language, $this->view->conf->availableLanguages())) { + $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/install.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); + } +} diff --git a/app/Exceptions/BadUrlException.php b/app/Exceptions/BadUrlException.php new file mode 100644 index 000000000..7d1fe110e --- /dev/null +++ b/app/Exceptions/BadUrlException.php @@ -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'); + } +} diff --git a/app/Exceptions/EntriesGetterException.php b/app/Exceptions/EntriesGetterException.php new file mode 100644 index 000000000..eaa330979 --- /dev/null +++ b/app/Exceptions/EntriesGetterException.php @@ -0,0 +1,7 @@ +<?php + +class FreshRSS_EntriesGetter_Exception extends Exception { + public function __construct ($message) { + parent::__construct ($message); + } +} diff --git a/app/models/Exception/EntriesGetterException.php b/app/Exceptions/FeedException.php index 3a51bff7c..50918ba95 100644 --- a/app/models/Exception/EntriesGetterException.php +++ b/app/Exceptions/FeedException.php @@ -1,6 +1,5 @@ <?php - -class EntriesGetterException extends Exception { +class FreshRSS_Feed_Exception extends Exception { public function __construct ($message) { parent::__construct ($message); } diff --git a/app/FreshRSS.php b/app/FreshRSS.php new file mode 100644 index 000000000..cdf8962cb --- /dev/null +++ b/app/FreshRSS.php @@ -0,0 +1,182 @@ +<?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(); + if (Minz_Request::isPost() && !is_referer_from_same_domain()) { + $loginOk = false; //Basic protection against XSRF attacks + Minz_Error::error( + 403, + array('error' => array(Minz_Translate::t('access_denied') . ' [HTTP_REFERER=' . + htmlspecialchars(empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']) . ']')) + ); + } + Minz_View::_param('loginOk', $loginOk); + $this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests + $this->loadNotifications(); + } + + private static function getCredentialsFromLongTermCookie() { + $token = Minz_Session::getLongTermCookie('FreshRSS_login'); + if (!ctype_alnum($token)) { + return array(); + } + $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; + $mtime = @filemtime($tokenFile); + if ($mtime + 2629744 < time()) { //1 month //TODO: Use a configuration instead + @unlink($tokenFile); + return array(); //Expired or token does not exist + } + $credentials = @file_get_contents($tokenFile); + return $credentials === false ? array() : explode("\t", $credentials, 2); + } + + private function accessControl($currentUser) { + if ($currentUser == '') { + switch (Minz_Configuration::authType()) { + case 'form': + $credentials = self::getCredentialsFromLongTermCookie(); + if (isset($credentials[1])) { + $currentUser = trim($credentials[0]); + Minz_Session::_param('passwordHash', trim($credentials[1])); + } + $loginOk = $currentUser != ''; + if (!$loginOk) { + $currentUser = Minz_Configuration::defaultUser(); + Minz_Session::_param('passwordHash'); + } + break; + case 'http_auth': + $currentUser = httpAuthUser(); + $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; + } + } + 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) { + if ($file[0] === '_') { + $theme_id = 'base-theme'; + $filename = substr($file, 1); + } else { + $theme_id = $theme['id']; + $filename = $file; + } + $filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename); + Minz_View::appendStyle(Minz_Url::display( + '/themes/' . $theme_id . '/' . $filename . '?' . $filetime + )); + } + } + + 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; + } + Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js'))); + Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); + Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); + } + + private function loadNotifications () { + $notif = Minz_Session::param ('notification'); + if ($notif) { + Minz_View::_param ('notification', $notif); + Minz_Session::_param ('notification'); + } + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php new file mode 100644 index 000000000..0a0dbd3ca --- /dev/null +++ b/app/Models/Category.php @@ -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; + } +} diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php new file mode 100644 index 000000000..f11f87f47 --- /dev/null +++ b/app/Models/CategoryDAO.php @@ -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; + } +} diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php new file mode 100644 index 000000000..95f819779 --- /dev/null +++ b/app/Models/Configuration.php @@ -0,0 +1,335 @@ +<?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, + 'display_categories' => false, + 'hide_read_feeds' => 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' => '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', + 'user_filter' => 'u', + 'help' => 'f1', + ), + '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(), + 'html5_notif_timeout' => 0, + ); + + 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 remove_query_by_get($get) { + $final_queries = array(); + foreach ($this->queries as $key => $query) { + if (empty($query['get']) || $query['get'] !== $get) { + $final_queries[$key] = $query; + } + } + $this->_queries($final_queries); + } + + 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) { + switch ($value) { + case FreshRSS_Entry::STATE_ALL: + // left blank on purpose + case FreshRSS_Entry::STATE_NOT_READ: + // left blank on purpose + case FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ: + $this->data['default_view'] = $value; + break; + default: + $this->data['default_view'] = FreshRSS_Entry::STATE_ALL; + break; + } + } + public function _display_posts ($value) { + $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; + } + public function _display_categories ($value) { + $this->data['display_categories'] = ((bool)$value) && $value !== 'no'; + } + public function _hide_read_feeds($value) { + $this->data['hide_read_feeds'] = (bool)$value; + } + public function _onread_jump_next ($value) { + $this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no'; + } + 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(); + $unique = 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']; + } + + $json_value = json_encode($value); + if (!in_array($json_value, $unique)) { + $unique[] = $json_value; + $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 _html5_notif_timeout ($value) { + $value = intval($value); + $this->data['html5_notif_timeout'] = $value >= 0 ? $value : 0; + } + + 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'; + } +} diff --git a/app/models/Days.php b/app/Models/Days.php index a859cbace..2d770c30b 100644 --- a/app/models/Days.php +++ b/app/Models/Days.php @@ -1,6 +1,6 @@ <?php -class Days { +class FreshRSS_Days { const TODAY = 0; const YESTERDAY = 1; const BEFORE_YESTERDAY = 2; diff --git a/app/Models/Entry.php b/app/Models/Entry.php new file mode 100644 index 000000000..9d7dd5dc4 --- /dev/null +++ b/app/Models/Entry.php @@ -0,0 +1,192 @@ +<?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; + const STATE_STRICT = 16; + + 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), + ); + } +} diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php new file mode 100644 index 000000000..c1f87ee34 --- /dev/null +++ b/app/Models/EntryDAO.php @@ -0,0 +1,566 @@ +<?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 ? + FreshRSS_EntryDAO::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 || $entry->isFavorite())) { + $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('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('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('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_STRICT) { + $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; + } +} diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php new file mode 100644 index 000000000..9dc395c3c --- /dev/null +++ b/app/Models/EntryDAOSQLite.php @@ -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('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('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'); + } +} diff --git a/app/Models/Factory.php b/app/Models/Factory.php new file mode 100644 index 000000000..08569b2e2 --- /dev/null +++ b/app/Models/Factory.php @@ -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(); + } + } + +} diff --git a/app/Models/Feed.php b/app/Models/Feed.php new file mode 100644 index 000000000..2a5ea45ac --- /dev/null +++ b/app/Models/Feed.php @@ -0,0 +1,331 @@ +<?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 static function example() { + $f = new FreshRSS_Feed('http://example.net/', false); + $f->faviconPrepare(); + return $f; + } + + public function id() { + return $this->id; + } + + public function hash() { + if ($this->hash === null) { + $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 lazyload="" postpone="" src="' . $elink . '" alt="" />'; + } elseif (strpos($mime, 'audio/') === 0) { + $content .= '<br /><audio lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />'; + } elseif (strpos($mime, 'video/') === 0) { + $content .= '<br /><video lazyload="" postpone="" preload="none" src="' . $elink . '" controls="controls" />'; + } + } + } + + $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); + } +} diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php new file mode 100644 index 000000000..b89ae2045 --- /dev/null +++ b/app/Models/FeedDAO.php @@ -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_STR); + $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + + if ($stm && $stm->execute()) { + return $stm->rowCount(); + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::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; + } +} diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php new file mode 100644 index 000000000..0d1872389 --- /dev/null +++ b/app/Models/FeedDAOSQLite.php @@ -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; + } + } + +} diff --git a/app/Models/Log.php b/app/Models/Log.php new file mode 100644 index 000000000..d2794458b --- /dev/null +++ b/app/Models/Log.php @@ -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; + } +} diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php new file mode 100644 index 000000000..d1e515200 --- /dev/null +++ b/app/Models/LogDAO.php @@ -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', ''); + } +} diff --git a/app/Models/Share.php b/app/Models/Share.php new file mode 100644 index 000000000..b146db722 --- /dev/null +++ b/app/Models/Share.php @@ -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; + } + +} diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php new file mode 100644 index 000000000..40505ab3e --- /dev/null +++ b/app/Models/StatsDAO.php @@ -0,0 +1,404 @@ +<?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 $this->initStatsArray(-self::ENTRY_COUNT_PERIOD, -1); + } + + /** + * Calculates the number of article per hour of the day per feed + * + * @param integer $feed id + * @return string + */ + public function calculateEntryRepartitionPerFeedPerHour($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('%H', $feed); + } + + /** + * Calculates the number of article per day of week per feed + * + * @param integer $feed id + * @return string + */ + public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('%w', $feed); + } + + /** + * Calculates the number of article per month per feed + * + * @param integer $feed + * @return string + */ + public function calculateEntryRepartitionPerFeedPerMonth($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('%m', $feed); + } + + /** + * Calculates the number of article per period per feed + * + * @param string $period format string to use for grouping + * @param integer $feed id + * @return string + */ + protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) { + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } else { + $restrict = ''; + } + $sql = <<<SQL +SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period +, COUNT(1) AS count +FROM {$this->prefix}entry AS e +{$restrict} +GROUP BY period +ORDER BY period ASC +SQL; + + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_NAMED); + + foreach ($res as $value) { + $repartition[(int) $value['period']] = (int) $value['count']; + } + + return $this->convertToSerie($repartition); + } + + /** + * Calculates the average number of article per hour per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerHour($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(1/24, $feed); + } + + /** + * Calculates the average number of article per day of week per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerDayOfWeek($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(7, $feed); + } + + /** + * Calculates the average number of article per month per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerMonth($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(30, $feed); + } + + /** + * Calculates the average number of article per feed + * + * @param float $period number used to divide the number of day in the period + * @param integer $feed id + * @return integer + */ + protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) { + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } else { + $restrict = ''; + } + $sql = <<<SQL +SELECT COUNT(1) AS count +, MIN(date) AS date_min +, MAX(date) AS date_max +FROM {$this->prefix}entry AS e +{$restrict} +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetch(PDO::FETCH_NAMED); + $date_min = new \DateTime(); + $date_min->setTimestamp($res['date_min']); + $date_max = new \DateTime(); + $date_max->setTimestamp($res['date_max']); + $interval = $date_max->diff($date_min, true); + $interval_in_days = $interval->format('%a'); + if ($interval_in_days <= 0) { + // Surely only one article. + // We will return count / (period/period) == count. + $interval_in_days = $period; + } + + return round($res['count'] / ($interval_in_days / $period), 2); + } + + /** + * Initialize an array for statistics depending on a range + * + * @param integer $min + * @param integer $max + * @return array + */ + protected function initStatsArray($min, $max) { + return array_map(function () { + return 0; + }, array_flip(range($min, $max))); + } + + /** + * Calculates feed count per category. + * Returns the result as a JSON 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.id) as id +, MAX(f.name) AS name +, MAX(date) AS last_date +, COUNT(*) AS nb_articles +FROM {$this->prefix}feed AS f, +{$this->prefix}entry AS e +WHERE f.id = e.id_feed +GROUP BY f.id +ORDER BY name +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + return $stm->fetchAll(PDO::FETCH_ASSOC); + } + + protected function convertToSerie($data) { + $serie = array(); + + foreach ($data as $key => $value) { + $serie[] = array($key, $value); + } + + return 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); + } + + /** + * Gets days ready for graphs + * + * @return string + */ + public function getDays() { + return $this->convertToTranslatedJson(array( + 'sun', + 'mon', + 'tue', + 'wed', + 'thu', + 'fri', + 'sat', + )); + } + + /** + * Gets months ready for graphs + * + * @return string + */ + public function getMonths() { + return $this->convertToTranslatedJson(array( + 'jan', + 'feb', + 'mar', + 'apr', + 'may', + 'jun', + 'jul', + 'aug', + 'sep', + 'oct', + 'nov', + 'dec', + )); + } + + /** + * Translates array content and encode it as JSON + * + * @param array $data + * @return string + */ + private function convertToTranslatedJson($data = array()) { + $translated = array_map(function ($a) { + return Minz_Translate::t($a); + }, $data); + + return json_encode($translated); + } + +} diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php new file mode 100644 index 000000000..3b1256de1 --- /dev/null +++ b/app/Models/StatsDAOSQLite.php @@ -0,0 +1,64 @@ +<?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); + } + + protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) { + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } else { + $restrict = ''; + } + $sql = <<<SQL +SELECT strftime('{$period}', e.date, 'unixepoch') AS period +, COUNT(1) AS count +FROM {$this->prefix}entry AS e +{$restrict} +GROUP BY period +ORDER BY period ASC +SQL; + + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_NAMED); + + $repartition = array(); + foreach ($res as $value) { + $repartition[(int) $value['period']] = (int) $value['count']; + } + + return $this->convertToSerie($repartition); + } + +} diff --git a/app/Models/Themes.php b/app/Models/Themes.php new file mode 100644 index 000000000..68fc17a2b --- /dev/null +++ b/app/Models/Themes.php @@ -0,0 +1,121 @@ +<?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' => '★', + 'stats' => '%', + 'tag' => '⚐', + 'up' => '△', + 'view-normal' => '☰', + 'view-global' => '☷', + 'view-reader' => '☕', + ); + if (!isset($alts[$name])) { + return ''; + } + + $url = $name . '.svg'; + $url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) : + (self::$defaultIconsUrl . $url); + + return $urlOnly ? Minz_Url::display($url) : + '<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />'; + } +} + +function _i($icon, $url_only = false) { + return FreshRSS_Themes::icon($icon, $url_only); +} diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php new file mode 100644 index 000000000..9f64fb4a7 --- /dev/null +++ b/app/Models/UserDAO.php @@ -0,0 +1,56 @@ +<?php + +class FreshRSS_UserDAO extends Minz_ModelPdo { + public function createUser($username) { + $db = Minz_Configuration::dataBase(); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + + $userPDO = new Minz_ModelPdo($username); + + $ok = false; + if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL + $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category')); + $stm = $userPDO->bd->prepare($sql); + $ok = $stm && $stm->execute(); + } else { //E.g. SQLite + global $SQL_CREATE_TABLES; + if (is_array($SQL_CREATE_TABLES)) { + $ok = true; + foreach ($SQL_CREATE_TABLES as $instruction) { + $sql = sprintf($instruction, '', Minz_Translate::t('default_category')); + $stm = $userPDO->bd->prepare($sql); + $ok &= ($stm && $stm->execute()); + } + } + } + + if ($ok) { + return true; + } else { + $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + + public function deleteUser($username) { + $db = Minz_Configuration::dataBase(); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); + + if ($db['type'] === 'sqlite') { + return unlink(DATA_PATH . '/' . $username . '.sqlite'); + } else { + $userPDO = new Minz_ModelPdo($username); + + $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); + $stm = $userPDO->bd->prepare($sql); + if ($stm && $stm->execute()) { + return true; + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } + } + } +} diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php new file mode 100644 index 000000000..16cb3a3b8 --- /dev/null +++ b/app/SQL/install.sql.mysql.php @@ -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;'); diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php new file mode 100644 index 000000000..7988ada04 --- /dev/null +++ b/app/SQL/install.sql.sqlite.php @@ -0,0 +1,59 @@ +<?php +global $SQL_CREATE_TABLES; +$SQL_CREATE_TABLES = array( +'CREATE TABLE IF NOT EXISTS `%1$scategory` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `name` varchar(255) NOT NULL, + UNIQUE (`name`) +);', + +'CREATE TABLE IF NOT EXISTS `%1$sfeed` ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `url` varchar(511) NOT NULL, + `%1$scategory` SMALLINT DEFAULT 0, + `name` varchar(255) NOT NULL, + `website` varchar(255), + `description` text, + `lastUpdate` int(11) DEFAULT 0, + `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"'); diff --git a/app/actualize_script.php b/app/actualize_script.php new file mode 100755 index 000000000..4c306b8da --- /dev/null +++ b/app/actualize_script.php @@ -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(); diff --git a/app/configuration/.gitignore b/app/configuration/.gitignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/app/configuration/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/app/controllers/configureController.php b/app/controllers/configureController.php deleted file mode 100755 index 380adcd1d..000000000 --- a/app/controllers/configureController.php +++ /dev/null @@ -1,343 +0,0 @@ -<?php - -class configureController extends ActionController { - public function firstAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { - Error::error ( - 403, - array ('error' => array (Translate::t ('access_denied'))) - ); - } - - $catDAO = new CategoryDAO (); - $catDAO->checkDefault (); - } - - public function categorizeAction () { - $feedDAO = new FeedDAO (); - $catDAO = new CategoryDAO (); - $catDAO->checkDefault (); - $defaultCategory = $catDAO->getDefault (); - $defaultId = $defaultCategory->id (); - - if (Request::isPost ()) { - $cats = Request::param ('categories', array ()); - $ids = Request::param ('ids', array ()); - $newCat = trim (Request::param ('new_category', '')); - - foreach ($cats as $key => $name) { - if (strlen ($name) > 0) { - $cat = new Category ($name); - $values = array ( - 'name' => $cat->name (), - 'color' => $cat->color () - ); - $catDAO->updateCategory ($ids[$key], $values); - } elseif ($ids[$key] != $defaultId) { - $feedDAO->changeCategory ($ids[$key], $defaultId); - $catDAO->deleteCategory ($ids[$key]); - } - } - - if ($newCat != '') { - $cat = new Category ($newCat); - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), - 'color' => $cat->color () - ); - - if ($catDAO->searchByName ($newCat) == false) { - $catDAO->addCategory ($values); - } - } - - // notif - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('categories_updated') - ); - Session::_param ('notification', $notif); - - Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true); - } - - $this->view->categories = $catDAO->listCategories (false); - $this->view->defaultCategory = $catDAO->getDefault (); - - View::prependTitle (Translate::t ('categories_management') . ' - '); - } - - public function feedAction () { - $catDAO = new CategoryDAO (); - $this->view->categories = $catDAO->listCategories (false); - - $feedDAO = new FeedDAO (); - $this->view->feeds = $feedDAO->listFeeds (); - - $id = 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 = $feedDAO->searchById ($id); - - if (!$this->view->flux) { - Error::error ( - 404, - array ('error' => array (Translate::t ('page_not_found'))) - ); - } else { - $catDAO = new CategoryDAO (); - $this->view->categories = $catDAO->listCategories (false); - - if (Request::isPost () && $this->view->flux) { - $name = Request::param ('name', ''); - $hist = Request::param ('keep_history', 'no'); - $cat = Request::param ('category', 0); - $path = Request::param ('path_entries', ''); - $priority = Request::param ('priority', 0); - $user = Request::param ('http_user', ''); - $pass = Request::param ('http_pass', ''); - - $keep_history = false; - if ($hist == 'yes') { - $keep_history = true; - } - - $httpAuth = ''; - if ($user != '' || $pass != '') { - $httpAuth = $user . ':' . $pass; - } - - $values = array ( - 'name' => $name, - 'category' => $cat, - 'pathEntries' => $path, - 'priority' => $priority, - 'httpAuth' => $httpAuth, - 'keep_history' => $keep_history - ); - - if ($feedDAO->updateFeed ($id, $values)) { - $this->view->flux->_category ($cat); - - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('feed_updated') - ); - } else { - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('error_occurred_update') - ); - } - - Session::_param ('notification', $notif); - Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true); - } - - View::prependTitle (Translate::t ('rss_feed_management') . ' - ' . $this->view->flux->name () . ' - '); - } - } else { - View::prependTitle (Translate::t ('rss_feed_management') . ' - '); - } - } - - public function displayAction () { - if (Request::isPost ()) { - $current_token = $this->view->conf->token (); - - $language = Request::param ('language', 'en'); - $nb = Request::param ('posts_per_page', 10); - $mode = Request::param ('view_mode', 'normal'); - $view = Request::param ('default_view', 'all'); - $auto_load_more = Request::param ('auto_load_more', 'no'); - $display = Request::param ('display_posts', 'no'); - $onread_jump_next = Request::param ('onread_jump_next', 'yes'); - $lazyload = Request::param ('lazyload', 'no'); - $sort = Request::param ('sort_order', 'low_to_high'); - $old = Request::param ('old_entries', 3); - $mail = Request::param ('mail_login', false); - $anon = Request::param ('anon_access', 'no'); - $token = Request::param ('token', $current_token); - $openArticle = Request::param ('mark_open_article', 'no'); - $openSite = Request::param ('mark_open_site', 'no'); - $scroll = Request::param ('mark_scroll', 'no'); - $urlShaarli = Request::param ('shaarli', ''); - $theme = Request::param ('theme', 'default'); - - $this->view->conf->_language ($language); - $this->view->conf->_postsPerPage (intval ($nb)); - $this->view->conf->_viewMode ($mode); - $this->view->conf->_defaultView ($view); - $this->view->conf->_autoLoadMore ($auto_load_more); - $this->view->conf->_displayPosts ($display); - $this->view->conf->_onread_jump_next ($onread_jump_next); - $this->view->conf->_lazyload ($lazyload); - $this->view->conf->_sortOrder ($sort); - $this->view->conf->_oldEntries ($old); - $this->view->conf->_mailLogin ($mail); - $this->view->conf->_anonAccess ($anon); - $this->view->conf->_token ($token); - $this->view->conf->_markWhen (array ( - 'article' => $openArticle, - 'site' => $openSite, - 'scroll' => $scroll, - )); - $this->view->conf->_urlShaarli ($urlShaarli); - $this->view->conf->_theme ($theme); - - $values = array ( - 'language' => $this->view->conf->language (), - 'posts_per_page' => $this->view->conf->postsPerPage (), - 'view_mode' => $this->view->conf->viewMode (), - 'default_view' => $this->view->conf->defaultView (), - 'auto_load_more' => $this->view->conf->autoLoadMore (), - 'display_posts' => $this->view->conf->displayPosts (), - 'onread_jump_next' => $this->view->conf->onread_jump_next (), - 'lazyload' => $this->view->conf->lazyload (), - 'sort_order' => $this->view->conf->sortOrder (), - 'old_entries' => $this->view->conf->oldEntries (), - 'mail_login' => $this->view->conf->mailLogin (), - 'anon_access' => $this->view->conf->anonAccess (), - 'token' => $this->view->conf->token (), - 'mark_when' => $this->view->conf->markWhen (), - 'url_shaarli' => $this->view->conf->urlShaarli (), - 'theme' => $this->view->conf->theme () - ); - - $confDAO = new RSSConfigurationDAO (); - $confDAO->update ($values); - Session::_param ('conf', $this->view->conf); - Session::_param ('mail', $this->view->conf->mailLogin ()); - - Session::_param ('language', $this->view->conf->language ()); - Translate::reset (); - - // notif - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('configuration_updated') - ); - Session::_param ('notification', $notif); - - Request::forward (array ('c' => 'configure', 'a' => 'display'), true); - } - - $this->view->themes = RSSThemes::get(); - - View::prependTitle (Translate::t ('general_and_reading_management') . ' - '); - } - - public function importExportAction () { - $catDAO = new CategoryDAO (); - $this->view->categories = $catDAO->listCategories (); - - $this->view->req = Request::param ('q'); - - if ($this->view->req == 'export') { - View::_title ('freshrss_feeds.opml'); - - $this->view->_useLayout (false); - header('Content-Type: text/xml; charset=utf-8'); - header('Content-disposition: attachment; filename=freshrss_feeds.opml'); - - $feedDAO = new FeedDAO (); - $catDAO = new CategoryDAO (); - - $list = array (); - foreach ($catDAO->listCategories () as $key => $cat) { - $list[$key]['name'] = $cat->name (); - $list[$key]['feeds'] = $feedDAO->listByCategory ($cat->id ()); - } - - $this->view->categories = $list; - } elseif ($this->view->req == 'import' && Request::isPost ()) { - if ($_FILES['file']['error'] == 0) { - // on parse le fichier OPML pour récupérer les catégories et les flux associés - try { - list ($categories, $feeds) = opml_import ( - file_get_contents ($_FILES['file']['tmp_name']) - ); - - // On redirige vers le controller feed qui va se charger d'insérer les flux en BDD - // les flux sont mis au préalable dans des variables de Request - Request::_param ('q', 'null'); - Request::_param ('categories', $categories); - Request::_param ('feeds', $feeds); - Request::forward (array ('c' => 'feed', 'a' => 'massiveImport')); - } catch (OpmlException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('bad_opml_file') - ); - Session::_param ('notification', $notif); - - Request::forward (array ( - 'c' => 'configure', - 'a' => 'importExport' - ), true); - } - } - } - - $feedDAO = new FeedDAO (); - $this->view->feeds = $feedDAO->listFeeds (); - - // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste - $this->view->flux = false; - - View::prependTitle (Translate::t ('import_export_opml') . ' - '); - } - - public function shortcutAction () { - $list_keys = array ('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', - 'escape', 'f', 'g', 'h', 'i', 'insert', 'j', 'k', 'l', 'left', - '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; - $list_names = array ('mark_read', 'mark_favorite', 'go_website', 'next_entry', - 'prev_entry', 'next_page', 'prev_page'); - - if (Request::isPost ()) { - $shortcuts = Request::param ('shortcuts'); - $shortcuts_ok = array (); - - foreach ($shortcuts as $key => $value) { - if (in_array ($key, $list_names) - && in_array ($value, $list_keys)) { - $shortcuts_ok[$key] = $value; - } - } - - $this->view->conf->_shortcuts ($shortcuts_ok); - - $values = array ( - 'shortcuts' => $this->view->conf->shortcuts () - ); - - $confDAO = new RSSConfigurationDAO (); - $confDAO->update ($values); - Session::_param ('conf', $this->view->conf); - - // notif - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('shortcuts_updated') - ); - Session::_param ('notification', $notif); - - Request::forward (array ('c' => 'configure', 'a' => 'shortcut'), true); - } - - View::prependTitle (Translate::t ('shortcuts_management') . ' - '); - } -} diff --git a/app/controllers/entryController.php b/app/controllers/entryController.php deleted file mode 100755 index 4c9eb9d1b..000000000 --- a/app/controllers/entryController.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php - -class entryController extends ActionController { - public function firstAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { - Error::error ( - 403, - array ('error' => array (Translate::t ('access_denied'))) - ); - } - - $this->params = array (); - $this->redirect = false; - $ajax = Request::param ('ajax'); - if ($ajax) { - $this->view->_useLayout (false); - } - } - public function lastAction () { - $ajax = Request::param ('ajax'); - if (!$ajax && $this->redirect) { - Request::forward (array ( - 'c' => 'index', - 'a' => 'index', - 'params' => $this->params - ), true); - } else { - Request::_param ('ajax'); - } - } - - public function readAction () { - $this->redirect = true; - - $id = Request::param ('id'); - $is_read = Request::param ('is_read'); - $get = Request::param ('get'); - $nextGet = Request::param ('nextGet', $get); - $dateMax = Request::param ('dateMax', 0); - - $is_read = !!$is_read; - - $entryDAO = new EntryDAO (); - if ($id == false) { - if (!$get) { - $entryDAO->markReadEntries ($is_read, $dateMax); - } else { - $typeGet = $get[0]; - $get = substr ($get, 2); - - if ($typeGet == 'c') { - $entryDAO->markReadCat ($get, $is_read, $dateMax); - $this->params = array ('get' => $nextGet); - } elseif ($typeGet == 'f') { - $entryDAO->markReadFeed ($get, $is_read, $dateMax); - $this->params = array ('get' => $nextGet); - } - } - - // notif - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('feeds_marked_read') - ); - Session::_param ('notification', $notif); - } else { - $entryDAO->updateEntry ($id, array ('is_read' => $is_read)); - } - } - - public function bookmarkAction () { - $this->redirect = true; - - $id = Request::param ('id'); - $is_fav = Request::param ('is_favorite'); - - if ($is_fav) { - $is_fav = true; - } else { - $is_fav = false; - } - - $entryDAO = new EntryDAO (); - if ($id != false) { - $entry = $entryDAO->searchById ($id); - - if ($entry != false) { - $values = array ( - 'is_favorite' => $is_fav, - ); - - $entryDAO->updateEntry ($entry->id (), $values); - } - } - } - - public function optimizeAction() { - // 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 = new EntryDAO(); - $entryDAO->optimizeTable(); - - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('optimization_complete') - ); - Session::_param ('notification', $notif); - - Request::forward(array( - 'c' => 'configure', - 'a' => 'display' - ), true); - } -} diff --git a/app/controllers/errorController.php b/app/controllers/errorController.php deleted file mode 100644 index 092609280..000000000 --- a/app/controllers/errorController.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php - -class ErrorController extends ActionController { - public function indexAction () { - switch (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 = Request::param ('logs'); - - View::prependTitle ($this->view->code . ' - '); - } -} diff --git a/app/controllers/feedController.php b/app/controllers/feedController.php deleted file mode 100755 index 1131c3a7a..000000000 --- a/app/controllers/feedController.php +++ /dev/null @@ -1,351 +0,0 @@ -<?php - -class feedController extends ActionController { - public function firstAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { - Error::error ( - 403, - array ('error' => array (Translate::t ('access_denied'))) - ); - } - - $this->catDAO = new CategoryDAO (); - $this->catDAO->checkDefault (); - } - - public function addAction () { - if (Request::isPost ()) { - $url = Request::param ('url_rss'); - $cat = Request::param ('category', false); - if ($cat === false) { - $def_cat = $this->catDAO->getDefault (); - $cat = $def_cat->id (); - } - - $user = Request::param ('username'); - $pass = Request::param ('password'); - $params = array (); - - try { - $feed = new Feed ($url); - $feed->_category ($cat); - - $httpAuth = ''; - if ($user != '' || $pass != '') { - $httpAuth = $user . ':' . $pass; - } - $feed->_httpAuth ($httpAuth); - - $feed->load (); - - $feedDAO = new FeedDAO (); - $values = array ( - 'id' => $feed->id (), - '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' => Translate::t ('already_subscribed', $feed->name ()) - ); - Session::_param ('notification', $notif); - } elseif (!$feedDAO->addFeed ($values)) { - // problème au niveau de la base de données - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('feed_not_added', $feed->name ()) - ); - Session::_param ('notification', $notif); - } else { - $entryDAO = new EntryDAO (); - $entries = $feed->entries (); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); - - // on ajoute les articles en masse sans vérification - foreach ($entries as $entry) { - if ($entry->date (true) >= $date_min || - $feed->keepHistory ()) { - $values = $entry->toArray (); - $entryDAO->addEntry ($values); - } - } - - // ok, ajout terminé - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('feed_added', $feed->name ()) - ); - Session::_param ('notification', $notif); - - // permet de rediriger vers la page de conf du flux - $params['id'] = $feed->id (); - } - } catch (BadUrlException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('invalid_url', $url) - ); - Session::_param ('notification', $notif); - } catch (FeedException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('internal_problem_feed') - ); - Session::_param ('notification', $notif); - } catch (FileNotExistException $e) { - // Répertoire de cache n'existe pas - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('internal_problem_feed') - ); - Session::_param ('notification', $notif); - } - - Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true); - } - } - - public function actualizeAction () { - $feedDAO = new FeedDAO (); - $entryDAO = new EntryDAO (); - - $id = Request::param ('id'); - $force = 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 (); - } - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); - - $i = 0; - $flux_update = 0; - foreach ($feeds as $feed) { - try { - $feed->load (); - $entries = $feed->entries (); - - // ajout des articles en masse sans se soucier des erreurs - // On ne vérifie pas que l'article n'est pas déjà en BDD - // car demanderait plus de ressources - // La BDD refusera l'ajout de son côté car l'id doit être - // unique - foreach ($entries as $entry) { - if ($entry->date (true) >= $date_min || - $feed->keepHistory ()) { - $values = $entry->toArray (); - $entryDAO->addEntry ($values); - } - } - - // on indique que le flux vient d'être mis à jour en BDD - $feedDAO->updateLastUpdate ($feed->id ()); - $flux_update++; - } catch (FeedException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $feedDAO->isInError ($feed->id ()); - } - - // 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; - } - } - - $entryDAO->cleanOldEntries ($nb_month_old); - - $url = array (); - if ($flux_update == 1) { - // on a mis un seul flux à jour - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('feed_actualized', $feed->name ()) - ); - } elseif ($flux_update > 1) { - // plusieurs flux on été mis à jour - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('n_feeds_actualized', $flux_update) - ); - } else { - // aucun flux n'a été mis à jour, oups - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('no_feed_actualized') - ); - } - - 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 (Request::param ('ajax', 0) == 0) { - Session::_param ('notification', $notif); - 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' => Translate::t ('feeds_actualized') - ); - Session::_param ('notification', $notif); - // et on désactive le layout car ne sert à rien - $this->view->_useLayout (false); - } - } - - public function massiveImportAction () { - $entryDAO = new EntryDAO (); - $feedDAO = new FeedDAO (); - - $categories = Request::param ('categories', array (), true); - $feeds = Request::param ('feeds', array (), true); - - // on ajoute les catégories en masse dans une fonction à part - $this->addCategories ($categories); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->oldEntries (); - $date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old); - - // la variable $error permet de savoir si une erreur est survenue - // Le but est de ne pas arrêter l'import même en cas d'erreur - // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais - // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées - $error = false; - $i = 0; - foreach ($feeds as $feed) { - try { - $feed->load (); - - $values = array ( - 'id' => $feed->id (), - 'url' => $feed->url (), - 'category' => $feed->category (), - 'name' => $feed->name (), - 'website' => $feed->website (), - 'description' => $feed->description (), - 'lastUpdate' => 0, - 'httpAuth' => $feed->httpAuth () - ); - - // ajout du flux que s'il n'est pas déjà en BDD - if (!$feedDAO->searchByUrl ($values['url'])) { - if (!$feedDAO->addFeed ($values)) { - $error = true; - } - } - } catch (FeedException $e) { - $error = true; - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - } - } - - if ($error) { - $res = Translate::t ('feeds_imported_with_errors'); - } else { - $res = Translate::t ('feeds_imported'); - } - - $notif = array ( - 'type' => 'good', - 'content' => $res - ); - Session::_param ('notification', $notif); - - // et on redirige vers la page import/export - Request::forward (array ( - 'c' => 'configure', - 'a' => 'importExport' - ), true); - } - - public function deleteAction () { - $type = Request::param ('type', 'feed'); - $id = Request::param ('id'); - - $feedDAO = new FeedDAO (); - if ($type == 'category') { - if ($feedDAO->deleteFeedByCategory ($id)) { - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('category_emptied') - ); - } else { - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('error_occured') - ); - } - } else { - if ($feedDAO->deleteFeed ($id)) { - $notif = array ( - 'type' => 'good', - 'content' => Translate::t ('feed_deleted') - ); - } else { - $notif = array ( - 'type' => 'bad', - 'content' => Translate::t ('error_occured') - ); - } - } - - Session::_param ('notification', $notif); - - if ($type == 'category') { - Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true); - } else { - Request::forward (array ('c' => 'configure', 'a' => 'feed'), true); - } - } - - private function addCategories ($categories) { - $catDAO = new CategoryDAO (); - - foreach ($categories as $cat) { - if (!$catDAO->searchByName ($cat->name ())) { - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), - 'color' => $cat->color () - ); - $catDAO->addCategory ($values); - } - } - } -} diff --git a/app/controllers/indexController.php b/app/controllers/indexController.php deleted file mode 100755 index d1c615d6d..000000000 --- a/app/controllers/indexController.php +++ /dev/null @@ -1,228 +0,0 @@ -<?php - -class indexController extends ActionController { - private $get = false; - private $nb_not_read = 0; - private $mode = 'all'; //TODO: Is this used? - - public function indexAction () { - $output = Request::param ('output'); - - if ($output == 'rss') { - $this->view->_useLayout (false); - } else { - View::appendScript (Url::display (array ('c' => 'javascript', 'a' => 'actualize'))); - - if(!$output) { - $output = $this->view->conf->viewMode(); - Request::_param ('output', $output); - } - - View::appendScript (Url::display ('/scripts/shortcut.js')); - View::appendScript (Url::display (array ('c' => 'javascript', 'a' => 'main'))); - View::appendScript (Url::display ('/scripts/endless_mode.js')); - - if ($output == 'global') { - View::appendScript (Url::display ('/scripts/global_view.js')); - } - } - - $nb_not_read = $this->view->nb_not_read; - if($nb_not_read > 0) { - View::appendTitle (' (' . $nb_not_read . ')'); - } - View::prependTitle (' - '); - - $entryDAO = new EntryDAO (); - $feedDAO = new FeedDAO (); - $catDAO = new CategoryDAO (); - - $this->view->cat_aside = $catDAO->listCategories (); - $this->view->nb_favorites = $entryDAO->countFavorites (); - $this->view->nb_total = $entryDAO->count (); - $this->view->currentName = ''; - - $this->view->get_c = ''; - $this->view->get_f = ''; - - $type = $this->getType (); - $error = $this->checkAndProcessType ($type); - if (!$error) { - // On récupère les différents éléments de filtrage - $this->view->state = $state = Request::param ('state', $this->view->conf->defaultView ()); - $filter = Request::param ('search', ''); - $this->view->order = $order = Request::param ('order', $this->view->conf->sortOrder ()); - $nb = Request::param ('nb', $this->view->conf->postsPerPage ()); - $first = Request::param ('next', ''); - - try { - // EntriesGetter permet de déporter la complexité du filtrage - $getter = new EntriesGetter ($type, $state, $filter, $order, $nb, $first); - $getter->execute (); - $entries = $getter->getPaginator (); - - // Si on a récupéré aucun article "non lus" - // on essaye de récupérer tous les articles - if ($state == 'not_read' && $entries->isEmpty ()) { - $this->view->state = 'all'; - $getter->_state ('all'); - $getter->execute (); - $entries = $getter->getPaginator (); - } - - $this->view->entryPaginator = $entries; - } catch(EntriesGetterException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE); - Error::error ( - 404, - array ('error' => array (Translate::t ('page_not_found'))) - ); - } - } else { - Error::error ( - 404, - array ('error' => array (Translate::t ('page_not_found'))) - ); - } - } - - /* - * Détermine le type d'article à récupérer : - * "tous", "favoris", "public", "catégorie" ou "flux" - */ - private function getType () { - $get = Request::param ('get', 'all'); - $typeGet = $get[0]; - $id = substr ($get, 2); - - $type = null; - if ($get == 'all' || $get == 'favoris' || $get == 'public') { - $type = array ( - 'type' => $get, - 'id' => $get - ); - } elseif ($typeGet == 'f' || $typeGet == 'c') { - $type = array ( - 'type' => $typeGet, - 'id' => $id - ); - } - - return $type; - } - /* - * Vérifie que la catégorie / flux sélectionné existe - * + Initialise correctement les variables de vue get_c et get_f - * + Initialise le titre - */ - private function checkAndProcessType ($type) { - if ($type['type'] == 'all') { - $this->view->currentName = Translate::t ('your_rss_feeds'); - View::prependTitle ($this->view->currentName); - $this->view->get_c = $type['type']; - return false; - } elseif ($type['type'] == 'favoris') { - $this->view->currentName = Translate::t ('your_favorites'); - View::prependTitle ($this->view->currentName); - $this->view->get_c = $type['type']; - return false; - } elseif ($type['type'] == 'public') { - $this->view->currentName = Translate::t ('public'); - View::prependTitle ($this->view->currentName); - $this->view->get_c = $type['type']; - return false; - } elseif ($type['type'] == 'c') { - $catDAO = new CategoryDAO (); - $cat = $catDAO->searchById ($type['id']); - if ($cat) { - $this->view->currentName = $cat->name (); - $nbnr = $cat->nbNotRead (); - View::prependTitle ($this->view->currentName . ($nbnr > 0 ? ' (' . $nbnr . ')' : '')); - $this->view->get_c = $type['id']; - return false; - } else { - return true; - } - } elseif ($type['type'] == 'f') { - $feedDAO = new FeedDAO (); - $feed = $feedDAO->searchById ($type['id']); - if ($feed) { - $this->view->currentName = $feed->name (); - $nbnr = $feed->nbNotRead (); - View::prependTitle ($this->view->currentName . ($nbnr > 0 ? ' (' . $nbnr . ')' : '')); - $this->view->get_f = $type['id']; - $this->view->get_c = $feed->category (); - return false; - } else { - return true; - } - } else { - return true; - } - } - - public function aboutAction () { - View::prependTitle (Translate::t ('about') . ' - '); - } - - public function logsAction () { - if (login_is_conf ($this->view->conf) && !is_logged ()) { - Error::error ( - 403, - array ('error' => array (Translate::t ('access_denied'))) - ); - } - - View::prependTitle (Translate::t ('logs') . ' - '); - - $logs = array(); - try { - $logDAO = new LogDAO (); - $logs = $logDAO->lister (); - $logs = array_reverse ($logs); - } catch(FileNotExistException $e) { - - } - - //gestion pagination - $page = Request::param ('page', 1); - $this->view->logsPaginator = new 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 = Request::param ('assertion'); - $params = 'assertion=' . $assert . '&audience=' . - urlencode (Url::display () . ':80'); - $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); - if ($res['status'] == 'okay' && $res['email'] == $this->view->conf->mailLogin ()) { - Session::_param ('mail', $res['email']); - } else { - $res = array (); - $res['status'] = 'failure'; - $res['reason'] = Translate::t ('invalid_login'); - } - - $this->view->res = json_encode ($res); - } - - public function logoutAction () { - $this->view->_useLayout (false); - Session::_param ('mail'); - } -} diff --git a/app/controllers/javascriptController.php b/app/controllers/javascriptController.php deleted file mode 100755 index 071cf65a4..000000000 --- a/app/controllers/javascriptController.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -class javascriptController extends ActionController { - public function firstAction () { - $this->view->_useLayout (false); - header('Content-type: text/javascript'); - } - - public function mainAction () {} - - public function actualizeAction () { - $feedDAO = new FeedDAO (); - $this->view->feeds = $feedDAO->listFeeds (); - } -} diff --git a/app/i18n/en.php b/app/i18n/en.php index 590030684..beba02c4d 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -3,24 +3,76 @@ return array ( // LAYOUT 'login' => 'Login', + 'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>', + 'login_with_persona' => 'Login with Persona', + 'login_persona_problem' => 'Connection problem with Persona?', 'logout' => 'Logout', 'search' => 'Search words or #tags', + 'search_short' => 'Search', 'configuration' => 'Configuration', - 'general_and_reading' => 'General and reading', + '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', + 'query_created' => 'Query "%s" has been created.', + 'no_query' => 'You haven’t created any user query yet.', + 'query_filter' => 'Filter applied:', + 'no_query_filter' => 'No filter', + 'query_deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.', 'about' => 'About', + 'stats' => 'Statistics', + 'stats_idle' => 'Idle feeds', + 'stats_main' => 'Main statistics', + 'stats_repartition' => 'Articles repartition', + 'stats_entry_per_hour' => 'Per hour', + 'stats_entry_per_day_of_week' => 'Per day of week', + 'stats_entry_per_month' => 'Per month', + + 'last_week' => 'Last week', + 'last_month' => 'Last month', + '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_opml' => 'Import / export (OPML)', + 'import_export' => 'Import / export', + 'bookmark' => 'Subscribe (FreshRSS bookmark)', 'subscription_management' => 'Subscriptions management', - 'all_feeds' => 'All (%d)', - 'favorite_feeds' => 'Favourites (%d)', + 'main_stream' => 'Main stream', + 'all_feeds' => 'All feeds', + 'favorite_feeds' => 'Favourites (%s)', 'not_read' => '%d unread', 'not_reads' => '%d unread', @@ -40,8 +92,13 @@ return array ( '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_adaptive' => 'Adjust showing', + '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', @@ -58,7 +115,7 @@ return array ( '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' => 'An error occurred during update', + 'error_occurred_update' => 'Nothing was changed', 'default_category' => 'Uncategorized', 'categories_updated' => 'Categories have been updated', @@ -66,26 +123,31 @@ return array ( 'feed_updated' => 'Feed has been updated', 'rss_feed_management' => 'RSS feeds management', 'configuration_updated' => 'Configuration has been updated', - 'general_and_reading_management'=> 'General and reading management', + 'sharing_management' => 'Sharing options management', 'bad_opml_file' => 'Your OPML file is invalid', 'shortcuts_updated' => 'Shortcuts have been updated', - 'shortcuts_management' => 'Shortcuts management', + 'shortcuts_navigation' => 'Navigation', + 'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.', + 'shortcuts_article_action' => 'Article actions', + 'shortcuts_other_action' => 'Other actions', 'feeds_marked_read' => 'Feeds have been marked as read', 'updated' => 'Modifications have been updated', 'already_subscribed' => 'You have already subscribed to <em>%s</em>', 'feed_added' => 'RSS feed <em>%s</em> has been added', 'feed_not_added' => '<em>%s</em> could not be added', - 'internal_problem_feed' => 'An internal problem occurred, RSS feed 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', - 'feeds_imported_with_errors' => 'Feeds have been imported but errors occurred', - 'feeds_imported' => 'Feeds have been imported', + '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', @@ -94,10 +156,13 @@ return array ( 'public' => 'Public', 'invalid_login' => 'Login is invalid', + 'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into.', + // VIEWS 'save' => 'Save', 'delete' => 'Delete', 'cancel' => 'Cancel', + 'submit' => 'Submit', '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>.', @@ -111,28 +176,54 @@ return array ( 'javascript_for_shortcuts' => 'JavaScript must be enabled in order to use shortcuts', 'javascript_should_be_activated'=> 'JavaScript must be enabled', 'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read', - 'see_on_website' => 'See article on its original website', + 'see_on_website' => 'See on original website', 'next_article' => 'Skip to the next article', - 'shift_for_last' => '+ <code>shift</code> to skip to the last article of page', + 'last_article' => 'Skip to the last article', 'previous_article' => 'Skip to the previous article', - 'shift_for_first' => '+ <code>shift</code> to skip to the first article of page', + 'first_article' => 'Skip to the first article', 'next_page' => 'Skip to the next page', 'previous_page' => 'Skip to the previous page', - - 'file_to_import' => 'File to import', + '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', + 'user_filter' => 'Access user filters', + 'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.', + 'help' => 'Display documentation', + + 'file_to_import' => 'File to import<br />(OPML, Json or Zip)', + 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)', 'import' => 'Import', + 'file_cannot_be_uploaded' => 'File cannot be uploaded!', + 'zip_error' => 'An error occured during Zip import.', + 'no_zip_extension' => 'Zip extension is not present on your server.', 'export' => 'Export', + 'export_opml' => 'Export list of feeds (OPML)', + 'export_starred' => 'Export your favourites', + 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', + 'starred_list' => 'List of favourite articles', + 'feed_list' => 'List of %s articles', 'or' => 'or', 'informations' => 'Information', + 'damn' => 'Damn!', + 'ok' => 'Ok!', + 'attention' => 'Be careful!', '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', - 'number_articles' => 'Number of articles', - 'keep_history' => 'Keep history?', + 'articles' => 'articles', + 'number_articles' => '%d 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 principal stream', + 'show_in_all_flux' => 'Show in main stream', 'yes' => 'Yes', 'no' => 'No', 'css_path_on_website' => 'Articles CSS path on original website', @@ -141,40 +232,98 @@ return array ( 'http_username' => 'HTTP username', 'http_password' => 'HTTP password', 'blank_to_disable' => 'Leave blank to disable', + 'share_name' => 'Share name to display', + 'share_url' => 'Share URL to use', 'not_yet_implemented' => 'Not yet implemented', 'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds', 'no_selected_feed' => 'No feed selected.', - 'think_to_add' => 'Think to add RSS feeds!', + 'think_to_add' => 'You may add some feeds.', + + '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', + 'username_admin' => 'Administrator username', + 'password' => 'Password', + 'create' => 'Create', + 'user_created' => 'User %s has been created', + 'user_deleted' => 'User %s has been deleted', - 'general_configuration' => 'General configuration', 'language' => 'Language', - 'delete_articles_every' => 'Remove articles every', 'month' => 'months', - 'persona_connection_email' => 'Login mail address (use <a href="https://persona.org/">Persona</a>)', - 'allow_anonymous' => 'Allow anonymous reading', - 'auth_token' => 'Authentication token', - 'explain_token' => 'Allows to access RSS output without authentication.<br />%s?token=%s', - 'reading_configuration' => 'Reading configuration', + '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', + 'number_divided_when_reader' => 'Divided by 2 in the reading view.', 'default_view' => 'Default view', + 'articles_to_display' => 'Articles to display', '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 marked as read,', - 'jump_next' => 'jump to next unread sibling', + 'display_categories_unfolded' => 'Show categories folded by default', + 'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)', + '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', + 'html5_notif_timeout' => 'HTML5 notification timeout', + 'seconds_(0_means_no_timeout)' => 'seconds (0 means no timeout)', 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', - 'auto_read_when' => 'Mark as read when', - 'article_selected' => 'article is selected', - 'article_open_on_website' => 'article is opened on its original website', - 'scroll' => 'page scrolls', + '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' => 'while scrolling', + '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 mail', - 'on_shaarli' => 'On your Shaarli', + 'by_email' => 'By email', 'optimize_bdd' => 'Optimize database', - 'optimize_todo_sometimes' => 'To do occasionally to reduce size of 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', @@ -183,18 +332,20 @@ return array ( 'by' => 'by', 'load_more' => 'Load more articles', - 'nothing_to_load' => 'There is no 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' => 'No feed to show.', + 'no_feed_to_display' => 'There is no article to show.', 'about_freshrss' => 'About FreshRSS', 'project_website' => 'Project website', @@ -204,31 +355,55 @@ return array ( '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://rsslounge.aditu.de/">RSSLounge</a>, <a href="http://tt-rss.org/redmine/projects/tt-rss/wiki">TinyTinyRSS</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. Objective is to provide a serious alternative to Google Reader.', + '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.', + '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 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' => 'Forbidden access', - 'forbidden_access_description' => 'Access is password protected, please <a class="signin" href="#">sign in</a> to read your feeds.', + 'forbidden_access' => 'Access is forbidden!', + 'login_required' => 'Login required:', - 'confirm_action' => 'Are you sure you want perform this action? It cannot be cancelled!', + 'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!', + 'confirm_action_feed_cat' => 'Are you sure you want to perform this action? You may lost related favorites and user queries. It cannot be cancelled!', + 'notif_title_new_articles' => 'FreshRSS: new articles!', + 'notif_body_new_articles' => 'There are \d new articles to read on FreshRSS.', // DATE - 'january' => 'january', - 'february' => 'february', - 'march' => 'march', - 'april' => 'april', - 'may' => 'may', - 'june' => 'june', - 'july' => 'july', - 'august' => 'august', - 'september' => 'september', - 'october' => 'october', - 'november' => 'november', - 'december' => 'december', + 'january' => 'January', + 'february' => 'February', + 'march' => 'March', + 'april' => 'April', + 'may' => 'May', + 'june' => 'June', + 'july' => 'July', + 'august' => 'August', + 'september' => 'September', + 'october' => 'October', + 'november' => 'November', + 'december' => 'December', + 'january' => 'Jan', + 'february' => 'Feb', + 'march' => 'Mar', + 'april' => 'Apr', + 'may' => 'May', + 'june' => 'Jun', + 'july' => 'Jul', + 'august' => 'Aug', + 'september' => 'Sep', + 'october' => 'Oct', + 'november' => 'Nov', + 'december' => 'Dec', + 'sun' => 'Sun', + 'mon' => 'Mon', + 'tue' => 'Tue', + 'wed' => 'Wed', + 'thu' => 'Thu', + 'fri' => 'Fri', + 'sat' => 'Sat', // special format for date() function 'Jan' => '\J\a\n\u\a\r\y', 'Feb' => '\F\e\b\r\u\a\r\y', @@ -243,61 +418,37 @@ return array ( '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 dS Y', - 'format_date_hour' => '%s dS Y \a\t H\.i', - - // INSTALLATION - 'freshrss_installation' => 'Installation - FreshRSS', - 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation - step %d', - 'steps' => 'Steps', - 'checks' => 'Checks', - 'bdd_configuration' => 'Database configuration', - '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 and it’s compatible with FreshRSS', - 'php_is_nok' => 'Your PHP version is %s. You must have at least version %s', - 'minz_is_ok' => 'You have Minz framework', - 'minz_is_nok' => 'You haven’t 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 haven’t cURL', - 'pdomysql_is_ok' => 'You have PDO and its driver for MySQL', - 'pdomysql_is_nok' => 'You haven’t PDO or its driver for MySQL', - 'dom_is_ok' => 'You have the necessary to browse the DOM', - 'dom_is_nok' => 'You haven’t the necessary to browse the DOM (php-xml package can be useful)', - 'cache_is_ok' => 'Permissions on cache directory are good', - 'log_is_ok' => 'Permissions on logs directory are good', - 'conf_is_ok' => 'Permissions on configuration directory are good', - 'data_is_ok' => 'Permissions on data 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', - 'username' => 'Username', - 'password' => 'Password', - 'bdd' => 'Database', - 'prefix' => 'Table prefix', - - 'installation_is_ok' => 'Installation process is finished. You must delete <em>install.php</em> file to access FreshRSS… or simply click on following button :)', - 'finish_installation' => 'Finish installation', - 'install_not_deleted' => 'Something was going wrong, you must delete the file <em>%s</em> manually.', + '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', + 'stats_no_idle' => 'There is no idle feed!', + + 'update' => 'Update', + 'update_system' => 'Update system', + 'update_check' => 'Check for new updates', + 'update_last' => 'Last verification: %s', + 'update_can_apply' => 'An update is available.', + 'update_apply' => 'Apply', + 'update_server_not_found' => 'Update server cannot be found. [%s]', + 'no_update' => 'No update to apply', + 'update_problem' => 'The update process has encountered an error: %s', + 'update_finished' => 'Update completed!', + + 'auth_reset' => 'Authentication reset', + 'auth_will_reset' => 'Authentication system will be reset: a form will be used instead of Persona.', + 'auth_not_persona' => 'Only Persona system can be reset.', + 'auth_no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.', + 'auth_form_set' => 'Form is now your default authentication system.', + 'auth_form_not_set' => 'A problem occured during authentication system configuration. Please retry later.', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 90ecbb8bb..b0fbf15ae 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -3,30 +3,82 @@ return array ( // LAYOUT 'login' => 'Connexion', + 'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>', + 'login_with_persona' => 'Connexion avec Persona', + 'login_persona_problem' => 'Problème de connexion à Persona ?', 'logout' => 'Déconnexion', 'search' => 'Rechercher des mots ou des #tags', + 'search_short' => 'Rechercher', 'configuration' => 'Configuration', - 'general_and_reading' => 'Général et lecture', + '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', + 'query_created' => 'Le filtre "%s" a bien été créé.', + 'no_query' => 'Vous n’avez pas encore créé de filtre.', + 'query_filter' => 'Filtres appliqués :', + 'no_query_filter' => 'Aucun filtre appliqué', + 'query_deprecated' => 'Ce filtre n’est plus valide. La catégorie ou le flux concerné a été supprimé.', 'about' => 'À propos', + 'stats' => 'Statistiques', + 'stats_idle' => 'Flux inactifs', + 'stats_main' => 'Statistiques principales', + 'stats_repartition' => 'Répartition des articles', + 'stats_entry_per_hour' => 'Par heure', + 'stats_entry_per_day_of_week' => 'Par jour de la semaine', + 'stats_entry_per_month' => 'Par mois', + + 'last_week' => 'Depuis la semaine dernière', + 'last_month' => 'Depuis le mois dernier', + 'last_3_month' => 'Depuis les trois derniers mois', + 'last_6_month' => 'Depuis les six derniers mois', + 'last_year' => 'Depuis l’année dernière', 'your_rss_feeds' => 'Vos flux RSS', 'add_rss_feed' => 'Ajouter un flux RSS', 'no_rss_feed' => 'Aucun flux RSS', - 'import_export_opml' => 'Importer / exporter (OPML)', + 'import_export' => 'Importer / exporter', + 'bookmark' => 'S’abonner (bookmark FreshRSS)', 'subscription_management' => 'Gestion des abonnements', - 'all_feeds' => 'Tous (%d)', - 'favorite_feeds' => 'Favoris (%d)', + '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' => 'Gestion', + 'administration' => 'Gérer', 'actualize' => 'Actualiser', 'mark_read' => 'Marquer comme lu', @@ -40,8 +92,13 @@ return array ( '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_adaptive' => 'Adapter l’affichage', + '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', @@ -55,126 +112,218 @@ return array ( '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' => 'Une erreur est survenue lors de la mise à jour', + '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_updated' => 'Les catégories ont été mises à jour.', 'categories_management' => 'Gestion des catégories', - 'feed_updated' => 'Le flux a été mis à jour', + 'feed_updated' => 'Le flux a été mis à jour.', 'rss_feed_management' => 'Gestion des flux RSS', - 'configuration_updated' => 'La configuration a été mise à jour', - 'general_and_reading_management'=> 'Gestion générale et affichage', - 'bad_opml_file' => 'Votre fichier OPML n’est pas valide', - 'shortcuts_updated' => 'Les raccourcis ont été mis à jour', - 'shortcuts_management' => 'Gestion des raccourcis', - 'feeds_marked_read' => 'Les flux ont été marqués comme lu', - 'updated' => 'Modifications enregistrées', + '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' => 'Un problème interne a été rencontré, le flux n’a pas pu être ajouté', - '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', - 'feeds_imported_with_errors' => 'Les flux ont été importés mais des erreurs sont survenues', - 'feeds_imported' => 'Les flux ont été importés', - 'category_emptied' => 'La catégorie a été vidée', - 'feed_deleted' => 'Le flux a été supprimé', - - 'optimization_complete' => 'Optimisation terminée', + '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', + 'invalid_login' => 'L’identifiant est invalide !', + + 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans.', // VIEWS 'save' => 'Enregistrer', 'delete' => 'Supprimer', 'cancel' => 'Annuler', + 'submit' => 'Valider', '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', + '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é', + '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 l’article sur le site d’origine', + 'see_on_website' => 'Voir sur le site d’origine', 'next_article' => 'Passer à l’article suivant', - 'shift_for_last' => '+ <code>shift</code> pour passer au dernier article de la page', + 'last_article' => 'Passer au dernier article', 'previous_article' => 'Passer à l’article précédent', - 'shift_for_first' => '+ <code>shift</code> pour passer au premier article de la page', + 'first_article' => 'Passer au premier article', 'next_page' => 'Passer à la page suivante', 'previous_page' => 'Passer à la page précédente', - - 'file_to_import' => 'Fichier à importer', + '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', + 'user_filter' => 'Accéder aux filtres utilisateur', + 'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.', + 'help' => 'Afficher la documentation', + + 'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)', + 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)', 'import' => 'Importer', + 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé!', + 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', + 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', 'export' => 'Exporter', + 'export_opml' => 'Exporter la liste des flux (OPML)', + 'export_starred' => 'Exporter les favoris', + 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', + 'starred_list' => 'Liste des articles favoris', + 'feed_list' => 'Liste des articles de %s', 'or' => 'ou', 'informations' => 'Informations', + 'damn' => 'Arf !', + 'ok' => 'Ok !', + 'attention' => 'Attention !', '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', - 'number_articles' => 'Nombre d’articles', - 'keep_history' => 'Garder l’historique ?', + 'articles' => 'articles', + 'number_articles' => '%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' => 'Chemin CSS des articles sur le site d’origine', + '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', + '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' => 'Pensez à en ajouter !', + 'think_to_add' => 'Vous pouvez ajouter des flux.', + + '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 connexions automatiques non-sûres au format : ', + 'api_enabled' => 'Autoriser l’accès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>', + 'auth_token' => 'Jeton d’identification', + 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>', + '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', + 'username_admin' => 'Nom d’utilisateur administrateur', + 'password' => 'Mot de passe', + 'create' => 'Créer', + 'user_created' => 'L’utilisateur %s a été créé.', + 'user_deleted' => 'L’utilisateur %s a été supprimé.', - 'general_configuration' => 'Configuration générale', 'language' => 'Langue', - 'delete_articles_every' => 'Supprimer les articles tous les', 'month' => 'mois', - 'persona_connection_email' => 'Adresse mail de connexion (utilise <a href="https://persona.org/">Persona</a>)', - 'allow_anonymous' => 'Autoriser la lecture anonyme', - 'auth_token' => 'Jeton d’identification', - 'explain_token' => 'Permet d’accéder à la sortie RSS sans besoin de s’authentifier.<br />%s?output=rss&token=%s', - 'reading_configuration' => 'Configuration de lecture', + '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', + 'number_divided_when_reader' => 'Divisé par 2 dans la vue de lecture.', 'default_view' => 'Vue par défaut', + 'articles_to_display' => 'Articles à afficher', '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 marqué comme lu,', - 'jump_next' => 'sauter au prochain voisin non lu', - 'img_with_lazyload' => 'Utiliser le mode “lazy load” pour charger les images', - 'auto_read_when' => 'Marquer comme lu lorsque', - 'article_selected' => 'l’article est sélectionné', - 'article_open_on_website' => 'l’article est ouvert sur le site d’origine', + 'display_categories_unfolded' => 'Afficher les catégories pliées par défaut', + 'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (ne fonctionne pas avec la configuration “Afficher tous les articles”)', + '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', + 'html5_notif_timeout' => 'Temps d’affichage de la notification HTML5', + 'seconds_(0_means_no_timeout)' => 'secondes (0 signifie aucun timeout ) ', + '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 mail', - 'on_shaarli' => 'Sur votre Shaarli', + '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', @@ -183,38 +332,45 @@ return array ( 'by' => 'par', 'load_more' => 'Charger plus d’articles', - 'nothing_to_load' => 'Il n’y a pas plus d’article', + '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 flux à afficher.', + '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 mail</a>', + '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://rsslounge.aditu.de/">RSSLounge</a>, <a href="http://tt-rss.org/redmine/projects/tt-rss/wiki">TinyTinyRSS</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. L’objectif étant d’offrir une alternative sérieuse au futur feu-Google Reader.', + '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', + 'logs_empty' => 'Les logs sont vides.', + 'clear_logs' => 'Effacer les logs', - 'forbidden_access' => 'Accès interdit', - 'forbidden_access_description' => 'L’accès est protégé par un mot de passe, veuillez <a class="signin" href="#">vous connecter</a> pour accéder aux flux.', + 'forbidden_access' => 'L’accès vous est interdit !', + 'login_required' => 'Accès protégé par mot de passe :', - 'confirm_action' => 'Êtes-vous sûr de vouloir continuer ? Cette action ne peut être annulée !', + 'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !', + 'confirm_action_feed_cat' => 'Êtes-vous sûr(e) de vouloir continuer ? Vous pourriez perdre les favoris et les filtres associés. Cette action ne peut être annulée !', + 'notif_title_new_articles' => 'FreshRSS : nouveaux articles !', + 'notif_body_new_articles' => 'Il y a \d nouveaux articles à lire sur FreshRSS.', // DATE 'january' => 'janvier', @@ -229,6 +385,25 @@ return array ( 'october' => 'octobre', 'november' => 'novembre', 'december' => 'décembre', + 'jan' => 'jan.', + 'feb' => 'fév.', + 'mar' => 'mar.', + 'apr' => 'avr.', + 'may' => 'mai.', + 'jun' => 'juin', + 'jul' => 'jui.', + 'aug' => 'août', + 'sep' => 'sep.', + 'oct' => 'oct.', + 'nov' => 'nov.', + 'dec' => 'déc.', + 'sun' => 'dim.', + 'mon' => 'lun.', + 'tue' => 'mar.', + 'wed' => 'mer.', + 'thu' => 'jeu.', + 'fri' => 'ven.', + 'sat' => 'sam.', // format spécial pour la fonction date() 'Jan' => '\j\a\n\v\i\e\r', 'Feb' => '\f\é\v\r\i\e\r', @@ -243,61 +418,37 @@ return array ( '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' => 'd %s Y', - 'format_date_hour' => '\l\e d %s Y \à H\:i', - - // INSTALLATION - 'freshrss_installation' => 'Installation - FreshRSS', - 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation - étape %d', - 'steps' => 'Étapes', - 'checks' => 'Vérifications', - 'bdd_configuration' => 'Configuration de la base de données', - '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 le Javascript d’activé', - 'php_is_ok' => 'Votre version de PHP est la %s et est compatible avec FreshRSS', - 'php_is_nok' => 'Votre version de PHP est la %s. Vous devriez avoir 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', - 'pdomysql_is_ok' => 'Vous disposez de PDO et de son driver pour 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' => 'Vous ne disposez pas du nécessaire pour parcourir le DOM (voir du côté du paquet php-xml ?)', - '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', - 'conf_is_ok' => 'Les droits sur le répertoire de configuration sont bons', - 'data_is_ok' => 'Les droits sur le répertoire de data 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', - 'username' => 'Nom utilisateur', - 'password' => 'Mot de passe', - 'bdd' => 'Base de données', - 'prefix' => 'Préfixe des tables', - - 'installation_is_ok' => 'L’installation s’est bien passée. Il faut maintenant supprimer le fichier <em>install.php</em> pour pouvoir accéder à FreshRSS… ou simplement cliquer sur le bouton ci-dessous :)', - 'finish_installation' => 'Terminer l’installation', - 'install_not_deleted' => 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.', + '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', + 'stats_no_idle' => 'Il n’y a aucun flux inactif !', + + 'update' => 'Mise à jour', + 'update_system' => 'Système de mise à jour', + 'update_check' => 'Vérifier les mises à jour', + 'update_last' => 'Dernière vérification : %s', + 'update_can_apply' => 'Une mise à jour est disponible.', + 'update_apply' => 'Appliquer la mise à jour', + 'update_server_not_found' => 'Le serveur de mise à jour n’a pas été trouvé. [%s]', + 'no_update' => 'Aucune mise à jour à appliquer', + 'update_problem' => 'La mise à jour a rencontré un problème : %s', + 'update_finished' => 'La mise à jour est terminée !', + + 'auth_reset' => 'Réinitialisation de l’authentification', + 'auth_will_reset' => 'Le système d’authentification va être réinitialisé : un formulaire sera utilisé à la place de Persona.', + 'auth_not_persona' => 'Seul le système d’authentification Persona peut être réinitialisé.', + 'auth_no_password_set' => 'Aucun mot de passe administrateur n’a été précisé. Cette fonctionnalité n’est pas disponible.', + 'auth_form_set' => 'Le formulaire est désormais votre système d’authentification.', + 'auth_form_not_set' => 'Un problème est survenu lors de la configuration de votre système d’authentification. Veuillez réessayer plus tard.', ); diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php new file mode 100644 index 000000000..c422de90f --- /dev/null +++ b/app/i18n/install.en.php @@ -0,0 +1,69 @@ +<?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)', + 'pdo_is_ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite)', + 'pdo_is_nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite)', + 'dom_is_ok' => 'You have the required library to browse the DOM', + 'dom_is_nok' => 'You lack a required library to browse the DOM (php-xml package)', + 'pcre_is_ok' => 'You have the required library for regular expressions (PCRE)', + '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', + 'http_referer_is_ok' => 'Your HTTP REFERER is known and corresponds to your server.', + 'http_referer_is_nok' => 'Please check that you are not altering your HTTP REFERER.', + '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.', +); diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php new file mode 100644 index 000000000..785c02459 --- /dev/null +++ b/app/i18n/install.fr.php @@ -0,0 +1,68 @@ +<?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)', + 'pdo_is_ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite)', + 'pdo_is_nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite)', + 'dom_is_ok' => 'Vous disposez du nécessaire pour parcourir le DOM', + 'dom_is_nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml)', + 'pcre_is_ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE)', + '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', + 'http_referer_is_ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.', + 'http_referer_is_nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.', + '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.', +); diff --git a/app/index.html b/app/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/app/index.html @@ -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> diff --git a/app/install.php b/app/install.php new file mode 100644 index 000000000..4449cd063 --- /dev/null +++ b/app/install.php @@ -0,0 +1,879 @@ +<?php +if (function_exists('opcache_reset')) { + opcache_reset(); +} + +define('BCRYPT_COST', 9); + +session_name('FreshRSS'); +session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true); +session_start(); + +if (isset($_GET['step'])) { + define('STEP',(int)$_GET['step']); +} else { + define('STEP', 0); +} + +define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;'); + +if (STEP === 3 && isset($_POST['type'])) { + $_SESSION['bd_type'] = $_POST['type']; +} + +if (isset($_SESSION['bd_type'])) { + switch ($_SESSION['bd_type']) { + case 'mysql': + include(APP_PATH . '/SQL/install.sql.mysql.php'); + break; + case 'sqlite': + include(APP_PATH . '/SQL/install.sql.sqlite.php'); + break; + } +} + +function param($key, $default = false) { + if (isset($_POST[$key])) { + return $_POST[$key]; + } else { + return $default; + } +} + + +// gestion internationalisation +$translates = array(); +$actual = 'en'; +function initTranslate() { + global $translates; + global $actual; + + $actual = isset($_SESSION['language']) ? $_SESSION['language'] : getBetterLanguage('en'); + + $file = APP_PATH . '/i18n/' . $actual . '.php'; + if (file_exists($file)) { + $translates = array_merge($translates, include($file)); + } + + $file = APP_PATH . '/i18n/install.' . $actual . '.php'; + if (file_exists($file)) { + $translates = array_merge($translates, include($file)); + } +} + +function getBetterLanguage($fallback) { + $available = availableLanguages(); + $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE']; + $language = strtolower(substr($accept, 0, 2)); + + if (isset($available[$language])) { + return $language; + } else { + return $fallback; + } +} + +function availableLanguages() { + return array( + 'en' => 'English', + 'fr' => 'Français' + ); +} + +function _t($key) { + global $translates; + $translate = $key; + if (isset($translates[$key])) { + $translate = $translates[$key]; + } + + $args = func_get_args(); + unset($args[0]); + + return vsprintf($translate, $args); +} + + +/*** SAUVEGARDES ***/ +function saveLanguage() { + if (!empty($_POST)) { + if (!isset($_POST['language'])) { + return false; + } + + $_SESSION['language'] = $_POST['language']; + + header('Location: index.php?step=1'); + } +} + +function saveStep2() { + if (!empty($_POST)) { + $_SESSION['title'] = substr(trim(param('title', _t('freshrss'))), 0, 25); + $_SESSION['old_entries'] = param('old_entries', 3); + $_SESSION['auth_type'] = param('auth_type', 'form'); + $_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', param('default_user', '')), 0, 16); + $_SESSION['mail_login'] = filter_var(param('mail_login', ''), FILTER_VALIDATE_EMAIL); + + $password_plain = param('passwordPlain', false); + if ($password_plain !== false) { + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($password_plain, PASSWORD_BCRYPT, array('cost' => BCRYPT_COST)); + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $_SESSION['passwordHash'] = $passwordHash; + } + + if (empty($_SESSION['title']) || + empty($_SESSION['old_entries']) || + empty($_SESSION['auth_type']) || + empty($_SESSION['default_user'])) { + return false; + } + + if (($_SESSION['auth_type'] === 'form' && empty($_SESSION['passwordHash'])) || + ($_SESSION['auth_type'] === 'persona' && empty($_SESSION['mail_login']))) { + return false; + } + + $_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); + if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { + $_SESSION['old_entries'] = 3; + } + + $token = ''; + if ($_SESSION['mail_login']) { + $token = sha1($_SESSION['salt'] . $_SESSION['mail_login']); + } + + $config_array = array( + 'language' => $_SESSION['language'], + 'theme' => 'Origine', + 'old_entries' => $_SESSION['old_entries'], + 'mail_login' => $_SESSION['mail_login'], + 'passwordHash' => $_SESSION['passwordHash'], + 'token' => $token, + ); + + $configPath = DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'; + @unlink($configPath); //To avoid access-rights problems + file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';'); + + if ($_SESSION['mail_login'] != '') { + $personaFile = DATA_PATH . '/persona/' . $_SESSION['mail_login'] . '.txt'; + @unlink($personaFile); + file_put_contents($personaFile, $_SESSION['default_user']); + } + + header('Location: index.php?step=3'); + } +} + +function saveStep3() { + if (!empty($_POST)) { + if ($_SESSION['bd_type'] === 'sqlite') { + $_SESSION['bd_base'] = $_SESSION['default_user']; + $_SESSION['bd_host'] = ''; + $_SESSION['bd_user'] = ''; + $_SESSION['bd_password'] = ''; + $_SESSION['bd_prefix'] = ''; + $_SESSION['bd_prefix_user'] = ''; //No prefix for SQLite + } else { + if (empty($_POST['type']) || + empty($_POST['host']) || + empty($_POST['user']) || + empty($_POST['base'])) { + $_SESSION['bd_error'] = 'Missing parameters!'; + } + $_SESSION['bd_base'] = substr($_POST['base'], 0, 64); + $_SESSION['bd_host'] = $_POST['host']; + $_SESSION['bd_user'] = $_POST['user']; + $_SESSION['bd_password'] = $_POST['pass']; + $_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16); + $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] .(empty($_SESSION['default_user']) ? '' :($_SESSION['default_user'] . '_')); + } + + $ini_array = array( + 'general' => array( + 'environment' => empty($_SESSION['environment']) ? 'production' : $_SESSION['environment'], + 'salt' => $_SESSION['salt'], + 'base_url' => '', + 'title' => $_SESSION['title'], + 'default_user' => $_SESSION['default_user'], + 'allow_anonymous' => isset($_SESSION['allow_anonymous']) ? $_SESSION['allow_anonymous'] : false, + 'allow_anonymous_refresh' => isset($_SESSION['allow_anonymous_refresh']) ? $_SESSION['allow_anonymous_refresh'] : false, + 'auth_type' => $_SESSION['auth_type'], + 'api_enabled' => isset($_SESSION['api_enabled']) ? $_SESSION['api_enabled'] : false, + 'unsafe_autologin_enabled' => isset($_SESSION['unsafe_autologin_enabled']) ? $_SESSION['unsafe_autologin_enabled'] : false, + ), + 'db' => array( + 'type' => $_SESSION['bd_type'], + 'host' => $_SESSION['bd_host'], + 'user' => $_SESSION['bd_user'], + 'password' => $_SESSION['bd_password'], + 'base' => $_SESSION['bd_base'], + 'prefix' => $_SESSION['bd_prefix'], + ), + ); + + @unlink(DATA_PATH . '/config.php'); //To avoid access-rights problems + file_put_contents(DATA_PATH . '/config.php', "<?php\n return " . var_export($ini_array, true) . ';'); + + $res = checkBD(); + + if ($res) { + $_SESSION['bd_error'] = ''; + header('Location: index.php?step=4'); + } elseif (empty($_SESSION['bd_error'])) { + $_SESSION['bd_error'] = 'Unknown error!'; + } + } + invalidateHttpCache(); +} + +function newPdo() { + switch ($_SESSION['bd_type']) { + case 'mysql': + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + ); + break; + case 'sqlite': + $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; + $driver_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + break; + default: + return false; + } + return new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); +} + +function deleteInstall() { + $res = unlink(DATA_PATH . '/do-install.txt'); + + if (!$res) { + return false; + } + + header('Location: index.php'); +} + + +/*** VÉRIFICATIONS ***/ +function checkStep() { + $s0 = checkStep0(); + $s1 = checkStep1(); + $s2 = checkStep2(); + $s3 = checkStep3(); + if (STEP > 0 && $s0['all'] != 'ok') { + header('Location: index.php?step=0'); + } elseif (STEP > 1 && $s1['all'] != 'ok') { + header('Location: index.php?step=1'); + } elseif (STEP > 2 && $s2['all'] != 'ok') { + header('Location: index.php?step=2'); + } elseif (STEP > 3 && $s3['all'] != 'ok') { + header('Location: index.php?step=3'); + } + $_SESSION['actualize_feeds'] = true; +} + +function checkStep0() { + $languages = availableLanguages(); + $language = isset($_SESSION['language']) && + isset($languages[$_SESSION['language']]); + + return array( + 'language' => $language ? 'ok' : 'ko', + 'all' => $language ? 'ok' : 'ko' + ); +} + +function checkStep1() { + $php = version_compare(PHP_VERSION, '5.2.1') >= 0; + $minz = file_exists(LIB_PATH . '/Minz'); + $curl = extension_loaded('curl'); + $pdo_mysql = extension_loaded('pdo_mysql'); + $pdo_sqlite = extension_loaded('pdo_sqlite'); + $pdo = $pdo_mysql || $pdo_sqlite; + $pcre = extension_loaded('pcre'); + $ctype = extension_loaded('ctype'); + $dom = class_exists('DOMDocument'); + $data = DATA_PATH && is_writable(DATA_PATH); + $cache = CACHE_PATH && is_writable(CACHE_PATH); + $log = LOG_PATH && is_writable(LOG_PATH); + $favicons = is_writable(DATA_PATH . '/favicons'); + $persona = is_writable(DATA_PATH . '/persona'); + $http_referer = is_referer_from_same_domain(); + + return array( + 'php' => $php ? 'ok' : 'ko', + 'minz' => $minz ? 'ok' : 'ko', + 'curl' => $curl ? 'ok' : 'ko', + 'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko', + 'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko', + 'pdo' => $pdo ? 'ok' : 'ko', + 'pcre' => $pcre ? 'ok' : 'ko', + 'ctype' => $ctype ? 'ok' : 'ko', + 'dom' => $dom ? 'ok' : 'ko', + 'data' => $data ? 'ok' : 'ko', + 'cache' => $cache ? 'ok' : 'ko', + 'log' => $log ? 'ok' : 'ko', + 'favicons' => $favicons ? 'ok' : 'ko', + 'persona' => $persona ? 'ok' : 'ko', + 'http_referer' => $http_referer ? 'ok' : 'ko', + 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && + $data && $cache && $log && $favicons && $persona && $http_referer ? + 'ok' : 'ko' + ); +} + +function checkStep2() { + $conf = !empty($_SESSION['title']) && + !empty($_SESSION['old_entries']) && + isset($_SESSION['mail_login']) && + !empty($_SESSION['default_user']); + + $form = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash'])) + ); + + $persona = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'persona' || !empty($_SESSION['mail_login'])) + ); + + $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; + if ($defaultUser === null) { + $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; + } + $data = is_writable(DATA_PATH . '/' . $defaultUser . '_user.php'); + + return array( + 'conf' => $conf ? 'ok' : 'ko', + 'form' => $form ? 'ok' : 'ko', + 'persona' => $persona ? 'ok' : 'ko', + 'data' => $data ? 'ok' : 'ko', + 'all' => $conf && $form && $persona && $data ? 'ok' : 'ko' + ); +} + +function checkStep3() { + $conf = is_writable(DATA_PATH . '/config.php'); + + $bd = isset($_SESSION['bd_type']) && + isset($_SESSION['bd_host']) && + isset($_SESSION['bd_user']) && + isset($_SESSION['bd_password']) && + isset($_SESSION['bd_base']) && + isset($_SESSION['bd_prefix']) && + isset($_SESSION['bd_error']); + $conn = empty($_SESSION['bd_error']); + + return array( + 'bd' => $bd ? 'ok' : 'ko', + 'conn' => $conn ? 'ok' : 'ko', + 'conf' => $conf ? 'ok' : 'ko', + 'all' => $bd && $conn && $conf ? 'ok' : 'ko' + ); +} + +function checkBD() { + $ok = false; + + try { + $str = ''; + $driver_options = null; + switch ($_SESSION['bd_type']) { + case 'mysql': + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' + ); + + try { // on ouvre une connexion juste pour créer la base si elle n'existe pas + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';'; + $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + $sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']); + $res = $c->query($sql); + } catch (PDOException $e) { + } + + // on écrase la précédente connexion en sélectionnant la nouvelle BDD + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; + break; + case 'sqlite': + $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; + $driver_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + break; + default: + return false; + } + + $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + + if (defined('SQL_CREATE_TABLES')) { + $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user'], _t('default_category')); + $stm = $c->prepare($sql); + $ok = $stm->execute(); + } else { + global $SQL_CREATE_TABLES; + if (is_array($SQL_CREATE_TABLES)) { + $ok = true; + foreach ($SQL_CREATE_TABLES as $instruction) { + $sql = sprintf($instruction, $_SESSION['bd_prefix_user'], _t('default_category')); + $stm = $c->prepare($sql); + $ok &= $stm->execute(); + } + } + } + } catch (PDOException $e) { + $ok = false; + $_SESSION['bd_error'] = $e->getMessage(); + } + + if (!$ok) { + @unlink(DATA_PATH . '/config.php'); + } + + return $ok; +} + +/*** AFFICHAGE ***/ +function printStep0() { + global $actual; +?> + <?php $s0 = checkStep0(); if ($s0['all'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('language_defined'); ?></p> + <?php } ?> + + <form action="index.php?step=0" method="post"> + <legend><?php echo _t('choose_language'); ?></legend> + <div class="form-group"> + <label class="group-name" for="language"><?php echo _t('language'); ?></label> + <div class="group-controls"> + <select name="language" id="language"> + <?php $languages = availableLanguages(); ?> + <?php foreach ($languages as $short => $lib) { ?> + <option value="<?php echo $short; ?>"<?php echo $actual == $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> + <?php } ?> + </select> + </div> + </div> + + <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> + <?php if ($s0['all'] == 'ok') { ?> + <a class="btn btn-important next-step" href="?step=1"><?php echo _t('next_step'); ?></a> + <?php } ?> + </div> + </div> + </form> +<?php +} + +function printStep1() { + $res = checkStep1(); +?> + <noscript><p class="alert alert-warn"><span class="alert-head"><?php echo _t('attention'); ?></span> <?php echo _t('javascript_is_better'); ?></p></noscript> + + <?php if ($res['php'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('php_is_ok', PHP_VERSION); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('php_is_nok', PHP_VERSION, '5.2.1'); ?></p> + <?php } ?> + + <?php if ($res['minz'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('minz_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('minz_is_nok', LIB_PATH . '/Minz'); ?></p> + <?php } ?> + + <?php if ($res['pdo'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('pdo_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('pdo_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['curl'] == 'ok') { ?> + <?php $version = curl_version(); ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('curl_is_ok', $version['version']); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('curl_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['pcre'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('pcre_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('pcre_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['ctype'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('ctype_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('ctype_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['dom'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('dom_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('dom_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['data'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('data_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('file_is_nok', DATA_PATH); ?></p> + <?php } ?> + + <?php if ($res['cache'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('cache_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('file_is_nok', CACHE_PATH); ?></p> + <?php } ?> + + <?php if ($res['log'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('log_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('file_is_nok', LOG_PATH); ?></p> + <?php } ?> + + <?php if ($res['favicons'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('favicons_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('file_is_nok', DATA_PATH . '/favicons'); ?></p> + <?php } ?> + + <?php if ($res['persona'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('persona_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('file_is_nok', DATA_PATH . '/persona'); ?></p> + <?php } ?> + + <?php if ($res['http_referer'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('http_referer_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('http_referer_is_nok'); ?></p> + <?php } ?> + + <?php if ($res['all'] == 'ok') { ?> + <a class="btn btn-important next-step" href="?step=2"><?php echo _t('next_step'); ?></a> + <?php } else { ?> + <p class="alert alert-error"><?php echo _t('fix_errors_before'); ?></p> + <?php } ?> +<?php +} + +function printStep2() { +?> + <?php $s2 = checkStep2(); if ($s2['all'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('general_conf_is_ok'); ?></p> + <?php } elseif (!empty($_POST)) { ?> + <p class="alert alert-error"><?php echo _t('fix_errors_before'); ?></p> + <?php } ?> + + <form action="index.php?step=2" method="post"> + <legend><?php echo _t('general_configuration'); ?></legend> + + <div class="form-group"> + <label class="group-name" for="title"><?php echo _t('title'); ?></label> + <div class="group-controls"> + <input type="text" id="title" name="title" value="<?php echo isset($_SESSION['title']) ? $_SESSION['title'] : _t('freshrss'); ?>" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="old_entries"><?php echo _t('delete_articles_every'); ?></label> + <div class="group-controls"> + <input type="number" id="old_entries" name="old_entries" required="required" min="1" max="1200" value="<?php echo isset($_SESSION['old_entries']) ? $_SESSION['old_entries'] : '3'; ?>" /> <?php echo _t('month'); ?> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="default_user"><?php echo _t('default_user'); ?></label> + <div class="group-controls"> + <input type="text" id="default_user" name="default_user" required="required" size="16" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" value="<?php echo isset($_SESSION['default_user']) ? $_SESSION['default_user'] : ''; ?>" placeholder="<?php echo httpAuthUser() == '' ? 'user1' : httpAuthUser(); ?>" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="auth_type"><?php echo _t('auth_type'); ?></label> + <div class="group-controls"> + <select id="auth_type" name="auth_type" required="required" onchange="auth_type_change(true)"> + <?php + function no_auth($auth_type) { + return !in_array($auth_type, array('form', 'persona', 'http_auth', 'none')); + } + $auth_type = isset($_SESSION['auth_type']) ? $_SESSION['auth_type'] : ''; + ?> + <option value="form"<?php echo $auth_type === 'form' || no_auth($auth_type) ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo _t('auth_form'); ?></option> + <option value="persona"<?php echo $auth_type === 'persona' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_persona'); ?></option> + <option value="http_auth"<?php echo $auth_type === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('http_auth'); ?>(REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option> + <option value="none"<?php echo $auth_type === 'none' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_none'); ?></option> + </select> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="passwordPlain"><?php echo _t('password_form'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" autocomplete="off" <?php echo $auth_type === 'form' ? ' required="required"' : ''; ?> /> + <a class="btn toggle-password" data-toggle="passwordPlain"><?php echo FreshRSS_Themes::icon('key'); ?></a> + </div> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="mail_login"><?php echo _t('persona_connection_email'); ?></label> + <div class="group-controls"> + <input type="email" id="mail_login" name="mail_login" value="<?php echo isset($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="alice@example.net" <?php echo $auth_type === 'persona' ? ' required="required"' : ''; ?> /> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <script> + function toggle_password() { + var button = this; + var passwordField = document.getElementById(button.getAttribute('data-toggle')); + + passwordField.setAttribute('type', 'text'); + button.className += ' active'; + + setTimeout(function() { + passwordField.setAttribute('type', 'password'); + button.className = button.className.replace(/(?:^|\s)active(?!\S)/g , ''); + }, 2000); + + return false; + } + toggles = document.getElementsByClassName('toggle-password'); + for (var i = 0 ; i < toggles.length ; i++) { + toggles[i].addEventListener('click', toggle_password); + } + + function auth_type_change(focus) { + var auth_value = document.getElementById('auth_type').value, + password_input = document.getElementById('passwordPlain'), + mail_input = document.getElementById('mail_login'); + + if (auth_value === 'form') { + password_input.required = true; + mail_input.required = false; + if (focus) { + password_input.focus(); + } + } else if (auth_value === 'persona') { + password_input.required = false; + mail_input.required = true; + if (focus) { + mail_input.focus(); + } + } else { + password_input.required = false; + mail_input.required = false; + } + } + auth_type_change(false); + </script> + + <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> + <?php if ($s2['all'] == 'ok') { ?> + <a class="btn btn-important next-step" href="?step=3"><?php echo _t('next_step'); ?></a> + <?php } ?> + </div> + </div> + </form> +<?php +} + +function printStep3() { +?> + <?php $s3 = checkStep3(); if ($s3['all'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('ok'); ?></span> <?php echo _t('bdd_conf_is_ok'); ?></p> + <?php } elseif ($s3['conn'] == 'ko') { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('bdd_conf_is_ko'),(empty($_SESSION['bd_error']) ? '' : ' : ' . $_SESSION['bd_error']); ?></p> + <?php } ?> + + <form action="index.php?step=3" method="post"> + <legend><?php echo _t('bdd_configuration'); ?></legend> + <div class="form-group"> + <label class="group-name" for="type"><?php echo _t('bdd_type'); ?></label> + <div class="group-controls"> + <select name="type" id="type" onchange="mySqlShowHide()"> + <?php if (extension_loaded('pdo_mysql')) {?> + <option value="mysql" + <?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql') ? 'selected="selected"' : ''; ?>> + MySQL + </option> + <?php }?> + <?php if (extension_loaded('pdo_sqlite')) {?> + <option value="sqlite" + <?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'sqlite') ? 'selected="selected"' : ''; ?>> + SQLite + </option> + <?php }?> + </select> + </div> + </div> + + <div id="mysql"> + <div class="form-group"> + <label class="group-name" for="host"><?php echo _t('host'); ?></label> + <div class="group-controls"> + <input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : 'localhost'; ?>" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="user"><?php echo _t('username'); ?></label> + <div class="group-controls"> + <input type="text" id="user" name="user" maxlength="16" pattern="[0-9A-Za-z_.-]{1,16}" value="<?php echo isset($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="pass"><?php echo _t('password'); ?></label> + <div class="group-controls"> + <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="base"><?php echo _t('bdd'); ?></label> + <div class="group-controls"> + <input type="text" id="base" name="base" maxlength="64" pattern="[0-9A-Za-z_]{1,64}" value="<?php echo isset($_SESSION['bd_base']) ? $_SESSION['bd_base'] : ''; ?>" placeholder="freshrss" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="prefix"><?php echo _t('prefix'); ?></label> + <div class="group-controls"> + <input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" /> + </div> + </div> + </div> + <script> + function mySqlShowHide() { + document.getElementById('mysql').style.display = document.getElementById('type').value === 'mysql' ? 'block' : 'none'; + } + mySqlShowHide(); + </script> + + <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> + <?php if ($s3['all'] == 'ok') { ?> + <a class="btn btn-important next-step" href="?step=4"><?php echo _t('next_step'); ?></a> + <?php } ?> + </div> + </div> + </form> +<?php +} + +function printStep4() { +?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t('congratulations'); ?></span> <?php echo _t('installation_is_ok'); ?></p> + <a class="btn btn-important next-step" href="?step=5"><?php echo _t('finish_installation'); ?></a> +<?php +} + +function printStep5() { +?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('oops'); ?></span> <?php echo _t('install_not_deleted', DATA_PATH . '/do-install.txt'); ?></p> +<?php +} + +checkStep(); + +initTranslate(); + +switch (STEP) { +case 0: +default: + saveLanguage(); + break; +case 1: + break; +case 2: + saveStep2(); + break; +case 3: + saveStep3(); + break; +case 4: + break; +case 5: + deleteInstall(); + break; +} +?> +<!DOCTYPE html> +<html lang="fr"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="initial-scale=1.0"> + <title><?php echo _t('freshrss_installation'); ?></title> + <link rel="stylesheet" type="text/css" media="all" href="../themes/base-theme/template.css" /> + <link rel="stylesheet" type="text/css" media="all" href="../themes/Origine/origine.css" /> + </head> + <body> + +<div class="header"> + <div class="item title"> + <h1><a href="index.php"><?php echo _t('freshrss'); ?></a></h1> + <h2><?php echo _t('installation_step', STEP); ?></h2> + </div> +</div> + +<div id="global"> + <ul class="nav nav-list aside"> + <li class="nav-header"><?php echo _t('steps'); ?></li> + <li class="item<?php echo STEP == 0 ? ' active' : ''; ?>"><a href="?step=0"><?php echo _t('language'); ?></a></li> + <li class="item<?php echo STEP == 1 ? ' active' : ''; ?>"><a href="?step=1"><?php echo _t('checks'); ?></a></li> + <li class="item<?php echo STEP == 2 ? ' active' : ''; ?>"><a href="?step=2"><?php echo _t('general_configuration'); ?></a></li> + <li class="item<?php echo STEP == 3 ? ' active' : ''; ?>"><a href="?step=3"><?php echo _t('bdd_configuration'); ?></a></li> + <li class="item<?php echo STEP == 4 ? ' active' : ''; ?>"><a href="?step=5"><?php echo _t('this_is_the_end'); ?></a></li> + </ul> + + <div class="post"> + <?php + switch (STEP) { + case 0: + default: + printStep0(); + break; + case 1: + printStep1(); + break; + case 2: + printStep2(); + break; + case 3: + printStep3(); + break; + case 4: + printStep4(); + break; + case 5: + printStep5(); + break; + } + ?> + </div> +</div> + </body> +</html> diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index d91aebbdd..d5c9bf4c9 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -1,13 +1,33 @@ -<div class="nav nav-list aside"> - <li class="nav-header"><?php echo Translate::t ('configuration'); ?></li> - - <li class="item<?php echo Request::actionName () == 'display' ? ' active' : ''; ?>"> - <a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'display')); ?>"><?php echo Translate::t ('general_and_reading'); ?></a> +<ul class="nav nav-list aside"> + <li class="nav-header"><?php echo _t('configuration'); ?></li> + <li class="item<?php echo Minz_Request::actionName() === 'display' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'display'); ?>"><?php echo _t('display_configuration'); ?></a> </li> - <li class="item<?php echo Request::actionName () == 'categorize' ? ' active' : ''; ?>"> - <a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'categorize')); ?>"><?php echo Translate::t ('categories'); ?></a> + <li class="item<?php echo Minz_Request::actionName() === 'reading' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'reading'); ?>"><?php echo _t('reading_configuration'); ?></a> </li> - <li class="item<?php echo Request::actionName () == 'shortcut' ? ' active' : ''; ?>"> - <a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'shortcut')); ?>"><?php echo Translate::t ('shortcuts'); ?></a> + <li class="item<?php echo Minz_Request::actionName() === 'archiving' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'archiving'); ?>"><?php echo _t('archiving_configuration'); ?></a> </li> -</div> + <li class="item<?php echo Minz_Request::actionName() === 'sharing' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'sharing'); ?>"><?php echo _t('sharing'); ?></a> + </li> + <li class="item<?php echo Minz_Request::actionName() === 'shortcut' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'shortcut'); ?>"><?php echo _t('shortcuts'); ?></a> + </li> + <li class="item<?php echo Minz_Request::actionName() === 'queries' ? ' active' : ''; ?>"> + <a href="<?php echo _url('configure', 'queries'); ?>"><?php echo _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 _t('users'); ?></a> + </li> + <?php + $current_user = Minz_Session::param('currentUser', ''); + if (Minz_Configuration::isAdmin($current_user)) { + ?> + <li class="item<?php echo Minz_Request::controllerName() === 'update' ? ' active' : ''; ?>"> + <a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('update'); ?></a> + </li> + <?php } ?> +</ul> diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 6df64e6dc..5ffb1c791 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -1,58 +1,75 @@ <ul class="nav nav-list aside aside_feed"> - <li class="nav-header"><?php echo Translate::t ('your_rss_feeds'); ?></li> + <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 Url::display (array ('c' => 'feed', 'a' => 'add')); ?>"> + <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 Translate::t ('add_rss_feed'); ?>" /> + <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"><i class="icon i_down"></i></a> + <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-close"><a href="#close">❌</a></li> - <li class="dropdown-header"><?php echo Translate::t ('category'); ?></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 () == '000000' ? ' selected="selected"' : ''; ?>> + <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 Translate::t ('http_authentication'); ?></li> + <li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li> <li class="input"> - <input type="text" name="username" id="username" placeholder="<?php echo Translate::t ('username'); ?>" /> + <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="password" id="password" placeholder="<?php echo Translate::t ('password'); ?>" /> + <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"><i class="icon i_add"></i></button> + <button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('add'); ?></button> </div> </form></li> - <li class="item<?php echo Request::actionName () == 'importExport' ? ' active' : ''; ?>"><a href="<?php echo _url ('configure', 'importExport'); ?>"><?php echo Translate::t ('import_export_opml'); ?></a></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 ($this->flux && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"> + <li class="item<?php echo (isset($this->flux) && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"> <a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>"> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="" /> + <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <?php echo $feed->name (); ?> </a> </li> <?php } ?> <?php } else { ?> - <li class="item disable"><?php echo Translate::t ('no_rss_feed'); ?></li> + <li class="item disable"><?php echo Minz_Translate::t ('no_rss_feed'); ?></li> <?php } ?> </ul> diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index 5ac86db96..aac3c0896 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -1,106 +1,103 @@ -<div class="aside aside_flux" id="aside_flux"> - <a class="toggle_aside" href="#close"><i class="icon i_close"></i></a> +<div class="aside aside_flux<?php if ($this->conf->hide_read_feeds && ($this->state & FreshRSS_Entry::STATE_NOT_READ) && !($this->state & FreshRSS_Entry::STATE_READ)) echo ' state_unread'; ?>" id="aside_flux"> + <a class="toggle_aside" href="#close"><?php echo _i('close'); ?></a> <ul class="categories"> - <?php - $params = Request::params (); - $params['output'] = 'rss'; - if (isset ($params['search'])) { - $params['search'] = urlencode ($params['search']); - } - - $token = $this->conf->token (); - if (login_is_conf($this->conf) && $token != '') { - $params['token'] = $token; - } + <?php if ($this->loginOk) { ?> + <form id="mark-read-aside" method="post" style="display: none"></form> - $url = array ( - 'c' => 'index', - 'a' => 'index', - 'params' => $params - ); - ?> - <?php if (!login_is_conf ($this->conf) || is_logged ()) { ?> <li> - <div class="stick"> - <a class="btn btn-important" href="<?php echo _url ('configure', 'feed'); ?>"><?php echo Translate::t ('subscription_management'); ?></a> - <a class="btn btn-important" href="<?php echo Url::display ($url); ?>"><i class="icon i_rss"></i></a> + <div class="stick configure-feeds"> + <a class="btn btn-important" href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('subscription_management'); ?></a> + <a class="btn btn-important" href="<?php echo _url('configure', 'categorize'); ?>" title="<?php echo _t('categories_management'); ?>"><?php echo _i('category-white'); ?></a> </div> </li> + <?php } elseif (Minz_Configuration::needsLogin()) { ?> + <li><a href="<?php echo _url('index', 'about'); ?>"><?php echo _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="all"> - <a class="btn<?php echo $this->get_c == 'all' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index'); ?>"> - <i class="icon i_all"></i> - <?php echo Translate::t ('all_feeds', $this->nb_total); ?> - <?php if ($this->nb_not_read > 0) { ?> - <span class="notRead"><?php echo $this->nb_not_read > 1 ? Translate::t ('not_reads', $this->nb_not_read) : Translate::t ('not_read', $this->nb_not_read); ?></span> - <?php } ?> + <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 _i('all'); ?> + <?php echo _t('main_stream'); ?> </a> </div> </li> <li> - <div class="favorites"> - <a class="btn<?php echo $this->get_c == 'favoris' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'favoris'); ?>"> - <i class="icon i_bookmark"></i> - <?php echo Translate::t ('favorite_feeds', $this->nb_favorites); ?> + <div class="category favorites<?php echo $this->get_c == 's' ? ' active' : ''; ?>"> + <a data-unread="<?php echo formatNumber($this->nb_favorites['unread']); ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>"> + <?php echo _i('bookmark'); ?> + <?php echo _t('favorite_feeds', formatNumber($this->nb_favorites['all'])); ?> </a> </div> </li> - <?php foreach ($this->cat_aside as $cat) { ?> - <?php $feeds = $cat->feeds (); $catNotRead = $cat->nbNotRead (); ?> - <?php if (!empty ($feeds)) { ?> - <li> - <?php $c_active = false; if ($this->get_c == $cat->id ()) { $c_active = true; } ?> - <div class="category<?php echo $c_active ? ' active' : ''; ?>"> - <a class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id ()); ?>"> - <?php echo $cat->name (); ?> - <?php if ($catNotRead > 0) { ?> - <span class="notRead" title="<?php echo $catNotRead > 1 ? Translate::t ('not_reads', $catNotRead) : Translate::t ('not_read', $catNotRead); ?>"><?php echo $catNotRead ; ?></span> - <?php } ?> - </a> - </div> - - <ul class="feeds<?php echo $c_active ? ' active' : ''; ?>"> - <?php foreach ($feeds as $feed) { ?> - <?php $nbEntries = $feed->nbEntries (); ?> - <?php $f_active = false; if ($this->get_f == $feed->id ()) { $f_active = true; } ?> - <li class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"> - <div class="dropdown"> - <div id="dropdown-<?php echo $feed->id(); ?>" class="dropdown-target"></div> - <a class="dropdown-toggle" href="#dropdown-<?php echo $feed->id(); ?>"><i class="icon i_configure"></i></a> - <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_' . $feed->id ()); ?>"><?php echo Translate::t ('filter'); ?></a></li> - <li class="item"><a target="_blank" href="<?php echo $feed->website (); ?>"><?php echo Translate::t ('see_website'); ?></a></li> - <?php if (!login_is_conf ($this->conf) || is_logged ()) { ?> - <li class="separator"></li> - - <li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>"><?php echo Translate::t ('administration'); ?></a></li> - <li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', $feed->id ()); ?>"><?php echo Translate::t ('actualize'); ?></a></li> - <li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', 'f_' . $feed->id ()); ?>"><?php echo Translate::t ('mark_read'); ?></a></li> - <?php } ?> - </ul> - </div> - - <?php $not_read = $feed->nbNotRead (); ?> + <?php + foreach ($this->cat_aside as $cat) { + $feeds = $cat->feeds(); + if (!empty($feeds)) { + $c_active = false; + $c_show = false; + if ($this->get_c == $cat->id()) { + $c_active = true; + if (!$this->conf->display_categories || $this->get_f) { + $c_show = true; + } + } + ?><li data-unread="<?php echo $cat->nbNotRead(); ?>"<?php if ($c_active) echo ' class="active"'; ?>><?php + ?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php + ?><a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name(); ?></a><?php + ?><a class="btn dropdown-toggle" href="#"><?php echo _i($c_show ? 'up' : 'down'); ?></a><?php + ?></div><?php + ?><ul class="feeds<?php echo $c_show ? ' 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' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>"><?php + ?><div class="dropdown"><?php + ?><div class="dropdown-target"></div><?php + ?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website(); ?>"><?php echo _i('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> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="" /> - <?php echo $not_read > 0 ? '<b>' : ''; ?> - <a class="feed" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"> - <?php echo $not_read > 0 ? '(' . $not_read . ') ' : ''; ?> - <?php echo $feed->name(); ?> - </a> - <?php echo $not_read > 0 ? '</b>' : ''; ?> - </li> - <?php } ?> - </ul> +<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 _t('filter'); ?></a></li> + <?php if ($this->loginOk) { ?> + <li class="item"><a href="<?php echo _url('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo _t('stats'); ?></a></li> + <?php } ?> + <li class="item"><a target="_blank" href="http://example.net/"><?php echo _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 _t('administration'); ?></a></li> + <li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo _t('actualize'); ?></a></li> + <li class="item"> + <?php $confirm = $this->conf->reading_confirm ? 'confirm' : ''; ?> + <button class="read_all as-link <?php echo $confirm; ?>" + form="mark-read-aside" + formaction="<?php echo _url('entry', 'read', 'get', 'f_!!!!!!'); ?>" + type="submit"><?php echo _t('mark_read'); ?></button> </li> - <?php } } ?> + <?php } ?> </ul> - - <span class="aside_flux_ender"><!-- hack for fix menus, if it can be helpful ;) --></span> -</div> +</script> diff --git a/app/layout/aside_stats.phtml b/app/layout/aside_stats.phtml new file mode 100644 index 000000000..fbfb9d84d --- /dev/null +++ b/app/layout/aside_stats.phtml @@ -0,0 +1,12 @@ +<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> + <li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>"> + <a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a> + </li> +</ul> diff --git a/app/layout/header.phtml b/app/layout/header.phtml index b75ed818b..028e63d8a 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,76 +1,115 @@ -<?php if (login_is_conf ($this->conf)) { ?> -<ul class="nav nav-head nav-login"> - <?php if (!is_logged ()) { ?> - <li class="item"><i class="icon i_login"></i> <a class="signin" href="#"><?php echo Translate::t ('login'); ?></a></li> - <?php } else { ?> - <li class="item"><i class="icon i_logout"></i> <a class="signout" href="#"><?php echo Translate::t ('logout'); ?></a></li> - <?php } ?> -</ul> -<?php } ?> +<?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"> - <img class="logo" src="<?php echo Url::display ('/icons/icon-32.png'); ?>" alt="" /> - <h1><a href="<?php echo _url ('index', 'index'); ?>"><?php echo Configuration::title (); ?></a></h1> + <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(!login_is_conf ($this->conf) || - is_logged() || - $this->conf->anonAccess() == 'yes') { ?> - <form action="<?php echo _url ('index', 'index'); ?>" method="get"> + <?php if ($this->loginOk || Minz_Configuration::allowAnonymous()) { ?> + <form action="<?php echo _url('index', 'index'); ?>" method="get"> <div class="stick"> - <?php $search = Request::param ('search', ''); ?> - <input type="text" name="search" id="search" value="<?php echo $search; ?>" placeholder="<?php echo Translate::t ('search'); ?>" /> + <?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 = Request::param ('get', ''); ?> - <?php if($get != '') { ?> + <?php $get = Minz_Request::param('get', ''); ?> + <?php if ($get != '') { ?> <input type="hidden" name="get" value="<?php echo $get; ?>" /> <?php } ?> - <?php $order = Request::param ('order', ''); ?> - <?php if($order != '') { ?> + <?php $order = Minz_Request::param('order', ''); ?> + <?php if ($order != '') { ?> <input type="hidden" name="order" value="<?php echo $order; ?>" /> <?php } ?> - <?php $state = Request::param ('state', ''); ?> - <?php if($state != '') { ?> + <?php $state = Minz_Request::param('state', ''); ?> + <?php if ($state != '') { ?> <input type="hidden" name="state" value="<?php echo $state; ?>" /> <?php } ?> - <button class="btn" type="submit"><i class="icon i_search"></i></button> + <button class="btn" type="submit"><?php echo _i('search'); ?></button> </div> </form> <?php } ?> </div> - <?php if (!login_is_conf ($this->conf) || is_logged ()) { ?> + <?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"><i class="icon i_configure"></i></a> + <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 Translate::t ('configuration'); ?></li> - <li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Translate::t ('general_and_reading'); ?></a></li> - <li class="item"><a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Translate::t ('categories'); ?></a></li> - <li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Translate::t ('shortcuts'); ?></a></li> - <li class="separator"></li> - <li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Translate::t ('about'); ?></a></li> - <li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Translate::t ('logs'); ?></a></li> - <?php if (login_is_conf ($this->conf) && is_logged ()) { ?> + <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 class="signout" href="#"><i class="icon i_logout"></i> <?php echo Translate::t ('logout'); ?></a></li> + <li class="item"><a href="<?php echo _url('configure', 'users'); ?>"><?php echo _t('users'); ?></a></li> + <?php + $current_user = Minz_Session::param('currentUser', ''); + if (Minz_Configuration::isAdmin($current_user)) { + ?> + <li class="item"><a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('update'); ?></a></li> <?php } ?> + <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 } - - if (login_is_conf ($this->conf) && !is_logged ()) { ?> - <div class="item configure"> - <i class="icon i_login"></i> <a class="signin" href="#"><?php echo Translate::t ('login'); ?></a> - </div> - <?php } ?> + <?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> diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml index 331570873..f95f45b5e 100644 --- a/app/layout/layout.phtml +++ b/app/layout/layout.phtml @@ -1,29 +1,62 @@ <!DOCTYPE html> -<html lang="fr"> +<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"> - <link rel="icon" type="image/x-icon" href="<?php echo Url::display ('/favicon.ico'); ?>" /> - <link rel="icon" type="image/png" href="<?php echo Url::display ('/favicon.ico'); ?>" /> - - <?php echo self::headTitle (); ?> - <?php echo self::headStyle (); ?> - <?php echo self::headScript (); ?> + <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; + $params['ajax'] = 1; +?> + <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" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" /> + <link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" /> +<?php + if (isset($this->url)) { + $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> -<?php $this->partial ('header'); ?> + <body class="<?php echo Minz_Request::param('output', 'normal'); ?>"> +<?php $this->partial('header'); ?> <div id="global"> - <?php $this->render (); ?> + <?php $this->render(); ?> </div> -<?php $this->partial ('persona'); ?> +<?php + $msg = ''; + $status = 'closed'; + if (isset($this->notification)) { + $msg = $this->notification['content']; + $status = $this->notification['type']; -<?php if (isset ($this->notification)) { ?> -<div class="notification <?php echo $this->notification['type']; ?>"> - <?php echo $this->notification['content']; ?> - <a class="close" href=""><i class="icon i_close"></i></a> + 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> -<?php } ?> </body> </html> diff --git a/app/layout/nav_entries.phtml b/app/layout/nav_entries.phtml index 5501f1725..3141e92a0 100644 --- a/app/layout/nav_entries.phtml +++ b/app/layout/nav_entries.phtml @@ -1,5 +1,5 @@ -<ul class="nav_entries"> - <li class="item"><a class="previous_entry" href="#"><i class="icon i_prev"></i></a></li> - <li class="item"><a class="up" href="#"><i class="icon i_up"></i></a></li> - <li class="item"><a class="next_entry" href="#"><i class="icon i_next"></i></a></li> +<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>
\ No newline at end of file diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index fd7ed550b..a9e6614e7 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -1,168 +1,306 @@ +<?php + $actual_view = Minz_Request::param('output', 'normal'); +?> <div class="nav_menu"> - <a class="btn toggle_aside" href="#aside_flux"><i class="icon i_category"></i></a> + <?php if ($actual_view === 'normal') { ?> + <a class="btn toggle_aside" href="#aside_flux"><?php echo _i('category'); ?></a> + <?php } ?> + + <?php if ($this->loginOk) { ?> + <div id="nav_menu_actions" 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 _t('show_read'); ?>"> + <?php echo _i('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 _t('show_not_reads'); ?>"> + <?php echo _i('unread'); ?> + </a> + + <?php + if ($this->state & FreshRSS_Entry::STATE_FAVORITE || $this->get_c == 's') { + $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 _t('show_favorite'); ?>"> + <?php echo _i('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 _t('show_not_favorite'); ?>"> + <?php echo _i('non-starred'); ?> + </a> + + <div class="dropdown"> + <div id="dropdown-query" class="dropdown-target"></div> + + <a class="dropdown-toggle btn" href="#dropdown-query"><?php echo _i('down'); ?></a> + <ul class="dropdown-menu"> + <li class="dropdown-close"><a href="#close">❌</a></li> - <?php if (!login_is_conf ($this->conf) || is_logged ()) { ?> - <a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><i class="icon i_refresh"></i></a> + <li class="dropdown-header"> + <?php echo _t('queries'); ?> + <a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo _i('configure'); ?></a> + </li> + <?php foreach ($this->conf->queries as $query) { ?> + <li class="item query"> + <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 _i('bookmark-add'); ?> <?php echo _t('add_query'); ?></a></li> + </ul> + </div> + </div> <?php $get = false; - $string_mark = Translate::t ('mark_all_read'); + $string_mark = _t('mark_all_read'); if ($this->get_f) { $get = 'f_' . $this->get_f; - $string_mark = Translate::t ('mark_feed_read'); - } elseif ($this->get_c && - $this->get_c != 'all' && - $this->get_c != 'favoris' && - $this->get_c != 'public') { - $get = 'c_' . $this->get_c; - $string_mark = Translate::t ('mark_cat_read'); + $string_mark = _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 = _t('mark_cat_read'); } $nextGet = $get; - if (($this->conf->onread_jump_next () === 'yes') && (strlen ($get) > 2)) { + 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; + case 'c': + foreach ($this->cat_aside as $cat) { + if ($cat->id() == $this->get_c) { + $foundCurrent = true; + continue; } - $nextGet = strlen ($anotherUnreadId) > 1 ? 'c_' . $anotherUnreadId : 'all'; - 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; + 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; } - break; + if ($feed->nbNotRead() <= 0) continue; + $anotherUnreadId = $feed->id(); + if ($foundCurrent) break; } + break; } - $nextGet = strlen ($anotherUnreadId) > 1 ? 'f_' . $anotherUnreadId : 'c_' . $this->get_c; - break; + } + $nextGet = empty($anotherUnreadId) ? 'c_' . $this->get_c : 'f_' . $anotherUnreadId; + break; + } + } + + $p = isset($this->entries[0]) ? $this->entries[0] : null; + $idMax = $p === null ? (time() - 1) . '000000' : $p->id(); + + if ($this->order === 'ASC') { //In this case we do not know but we guess idMax + $idMax2 = (time() - 1) . '000000'; + if (strcmp($idMax2, $idMax) > 0) { + $idMax = $idMax2; } } + + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax)); + $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); ?> + <form id="mark-read-menu" method="post" style="display: none"></form> + <div class="stick" id="nav_menu_read_all"> - <a class="read_all btn" href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet); ?>"><?php echo Translate::t ('mark_read'); ?></a> + <?php $confirm = $this->conf->reading_confirm ? 'confirm' : ''; ?> + <button class="read_all btn <?php echo $confirm; ?>" + form="mark-read-menu" + formaction="<?php echo $markReadUrl; ?>" + type="submit"><?php echo _t('mark_read'); ?></button> + <div class="dropdown"> <div id="dropdown-read" class="dropdown-target"></div> - <a class="dropdown-toggle btn" href="#dropdown-read"><i class="icon i_down"></i></a> + <a class="dropdown-toggle btn" href="#dropdown-read"><?php echo _i('down'); ?></a> <ul class="dropdown-menu"> - <li class="dropdown-close"><a href="#close"> </a></li> + <li class="dropdown-close"><a href="#close">❌</a></li> - <li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet); ?>"><?php echo $string_mark; ?></a></li> + <li class="item"> + <button class="as-link <?php echo $confirm; ?>" + form="mark-read-menu" + formaction="<?php echo $markReadUrl; ?>" + type="submit"><?php echo $string_mark; ?></button> + </li> <li class="separator"></li> <?php - $date = getdate (); - $today = mktime (0, 0, 0, $date['mon'], $date['mday'], $date['year']); - $one_week = $today - 604800; + $mark_before_today = $arUrl; + $mark_before_today['params']['idMax'] = $this->today . '000000'; + $mark_before_one_week = $arUrl; + $mark_before_one_week['params']['idMax'] = ($this->today - 604800) . '000000'; ?> - <li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'dateMax', $today); ?>"><?php echo Translate::t ('before_one_day'); ?></a></li> - <li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'dateMax', $one_week); ?>"><?php echo Translate::t ('before_one_week'); ?></a></li> + <li class="item"> + <button class="as-link <?php echo $confirm; ?>" + form="mark-read-menu" + formaction="<?php echo Minz_Url::display($mark_before_today); ?>" + type="submit"><?php echo _t('before_one_day'); ?></button> + </li> + <li class="item"> + <button class="as-link <?php echo $confirm; ?>" + form="mark-read-menu" + formaction="<?php echo Minz_Url::display($mark_before_one_week); ?>" + type="submit"><?php echo _t('before_one_week'); ?></button> + </li> </ul> </div> </div> <?php } ?> - <?php - $params = Request::params (); - if (isset ($params['search'])) { - $params['search'] = urlencode ($params['search']); - } - $url = array ( - 'c' => 'index', - 'a' => 'index', - 'params' => $params - ); - ?> - <div class="dropdown" id="nav_menu_views"> - <div id="dropdown-views" class="dropdown-target"></div> - <a class="dropdown-toggle btn" href="#dropdown-views"><?php echo Translate::t ('display'); ?> <i class="icon i_down"></i></a> - <ul class="dropdown-menu"> - <li class="dropdown-close"><a href="#close"> </a></li> - - <?php - $url_output = $url; - $actual_view = Request::param('output', 'normal'); - ?> - <?php if($actual_view != 'normal') { ?> - <li class="item"> - <?php $url_output['params']['output'] = 'normal'; ?> - <a class="view_normal" href="<?php echo Url::display ($url_output); ?>"> - <?php echo Translate::t ('normal_view'); ?> - </a> - </li> - <?php } if($actual_view != 'reader') { ?> - <li class="item"> - <?php $url_output['params']['output'] = 'reader'; ?> - <a class="view_normal" href="<?php echo Url::display ($url_output); ?>"> - <?php echo Translate::t ('reader_view'); ?> - </a> - </li> - <?php } if($actual_view != 'global') { ?> - <li class="item"> - <?php $url_output['params']['output'] = 'global'; ?> - <a class="view_normal" href="<?php echo Url::display ($url_output); ?>"> - <?php echo Translate::t ('global_view'); ?> - </a> - </li> - <?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 _t('normal_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> + <?php echo _i("view-normal"); ?> + </a> - <li class="separator"></li> + <?php $url_output['params']['output'] = 'global'; ?> + <a class="view_global btn <?php echo $actual_view == 'global'? 'active' : ''; ?>" title="<?php echo _t('global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> + <?php echo _i("view-global"); ?> + </a> - <li class="item"> - <?php - $url_state = $url; - if ($this->state == 'not_read') { - $url_state['params']['state'] = 'all'; - ?> - <a class="print_all" href="<?php echo Url::display ($url_state); ?>"> - <?php echo Translate::t ('show_all_articles'); ?> - </a> - <?php - } else { - $url_state['params']['state'] = 'not_read'; - ?> - <a class="print_non_read" href="<?php echo Url::display ($url_state); ?>"> - <?php echo Translate::t ('show_not_reads'); ?> - </a> - <?php } ?> - </li> + <?php $url_output['params']['output'] = 'reader'; ?> + <a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo _t('reader_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> + <?php echo _i("view-reader"); ?> + </a> + + <?php + $url_output['params']['output'] = 'rss'; + if ($this->conf->token) { + $url_output['params']['token'] = $this->conf->token; + } + ?> + <a class="view_rss btn" target="_blank" title="<?php echo _t('rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> + <?php echo _i('rss'); ?> + </a> + </div> - <li class="separator"></li> + <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 _t('search_short'); ?>" /> - <li class="item"> - <?php - $url_order = $url; - if ($this->order == 'low_to_high') { - $url_order['params']['order'] = 'high_to_low'; - ?> - <a href="<?php echo Url::display ($url_order); ?>"> - <?php echo Translate::t ('older_first'); ?> - </a> - <?php - } else { - $url_order['params']['order'] = 'low_to_high'; - ?> - <a href="<?php echo Url::display ($url_order); ?>"> - <?php echo Translate::t ('newer_first'); ?> - </a> - <?php } ?> - </li> - </ul> + <?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 id="toggle-order" class="btn" href="<?php echo Minz_Url::display($url_order); ?>" title="<?php echo _t($title); ?>"> + <?php echo _i($icon); ?> + </a> + + <?php if ($this->loginOk || Minz_Configuration::allowAnonymousRefresh()) { ?> + <a id="actualize" class="btn" href="<?php echo _url('feed', 'actualize'); ?>"><?php echo _i('refresh'); ?></a> + <?php } ?> </div> diff --git a/app/layout/persona.phtml b/app/layout/persona.phtml deleted file mode 100644 index f25e4c3fd..000000000 --- a/app/layout/persona.phtml +++ /dev/null @@ -1,68 +0,0 @@ -<?php if (login_is_conf ($this->conf)) { ?> - -<?php - $mail = Session::param ('mail', 'null'); - if ($mail != 'null') { - $mail = '\'' . $mail . '\''; - } -?> - -<script type="text/javascript"> -url = "<?php echo Url::display (); ?>" -login_url = "<?php echo Url::display (array ('a' => 'login')); ?>"; -logout_url = "<?php echo Url::display (array ('a' => 'logout')); ?>"; -currentUser = <?php echo $mail; ?>; - -$('a.signin').click(function() { - navigator.id.request(); - return false; -}); - -$('a.signout').click(function() { - navigator.id.logout(); - return false; -}); - -navigator.id.watch({ - loggedInUser: currentUser, - onlogin: function(assertion) { - // A user has logged in! Here you need to: - // 1. Send the assertion to your backend for verification and to create a session. - // 2. Update your UI. - $.ajax ({ - type: 'POST', - url: login_url, - data: {assertion: assertion}, - success: function(res, status, xhr) { - var res_obj = jQuery.parseJSON(res); - - if (res_obj.status == 'failure') { - //alert (res_obj.reason); - } else if (res_obj.status == 'okay') { - location.href = url; - } - }, - error: function(res, status, xhr) { - alert("login failure : " + res); - } - }); - }, - onlogout: function() { - // A user has logged out! Here you need to: - // Tear down the user's session by redirecting the user or making a call to your backend. - // Also, make sure loggedInUser will get set to null on the next page load. - // (That's a literal JavaScript null. Not false, 0, or undefined. null.) - $.ajax ({ - type: 'POST', - url: logout_url, - success: function(res, status, xhr) { - location.href = url; - }, - error: function(res, status, xhr) { - //alert("logout failure" + res); - } - }); - } -}); -</script> -<?php } ?> diff --git a/app/models/Category.php b/app/models/Category.php deleted file mode 100755 index 09bc1a683..000000000 --- a/app/models/Category.php +++ /dev/null @@ -1,325 +0,0 @@ -<?php - -class Category extends Model { - private $id = false; - private $name; - private $color; - private $nbFeed = -1; - private $nbNotRead = -1; - private $feeds = null; - - public function __construct ($name = '', $color = '#0062BE', $feeds = null) { - $this->_name ($name); - $this->_color ($color); - if (!empty($feeds)) { - $this->_feeds ($feeds); - $this->nbFeed = 0; - $this->nbNotRead = 0; - foreach ($feeds as $feed) { - $this->nbFeed++; - $this->nbNotRead += $feed->nbNotRead (); - } - } - } - - public function id () { - if (!$this->id) { - return small_hash ($this->name . time () . Configuration::selApplication ()); - } else { - return $this->id; - } - } - public function name () { - return $this->name; - } - public function color () { - return $this->color; - } - public function nbFeed () { - if ($this->nbFeed < 0) { - $catDAO = new CategoryDAO (); - $this->nbFeed = $catDAO->countFeed ($this->id ()); - } - - return $this->nbFeed; - } - public function nbNotRead () { - if ($this->nbNotRead < 0) { - $catDAO = new CategoryDAO (); - $this->nbNotRead = $catDAO->countNotRead ($this->id ()); - } - - return $this->nbNotRead; - } - public function feeds () { - if (is_null ($this->feeds)) { - $feedDAO = new FeedDAO (); - $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 _color ($value) { - if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) { - $this->color = $value; - } else { - $this->color = '#0062BE'; - } - } - public function _feeds ($values) { - if (!is_array ($values)) { - $values = array ($values); - } - - $this->feeds = $values; - } -} - -class CategoryDAO extends Model_pdo { - public function addCategory ($valuesTmp) { - $sql = 'INSERT INTO ' . $this->prefix . 'category (id, name, color) VALUES(?, ?, ?)'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - $valuesTmp['id'], - $valuesTmp['name'], - $valuesTmp['color'], - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function updateCategory ($id, $valuesTmp) { - $sql = 'UPDATE ' . $this->prefix . 'category SET name=?, color=? WHERE id=?'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - $valuesTmp['name'], - $valuesTmp['color'], - $id - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $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 true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $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 = HelperCategory::daoToCategory ($res); - - if (isset ($cat[0])) { - return $cat[0]; - } else { - return false; - } - } - 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 = HelperCategory::daoToCategory ($res); - - if (isset ($cat[0])) { - return $cat[0]; - } else { - return false; - } - } - - public function listCategories ($prePopulateFeeds = true) { //TODO: Search code-base for places where $prePopulateFeeds should be false - if ($prePopulateFeeds) { - $sql = 'SELECT c.id as c_id, c.name as c_name, c.color as c_color, count(e.id) as nbNotRead, f.* ' - . 'FROM ' . $this->prefix . 'category c ' - . 'LEFT OUTER JOIN ' . $this->prefix . 'feed f ON f.category = c.id ' - . 'LEFT OUTER JOIN ' . $this->prefix . 'entry e ON e.id_feed = f.id AND e.is_read = 0 ' - . 'GROUP BY f.id ' - . 'ORDER BY c.name, f.name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - return HelperCategory::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC)); - } else { - $sql = 'SELECT * FROM ' . $this->prefix . 'category ORDER BY name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - return HelperCategory::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC)); - } - } - - public function getDefault () { - $sql = 'SELECT * FROM ' . $this->prefix . 'category WHERE id="000000"'; - $stm = $this->bd->prepare ($sql); - - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = HelperCategory::daoToCategory ($res); - - if (isset ($cat[0])) { - return $cat[0]; - } else { - return false; - } - } - public function checkDefault () { - $def_cat = $this->searchById ('000000'); - - if ($def_cat === false) { - $cat = new Category (Translate::t ('default_category')); - $cat->_id ('000000'); - - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), - 'color' => $cat->color () - ); - - $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']; - } -} - -class HelperCategory { - 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 daoToCategoryPrepopulated ($listDAO) { - $list = array (); - - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); - } - - $previousLine = null; - $feedsDao = array(); - $nbLinesMinus1 = count($listDAO) - 1; - for ($i = 0; $i <= $nbLinesMinus1; $i++) { - $line = $listDAO[$i]; - $cat_id = $line['c_id']; - if (($i > 0) && (($cat_id !== $previousLine['c_id']) || ($i === $nbLinesMinus1))) { //End of current category - if ($i === $nbLinesMinus1) { //End of table - $feedsDao[] = $line; - } - $cat = new Category ( - $previousLine['c_name'], - $previousLine['c_color'], - HelperFeed::daoToFeed ($feedsDao) - ); - $cat->_id ($previousLine['c_id']); - $list[] = $cat; - - $feedsDao = array(); //Prepare for next category - $previousLine = $line; - $feedsDao[] = $line; - } else { - $previousLine = $line; - $feedsDao[] = $line; - } - } - - return $list; - } - - public static function daoToCategory ($listDAO) { - $list = array (); - - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); - } - - foreach ($listDAO as $key => $dao) { - $cat = new Category ( - $dao['name'], - $dao['color'] - ); - $cat->_id ($dao['id']); - $list[$key] = $cat; - } - - return $list; - } -} diff --git a/app/models/EntriesGetter.php b/app/models/EntriesGetter.php deleted file mode 100644 index 803aad732..000000000 --- a/app/models/EntriesGetter.php +++ /dev/null @@ -1,156 +0,0 @@ -<?php - -class EntriesGetter { - private $type = array ( - 'type' => 'all', - 'id' => 'all' - ); - private $state = 'all'; - private $filter = array ( - 'words' => array (), - 'tags' => array (), - ); - private $order = 'high_to_low'; - private $entries = array (); - - private $nb = 1; - private $first = ''; - private $next = ''; - - public function __construct ($type, $state, $filter, $order, $nb, $first = '') { - $this->_type ($type); - $this->_state ($state); - $this->_filter ($filter); - $this->_order ($order); - $this->nb = $nb; - $this->first = $first; - } - - public function type () { - return $this->type; - } - public function state () { - return $this->state; - } - public function filter () { - return $this->filter; - } - public function order () { - return $this->order; - } - public function entries () { - return $this->entries; - } - - public function _type ($value) { - if (!is_array ($value) || - !isset ($value['type']) || - !isset ($value['id'])) { - throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__); - } - - $type = $value['type']; - $id = $value['id']; - - if ($type != 'all' && $type != 'favoris' && $type != 'public' && $type != 'c' && $type != 'f') { - throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__); - } - - if (($type == 'all' || $type == 'favoris' || $type == 'public') && - ($type != $id)) { - throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__); - } - - $this->type = $value; - } - public function _state ($value) { - if ($value != 'all' && $value != 'not_read' && $value != 'read') { - throw new EntriesGetterException ('Bad state line ' . __LINE__ . ' in file ' . __FILE__); - } - - $this->state = $value; - } - public function _filter ($value) { - $value = trim ($value); - $terms = explode (' ', $value); - - foreach ($terms as $word) { - if (!empty ($word) && $word[0] == '#' && isset ($word[1])) { - $tag = substr ($word, 1); - $this->filter['tags'][$tag] = $tag; - } elseif (!empty ($word)) { - $this->filter['words'][$word] = $word; - } - } - } - public function _order ($value) { - if ($value != 'high_to_low' && $value != 'low_to_high') { - throw new EntriesGetterException ('Bad order line ' . __LINE__ . ' in file ' . __FILE__); - } - - $this->order = $value; - } - - public function execute () { - $entryDAO = new EntryDAO (); - - HelperEntry::$nb = $this->nb; //TODO: Update: Now done in SQL - HelperEntry::$first = $this->first; //TODO: Update: Now done in SQL - HelperEntry::$filter = $this->filter; - - $sqlLimit = (empty ($this->filter['words']) && empty ($this->filter['tags'])) ? $this->nb : ''; //Disable SQL LIMIT optimisation during search //TODO: Do better! - - switch ($this->type['type']) { - case 'all': - list ($this->entries, $this->next) = $entryDAO->listEntries ( - $this->state, - $this->order, - $this->first, - $sqlLimit - ); - break; - case 'favoris': - list ($this->entries, $this->next) = $entryDAO->listFavorites ( - $this->state, - $this->order, - $this->first, - $sqlLimit - ); - break; - case 'public': - list ($this->entries, $this->next) = $entryDAO->listPublic ( - $this->state, - $this->order, - $this->first, - $sqlLimit - ); - break; - case 'c': - list ($this->entries, $this->next) = $entryDAO->listByCategory ( - $this->type['id'], - $this->state, - $this->order, - $this->first, - $sqlLimit - ); - break; - case 'f': - list ($this->entries, $this->next) = $entryDAO->listByFeed ( - $this->type['id'], - $this->state, - $this->order, - $this->first, - $sqlLimit - ); - break; - default: - throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__); - } - } - - public function getPaginator () { - $paginator = new RSSPaginator ($this->entries, $this->next); - - return $paginator; - } -} diff --git a/app/models/Entry.php b/app/models/Entry.php deleted file mode 100755 index ed350cad8..000000000 --- a/app/models/Entry.php +++ /dev/null @@ -1,584 +0,0 @@ -<?php - -class Entry extends Model { - - private $id = null; - 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) { - $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 (array ()); - } - - public function id () { - if(is_null($this->id)) { - return small_hash ($this->guid . Configuration::selApplication ()); - } else { - return $this->id; - } - } - public function guid () { - return $this->guid; - } - public function title () { - return $this->title; - } - public function author () { - if (is_null ($this->author)) { - return ''; - } else { - return $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 isRead () { - return $this->is_read; - } - public function isFavorite () { - return $this->is_favorite; - } - public function feed ($object = false) { - if ($object) { - $feedDAO = new FeedDAO (); - return $feedDAO->searchById ($this->feed); - } else { - return $this->feed; - } - } - public function tags ($inString = false) { - if ($inString) { - if (!empty ($this->tags)) { - return '#' . implode(' #', $this->tags); - } else { - return ''; - } - } 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) { - if (is_int (intval ($value))) { - $this->date = $value; - } else { - $this->date = 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) { - $date = getdate (); - $today = mktime (0, 0, 0, $date['mon'], $date['mday'], $date['year']); - $yesterday = $today - 86400; - - if ($day == Days::TODAY && - $this->date >= $today && $this->date < $today + 86400) { - return true; - } elseif ($day == Days::YESTERDAY && - $this->date >= $yesterday && $this->date < $yesterday + 86400) { - return true; - } elseif ($day == Days::BEFORE_YESTERDAY && $this->date < $yesterday) { - return true; - } else { - 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 = new EntryDAO(); - $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( - $this->link(), $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) - ); - } -} - -class EntryDAO extends Model_pdo { - public function addEntry ($valuesTmp) { - $sql = 'INSERT INTO ' . $this->prefix . 'entry(id, guid, title, author, content, link, date, is_read, is_favorite, id_feed, tags) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - $valuesTmp['id'], - $valuesTmp['guid'], - $valuesTmp['title'], - $valuesTmp['author'], - base64_encode (gzdeflate (serialize ($valuesTmp['content']))), - $valuesTmp['link'], - $valuesTmp['date'], - $valuesTmp['is_read'], - $valuesTmp['is_favorite'], - $valuesTmp['id_feed'], - $valuesTmp['tags'], - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $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 ' . $info[0] . ': ' . $info[1] . ' ' . $info[2], Minz_Log::NOTICE); //TODO: Consider adding a Minz_Log::DEBUG level - } - return false; - } - } - - public function updateEntry ($id, $valuesTmp) { - if (isset ($valuesTmp['content'])) { - $valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content']))); - } - - $set = ''; - foreach ($valuesTmp as $key => $v) { - $set .= $key . '=?, '; - } - $set = substr ($set, 0, -2); - - $sql = 'UPDATE ' . $this->prefix . 'entry SET ' . $set . ' WHERE id=?'; - $stm = $this->bd->prepare ($sql); - - foreach ($valuesTmp as $v) { - $values[] = $v; - } - $values[] = $id; - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function markReadEntries ($read, $dateMax = 0) { - $sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE priority > 0'; - - $values = array ($read); - if ($dateMax > 0) { - $sql .= ' AND date < ?'; - $values[] = $dateMax; - } - - $stm = $this->bd->prepare ($sql); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - public function markReadCat ($id, $read, $dateMax = 0) { - $sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE category = ?'; - - $values = array ($read, $id); - if ($dateMax > 0) { - $sql .= ' AND date < ?'; - $values[] = $dateMax; - } - - $stm = $this->bd->prepare ($sql); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - public function markReadFeed ($id, $read, $dateMax = 0) { - $sql = 'UPDATE ' . $this->prefix . 'entry SET is_read = ? WHERE id_feed = ?'; - - $values = array ($read, $id); - if ($dateMax > 0) { - $sql .= ' AND date < ?'; - $values[] = $dateMax; - } - - $stm = $this->bd->prepare ($sql); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function updateEntries ($valuesTmp) { - if (isset ($valuesTmp['content'])) { - $valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content']))); - } - - $set = ''; - foreach ($valuesTmp as $key => $v) { - $set .= $key . '=?, '; - } - $set = substr ($set, 0, -2); - - $sql = 'UPDATE ' . $this->prefix . 'entry SET ' . $set; - $stm = $this->bd->prepare ($sql); - - foreach ($valuesTmp as $v) { - $values[] = $v; - } - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function cleanOldEntries ($nb_month) { - $date = 60 * 60 * 24 * 30 * $nb_month; - $sql = 'DELETE e.* FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE e.date <= ? AND e.is_favorite = 0 AND f.keep_history = 0'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - time () - $date - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function searchByGuid ($feed_id, $id) { - // un guid est unique pour un flux donné - $sql = 'SELECT * 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); - list ($entry, $next) = HelperEntry::daoToEntry ($res); - - if (isset ($entry[0])) { - return $entry[0]; - } else { - return false; - } - } - - public function searchById ($id) { - $sql = 'SELECT * FROM ' . $this->prefix . 'entry WHERE id=?'; - $stm = $this->bd->prepare ($sql); - - $values = array ($id); - - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - list ($entry, $next) = HelperEntry::daoToEntry ($res); - - if (isset ($entry[0])) { - return $entry[0]; - } else { - return false; - } - } - - private function listWhere ($where, $state, $order, $limitFromId = '', $limitCount = '', $values = array ()) { - if ($state == 'not_read') { - $where .= ' AND is_read = 0'; - } elseif ($state == 'read') { - $where .= ' AND is_read = 1'; - } - if (!empty($limitFromId)) { //TODO: Consider using LPAD(e.date, 11) //CONCAT is for cases when many entries have the same date - $where .= ' AND CONCAT(e.date, e.id) ' . ($order === 'low_to_high' ? '<=' : '>=') . ' (SELECT CONCAT(s.date, s.id) FROM ' . $this->prefix . 'entry s WHERE s.id = "' . $limitFromId . '")'; - } - - if ($order == 'low_to_high') { - $order = ' DESC'; - } else { - $order = ''; - } - - $sql = 'SELECT e.* FROM ' . $this->prefix . 'entry e' - . ' INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id' . $where - . ' ORDER BY e.date' . $order . ', e.id' . $order; - - if (!empty($limitCount)) { - $sql .= ' LIMIT ' . ($limitCount + 2); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ - } - - $stm = $this->bd->prepare ($sql); - $stm->execute ($values); - - return HelperEntry::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); - } - public function listEntries ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') { - return $this->listWhere (' WHERE priority > 0', $state, $order, $limitFromId, $limitCount); - } - public function listFavorites ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') { - return $this->listWhere (' WHERE is_favorite = 1', $state, $order, $limitFromId, $limitCount); - } - public function listPublic ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') { - return $this->listWhere (' WHERE is_public = 1', $state, $order, $limitFromId, $limitCount); - } - public function listByCategory ($cat, $state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') { - return $this->listWhere (' WHERE category = ?', $state, $order, $limitFromId, $limitCount, array ($cat)); - } - public function listByFeed ($feed, $state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') { - return $this->listWhere (' WHERE id_feed = ?', $state, $order, $limitFromId, $limitCount, array ($feed)); - } - - public function countUnreadRead () { - $sql = 'SELECT is_read, COUNT(*) AS count FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE priority > 0 GROUP BY is_read'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - - $readUnread = array('unread' => 0, 'read' => 0); - foreach ($res as $line) { - switch (intval($line['is_read'])) { - case 0: $readUnread['unread'] = intval($line['count']); break; - case 1: $readUnread['read'] = intval($line['count']); break; - } - } - return $readUnread; - } - public function count () { //Deprecated: use countUnreadRead() instead - $unreadRead = $this->countUnreadRead (); //This makes better use of caching - return $unreadRead['unread'] + $unreadRead['read']; - } - public function countNotRead () { //Deprecated: use countUnreadRead() instead - $unreadRead = $this->countUnreadRead (); //This makes better use of caching - return $unreadRead['unread']; - } - - public function countUnreadReadFavorites () { - $sql = 'SELECT is_read, COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE is_favorite=1 GROUP BY is_read'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $readUnread = array('unread' => 0, 'read' => 0); - foreach ($res as $line) { - switch (intval($line['is_read'])) { - case 0: $readUnread['unread'] = intval($line['count']); break; - case 1: $readUnread['read'] = intval($line['count']); break; - } - } - return $readUnread; - } - - public function countFavorites () { //Deprecated: use countUnreadReadFavorites() instead - $unreadRead = $this->countUnreadReadFavorites (); //This makes better use of caching - return $unreadRead['unread'] + $unreadRead['read']; - } - - public function optimizeTable() { - $sql = 'OPTIMIZE TABLE ' . $this->prefix . 'entry'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - } -} - -class HelperEntry { - public static $nb = 1; - public static $first = ''; - - public static $filter = array ( - 'words' => array (), - 'tags' => array (), - ); - - public static function daoToEntry ($listDAO) { - $list = array (); - - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); - } - - $count = 0; - $first_is_found = false; - $break_after = false; - $next = ''; - foreach ($listDAO as $key => $dao) { - $dao['content'] = unserialize (gzinflate (base64_decode ($dao['content']))); - $dao['tags'] = preg_split('/[\s#]/', $dao['tags']); - - if (self::tagsMatchEntry ($dao) && - self::searchMatchEntry ($dao)) { - if ($break_after) { - $next = $dao['id']; - break; - } - if ($first_is_found || $dao['id'] == self::$first || self::$first == '') { - $list[$key] = self::createEntry ($dao); - - $count++; - $first_is_found = true; //TODO: Update: Now done in SQL - } - if ($count >= self::$nb) { - $break_after = true; - } - } - } - - unset ($listDAO); - - return array ($list, $next); - } - - private static function createEntry ($dao) { - $entry = new Entry ( - $dao['id_feed'], - $dao['guid'], - $dao['title'], - $dao['author'], - $dao['content'], - $dao['link'], - $dao['date'], - $dao['is_read'], - $dao['is_favorite'] - ); - - $entry->_tags ($dao['tags']); - - if (isset ($dao['id'])) { - $entry->_id ($dao['id']); - } - - return $entry; - } - - private static function tagsMatchEntry ($dao) { - $tags = self::$filter['tags']; - foreach ($tags as $tag) { - if (!in_array ($tag, $dao['tags'])) { - return false; - } - } - - return true; - } - private static function searchMatchEntry ($dao) { - $words = self::$filter['words']; - - foreach ($words as $word) { - $word = strtolower ($word); - if (strpos (strtolower ($dao['title']), $word) === false && - strpos (strtolower ($dao['content']), $word) === false && - strpos (strtolower ($dao['link']), $word) === false) { - return false; - } - } - - return true; - } -} diff --git a/app/models/Exception/FeedException.php b/app/models/Exception/FeedException.php deleted file mode 100644 index bff297eb9..000000000 --- a/app/models/Exception/FeedException.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php - -class FeedException extends Exception { - public function __construct ($message) { - parent::__construct ($message); - } -} - -class BadUrlException extends FeedException { - public function __construct ($url) { - parent::__construct ('`' . $url . '` is not a valid URL'); - } -} - -class OpmlException extends FeedException { - public function __construct ($name_file) { - parent::__construct ('OPML file is invalid'); - } -} diff --git a/app/models/Feed.php b/app/models/Feed.php deleted file mode 100644 index 02a4e6be7..000000000 --- a/app/models/Feed.php +++ /dev/null @@ -1,541 +0,0 @@ -<?php - -class Feed extends Model { - private $id = null; - private $url; - private $category = '000000'; - 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 = false; - - public function __construct ($url) { - $this->_url ($url); - } - - public function id () { - if(is_null($this->id)) { - return small_hash ($this->url . Configuration::selApplication ()); - } else { - return $this->id; - } - } - public function url () { - return $this->url; - } - public function category () { - return $this->category; - } - public function entries () { - if (!is_null ($this->entries)) { - return $this->entries; - } else { - return array (); - } - } - 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 nbEntries () { - if ($this->nbEntries < 0) { - $feedDAO = new FeedDAO (); - $this->nbEntries = $feedDAO->countEntries ($this->id ()); - } - - return $this->nbEntries; - } - public function nbNotRead () { - if ($this->nbNotRead < 0) { - $feedDAO = new FeedDAO (); - $this->nbNotRead = $feedDAO->countNotRead ($this->id ()); - } - - return $this->nbNotRead; - } - public function favicon () { - $file = '/data/favicons/' . $this->id () . '.ico'; - - $favicon_url = Url::display ($file); - if (!file_exists (PUBLIC_PATH . $file)) { - $favicon_url = dowload_favicon ($this->website (), $this->id ()); - } - - return $favicon_url; - } - - public function _id ($value) { - $this->id = $value; - } - public function _url ($value) { - if (!is_null ($value) && !preg_match ('#^https?://#', $value)) { - $value = 'http://' . $value; - } - - if (!is_null ($value) && filter_var ($value, FILTER_VALIDATE_URL)) { - $this->url = $value; - } else { - throw new BadUrlException ($value); - } - } - public function _category ($value) { - $this->category = $value; - } - public function _name ($value) { - if (is_null ($value)) { - $value = ''; - } - $this->name = $value; - } - public function _website ($value) { - if (is_null ($value)) { - $value = ''; - } - $this->website = $value; - } - public function _description ($value) { - if (is_null ($value)) { - $value = ''; - } - $this->description = $value; - } - public function _lastUpdate ($value) { - $this->lastUpdate = $value; - } - public function _priority ($value) { - if (!is_int (intval ($value))) { - $value = 10; - } - $this->priority = $value; - } - public function _pathEntries ($value) { - $this->pathEntries = $value; - } - public function _httpAuth ($value) { - $this->httpAuth = $value; - } - public function _error ($value) { - if ($value) { - $value = true; - } else { - $value = false; - } - $this->error = $value; - } - public function _keepHistory ($value) { - if ($value) { - $value = true; - } else { - $value = false; - } - $this->keep_history = $value; - } - public function _nbNotRead ($value) { - if (!is_int ($value)) { - $value = -1; - } - - $this->nbNotRead = intval ($value); - } - - public function load () { - if (!is_null ($this->url)) { - if (CACHE_PATH === false) { - throw new FileNotExistException ( - 'CACHE_PATH', - MinzException::ERROR - ); - } else { - $feed = new SimplePie (); - $url = str_replace ('&', '&', $this->url); - if ($this->httpAuth != '') { - $url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url); - } - - $feed->set_feed_url ($url); - $feed->set_cache_location (CACHE_PATH); - $feed->strip_htmltags (array ( - 'base', 'blink', 'body', 'doctype', - 'font', 'form', 'frame', 'frameset', 'html', - 'input', 'marquee', 'meta', 'noscript', - 'param', 'script', 'style' - )); - $feed->init (); - - if ($feed->error ()) { - throw new FeedException ($feed->error); - } - - // si on a utilisé l'auto-discover, notre url va avoir changé - $subscribe_url = $feed->subscribe_url (); - if (!is_null ($subscribe_url) && $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 (empty($this->name)) { // May come from OPML - $title = $feed->get_title (); - $this->_name (!is_null ($title) ? $title : $this->url); - } - - $this->_website ($feed->get_link ()); - $this->_description ($feed->get_description ()); - - // et on charge les articles du flux - $this->loadEntries ($feed); - } - } - } - private function loadEntries ($feed) { - $entries = array (); - - foreach ($feed->get_items () as $item) { - $title = 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 (!is_null ($tags_tmp)) { - foreach ($tags_tmp as $tag) { - $tags[] = $tag->get_label (); - } - } - - $content = $item->get_content (); - - $entry = new Entry ( - $this->id (), - $item->get_id (), - !is_null ($title) ? $title : '', - !is_null ($author) ? $author->name : '', - !is_null ($content) ? $content : '', - !is_null ($link) ? $link : '', - $date ? $date : time () - ); - $entry->_tags ($tags); - // permet de récupérer le contenu des flux tronqués - $entry->loadCompleteContent($this->pathEntries()); - - $entries[$entry->id ()] = $entry; - } - - $this->entries = $entries; - } -} - -class FeedDAO extends Model_pdo { - public function addFeed ($valuesTmp) { - $sql = 'INSERT INTO ' . $this->prefix . 'feed (id, url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, ?, 10, ?, 0, 0)'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - $valuesTmp['id'], - $valuesTmp['url'], - $valuesTmp['category'], - $valuesTmp['name'], - $valuesTmp['website'], - $valuesTmp['description'], - $valuesTmp['lastUpdate'], - base64_encode ($valuesTmp['httpAuth']), - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - 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 true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function updateLastUpdate ($id) { - $sql = 'UPDATE ' . $this->prefix . 'feed SET lastUpdate=?, error=0 WHERE id=?'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - time (), - $id - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function isInError ($id) { - $sql = 'UPDATE ' . $this->prefix . 'feed SET error=1 WHERE id=?'; - $stm = $this->bd->prepare ($sql); - - $values = array ( - $id - ); - - if ($stm && $stm->execute ($values)) { - return true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - } - } - - public function changeCategory ($idOldCat, $idNewCat) { - $catDAO = new 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 true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $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 true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $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 true; - } else { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $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 = HelperFeed::daoToFeed ($res); - - if (isset ($feed[$id])) { - return $feed[$id]; - } else { - return false; - } - } - 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 (HelperFeed::daoToFeed ($res)); - - if (isset ($feed)) { - return $feed; - } else { - return false; - } - } - - public function listFeeds () { - $sql = 'SELECT * FROM ' . $this->prefix . 'feed ORDER BY name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - - return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); - } - - public function listFeedsOrderUpdate () { - $sql = 'SELECT * FROM ' . $this->prefix . 'feed ORDER BY lastUpdate'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - - return HelperFeed::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 HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); - } - - public function count () { - $sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'feed'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - - return $res[0]['count']; - } - - 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 is_read=0 AND id_feed=?'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - - return $res[0]['count']; - } -} - -class HelperFeed { - public static function daoToFeed ($listDAO) { - $list = array (); - - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); - } - - foreach ($listDAO as $key => $dao) { - if (empty ($dao['url'])) { - continue; - } - if (isset ($dao['id'])) { - $key = $dao['id']; - } - - $list[$key] = new Feed ($dao['url']); - $list[$key]->_category ($dao['category']); - $list[$key]->_name ($dao['name']); - $list[$key]->_website ($dao['website']); - $list[$key]->_description ($dao['description']); - $list[$key]->_lastUpdate ($dao['lastUpdate']); - $list[$key]->_priority ($dao['priority']); - $list[$key]->_pathEntries ($dao['pathEntries']); - $list[$key]->_httpAuth (base64_decode ($dao['httpAuth'])); - $list[$key]->_error ($dao['error']); - $list[$key]->_keepHistory ($dao['keep_history']); - if (isset ($dao['nbNotRead'])) { - $list[$key]->_nbNotRead ($dao['nbNotRead']); - } - if (isset ($dao['id'])) { - $list[$key]->_id ($dao['id']); - } - } - - return $list; - } -} diff --git a/app/models/Log.php b/app/models/Log.php deleted file mode 100644 index 5c280fa7a..000000000 --- a/app/models/Log.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -class Log_Model extends 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; - } -} - -class LogDAO extends Model_txt { - public function __construct () { - parent::__construct (LOG_PATH . '/application.log', 'r+'); - } - - public function lister () { - $logs = array (); - - $i = 0; - while (($line = $this->readLine ()) !== false) { - $logs[$i] = new Log_Model (); - $logs[$i]->_date (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\1", $line)); - $logs[$i]->_level (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\2", $line)); - $logs[$i]->_info (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\3", $line)); - $i++; - } - - return $logs; - } -}
\ No newline at end of file diff --git a/app/models/RSSConfiguration.php b/app/models/RSSConfiguration.php deleted file mode 100755 index 038ce2a2c..000000000 --- a/app/models/RSSConfiguration.php +++ /dev/null @@ -1,321 +0,0 @@ -<?php - -class RSSConfiguration extends Model { - private $available_languages = array ( - 'en' => 'English', - 'fr' => 'Français', - ); - private $language; - private $posts_per_page; - private $view_mode; - private $default_view; - private $display_posts; - private $onread_jump_next; - private $lazyload; - private $sort_order; - private $old_entries; - private $shortcuts = array (); - private $mail_login = ''; - private $mark_when = array (); - private $url_shaarli = ''; - private $theme; - private $anon_access; - private $token; - private $auto_load_more; - - public function __construct () { - $confDAO = new RSSConfigurationDAO (); - $this->_language ($confDAO->language); - $this->_postsPerPage ($confDAO->posts_per_page); - $this->_viewMode ($confDAO->view_mode); - $this->_defaultView ($confDAO->default_view); - $this->_displayPosts ($confDAO->display_posts); - $this->_onread_jump_next ($confDAO->onread_jump_next); - $this->_lazyload ($confDAO->lazyload); - $this->_sortOrder ($confDAO->sort_order); - $this->_oldEntries ($confDAO->old_entries); - $this->_shortcuts ($confDAO->shortcuts); - $this->_mailLogin ($confDAO->mail_login); - $this->_markWhen ($confDAO->mark_when); - $this->_urlShaarli ($confDAO->url_shaarli); - $this->_theme ($confDAO->theme); - $this->_anonAccess ($confDAO->anon_access); - $this->_token ($confDAO->token); - $this->_autoLoadMore ($confDAO->auto_load_more); - } - - public function availableLanguages () { - return $this->available_languages; - } - public function language () { - return $this->language; - } - public function postsPerPage () { - return $this->posts_per_page; - } - public function viewMode () { - return $this->view_mode; - } - public function defaultView () { - return $this->default_view; - } - public function displayPosts () { - return $this->display_posts; - } - public function onread_jump_next () { - return $this->onread_jump_next; - } - public function lazyload () { - return $this->lazyload; - } - public function sortOrder () { - return $this->sort_order; - } - public function oldEntries () { - return $this->old_entries; - } - public function shortcuts () { - return $this->shortcuts; - } - public function mailLogin () { - return $this->mail_login; - } - public function markWhen () { - return $this->mark_when; - } - public function markWhenArticle () { - return $this->mark_when['article']; - } - public function markWhenSite () { - return $this->mark_when['site']; - } - public function markWhenScroll () { - return $this->mark_when['scroll']; - } - public function urlShaarli () { - return $this->url_shaarli; - } - public function theme () { - return $this->theme; - } - public function anonAccess () { - return $this->anon_access; - } - public function token () { - return $this->token; - } - public function autoLoadMore () { - return $this->auto_load_more; - } - - public function _language ($value) { - if (!isset ($this->available_languages[$value])) { - $value = 'en'; - } - $this->language = $value; - } - public function _postsPerPage ($value) { - if (is_int (intval ($value)) && $value > 0) { - $this->posts_per_page = $value; - } else { - $this->posts_per_page = 10; - } - } - public function _viewMode ($value) { - if ($value == 'global' || $value == 'reader') { - $this->view_mode = $value; - } else { - $this->view_mode = 'normal'; - } - } - public function _defaultView ($value) { - if ($value == 'not_read') { - $this->default_view = 'not_read'; - } else { - $this->default_view = 'all'; - } - } - public function _displayPosts ($value) { - if ($value == 'yes') { - $this->display_posts = 'yes'; - } else { - $this->display_posts = 'no'; - } - } - public function _onread_jump_next ($value) { - if ($value == 'no') { - $this->onread_jump_next = 'no'; - } else { - $this->onread_jump_next = 'yes'; - } - } - public function _lazyload ($value) { - if ($value == 'no') { - $this->lazyload = 'no'; - } else { - $this->lazyload = 'yes'; - } - } - public function _sortOrder ($value) { - if ($value == 'high_to_low') { - $this->sort_order = 'high_to_low'; - } else { - $this->sort_order = 'low_to_high'; - } - } - public function _oldEntries ($value) { - if (is_int (intval ($value)) && $value > 0) { - $this->old_entries = $value; - } else { - $this->old_entries = 3; - } - } - public function _shortcuts ($values) { - foreach ($values as $key => $value) { - $this->shortcuts[$key] = $value; - } - } - public function _mailLogin ($value) { - if (filter_var ($value, FILTER_VALIDATE_EMAIL)) { - $this->mail_login = $value; - } elseif ($value == false) { - $this->mail_login = false; - } - } - public function _markWhen ($values) { - if(!isset($values['article'])) { - $values['article'] = 'yes'; - } - if(!isset($values['site'])) { - $values['site'] = 'yes'; - } - if(!isset($values['scroll'])) { - $values['scroll'] = 'yes'; - } - - $this->mark_when['article'] = $values['article']; - $this->mark_when['site'] = $values['site']; - $this->mark_when['scroll'] = $values['scroll']; - } - public function _urlShaarli ($value) { - $this->url_shaarli = ''; - if (filter_var ($value, FILTER_VALIDATE_URL)) { - $this->url_shaarli = $value; - } - } - public function _theme ($value) { - $this->theme = $value; - } - public function _anonAccess ($value) { - if ($value == 'yes') { - $this->anon_access = 'yes'; - } else { - $this->anon_access = 'no'; - } - } - public function _token ($value) { - $this->token = $value; - } - public function _autoLoadMore ($value) { - if ($value == 'yes') { - $this->auto_load_more = 'yes'; - } else { - $this->auto_load_more = 'no'; - } - } -} - -class RSSConfigurationDAO extends Model_array { - public $language = 'en'; - public $posts_per_page = 20; - public $view_mode = 'normal'; - public $default_view = 'not_read'; - public $display_posts = 'no'; - public $onread_jump_next = 'yes'; - public $lazyload = 'yes'; - public $sort_order = 'low_to_high'; - public $old_entries = 3; - public $shortcuts = array ( - 'mark_read' => 'r', - 'mark_favorite' => 'f', - 'go_website' => 'space', - 'next_entry' => 'j', - 'prev_entry' => 'k' - ); - public $mail_login = ''; - public $mark_when = array ( - 'article' => 'yes', - 'site' => 'yes', - 'scroll' => 'no' - ); - public $url_shaarli = ''; - public $theme = 'default'; - public $anon_access = 'no'; - public $token = ''; - public $auto_load_more = 'no'; - - public function __construct () { - parent::__construct (PUBLIC_PATH . '/data/Configuration.array.php'); - - // TODO : simplifier ce code, une boucle for() devrait suffir ! - if (isset ($this->array['language'])) { - $this->language = $this->array['language']; - } - if (isset ($this->array['posts_per_page'])) { - $this->posts_per_page = $this->array['posts_per_page']; - } - if (isset ($this->array['view_mode'])) { - $this->view_mode = $this->array['view_mode']; - } - if (isset ($this->array['default_view'])) { - $this->default_view = $this->array['default_view']; - } - if (isset ($this->array['display_posts'])) { - $this->display_posts = $this->array['display_posts']; - } - if (isset ($this->array['onread_jump_next'])) { - $this->onread_jump_next = $this->array['onread_jump_next']; - } - if (isset ($this->array['lazyload'])) { - $this->lazyload = $this->array['lazyload']; - } - if (isset ($this->array['sort_order'])) { - $this->sort_order = $this->array['sort_order']; - } - if (isset ($this->array['old_entries'])) { - $this->old_entries = $this->array['old_entries']; - } - if (isset ($this->array['shortcuts'])) { - $this->shortcuts = $this->array['shortcuts']; - } - if (isset ($this->array['mail_login'])) { - $this->mail_login = $this->array['mail_login']; - } - if (isset ($this->array['mark_when'])) { - $this->mark_when = $this->array['mark_when']; - } - if (isset ($this->array['url_shaarli'])) { - $this->url_shaarli = $this->array['url_shaarli']; - } - if (isset ($this->array['theme'])) { - $this->theme = $this->array['theme']; - } - if (isset ($this->array['anon_access'])) { - $this->anon_access = $this->array['anon_access']; - } - if (isset ($this->array['token'])) { - $this->token = $this->array['token']; - } - if (isset ($this->array['auto_load_more'])) { - $this->auto_load_more = $this->array['auto_load_more']; - } - } - - public function update ($values) { - foreach ($values as $key => $value) { - $this->array[$key] = $value; - } - - $this->writeFile($this->array); - } -} diff --git a/app/models/RSSPaginator.php b/app/models/RSSPaginator.php deleted file mode 100644 index 7010291bc..000000000 --- a/app/models/RSSPaginator.php +++ /dev/null @@ -1,29 +0,0 @@ -<?php - -// Un système de pagination beaucoup plus simple que Paginator -// mais mieux adapté à nos besoins -class RSSPaginator { - private $items = array (); - private $next = ''; - - public function __construct ($items, $next) { - $this->items = $items; - $this->next = $next; - } - - public function isEmpty () { - return empty ($this->items); - } - - public function items () { - return $this->items; - } - - public function render ($view, $getteur) { - $view = APP_PATH . '/views/helpers/'.$view; - - if (file_exists ($view)) { - include ($view); - } - } -} diff --git a/app/models/RSSThemes.php b/app/models/RSSThemes.php deleted file mode 100644 index 83db85acf..000000000 --- a/app/models/RSSThemes.php +++ /dev/null @@ -1,47 +0,0 @@ -<?php - -class RSSThemes extends Model { - private static $themes_dir = '/themes'; - - private static $list = array(); - - public static function init() { - $basedir = PUBLIC_PATH . self::$themes_dir; - - $themes_list = array_diff( - scandir($basedir), - array('..', '.') - ); - - foreach ($themes_list as $theme_dir) { - $json_filename = $basedir . '/' . $theme_dir . '/metadata.json'; - if(file_exists($json_filename)) { - $content = file_get_contents($json_filename); - $res = json_decode($content, true); - - if($res && - isset($res['name']) && - isset($res['author']) && - isset($res['description']) && - isset($res['version']) && - isset($res['files']) && is_array($res['files'])) { - $theme = $res; - $theme['path'] = $theme_dir; - self::$list[$theme_dir] = $theme; - } - } - } - } - - public static function get() { - return self::$list; - } - - public static function get_infos($theme_id) { - if (isset(self::$list[$theme_id])) { - return self::$list[$theme_id]; - } - - return false; - } -}
\ No newline at end of file diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml new file mode 100644 index 000000000..c9cc7fe02 --- /dev/null +++ b/app/views/configure/archiving.phtml @@ -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> diff --git a/app/views/configure/categorize.phtml b/app/views/configure/categorize.phtml index 9dac49ff8..23d1c9fa1 100644 --- a/app/views/configure/categorize.phtml +++ b/app/views/configure/categorize.phtml @@ -1,43 +1,55 @@ -<?php $this->partial ('aside_configure'); ?> +<?php $this->partial ('aside_feed'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <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 Translate::t ('categories_management'); ?> - <a href="<?php echo _url ('configure', 'feed'); ?>"><?php echo Translate::t ('rss_feed_management'); ?></a></legend> + <legend><?php echo Minz_Translate::t ('categories_management'); ?></legend> - <p class="alert alert-warn"><?php echo Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p> + <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 Translate::t ('category_number', $i); ?> + <?php echo Minz_Translate::t ('category_number', $i); ?> </label> <div class="group-controls"> - <input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" /> - <a class="confirm" href="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Translate::t ('ask_empty'); ?></a> (<?php echo Translate::t ('number_feeds', $cat->nbFeed ()); ?>) - <?php if ($cat->id () == $this->defaultCategory->id ()) { ?> - <i class="icon i_help"></i> <?php echo Translate::t ('can_not_be_deleted'); ?> + <div class="stick"> + <input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" /> + + <?php if ($cat->nbFeed () > 0) { ?> + <a class="btn" href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id ()); ?>"> + <?php echo _i('link'); ?> + </a> + <button formaction="<?php echo _url('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>" + class="btn btn-attention confirm" + data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" + type="submit"><?php echo _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 Translate::t ('add_category'); ?></label> + <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 Translate::t ('new_category'); ?>" /> + <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 Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button> + <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 $this->renderHelper ('confirm_action_script'); ?> diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 0f89437ac..8eb3a156b 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -1,184 +1,108 @@ <?php $this->partial ('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <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 Translate::t ('general_configuration'); ?></legend> + <legend><?php echo Minz_Translate::t ('display_configuration'); ?></legend> <div class="form-group"> - <label class="group-name" for="language"><?php echo Translate::t ('language'); ?></label> + <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> + <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 Translate::t ('theme'); ?></label> + <label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label> <div class="group-controls"> - <select name="theme" id="theme"> - <?php foreach ($this->themes as $theme) { ?> - <option value="<?php echo $theme['path']; ?>"<?php echo $this->conf->theme () == $theme['path'] ? ' selected="selected"' : ''; ?>> - <?php echo $theme['name'] . ' ' . Translate::t ('by') . ' ' . $theme['author']; ?> - </option> - <?php } ?> - </select> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="old_entries"><?php echo Translate::t ('delete_articles_every'); ?></label> - <div class="group-controls"> - <input type="number" id="old_entries" name="old_entries" value="<?php echo $this->conf->oldEntries (); ?>" /> <?php echo Translate::t ('month'); ?> + <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="mail_login"><?php echo Translate::t ('persona_connection_email'); ?></label> - <?php $mail = $this->conf->mailLogin (); ?> - <div class="group-controls"> - <input type="email" id="mail_login" name="mail_login" value="<?php echo $mail ? $mail : ''; ?>" placeholder="<?php echo Translate::t ('blank_to_disable'); ?>" /> - <noscript><b><?php echo Translate::t ('javascript_should_be_activated'); ?></b></noscript> - <label class="checkbox" for="anon_access"> - <input type="checkbox" name="anon_access" id="anon_access" value="yes"<?php echo $this->conf->anonAccess () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('allow_anonymous'); ?> - </label> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="token"><?php echo 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 Translate::t ('blank_to_disable'); ?>"/> - <i class="icon i_help"></i> <?php echo Translate::t('explain_token', Url::display(), $token); ?> - </div> - </div> - - <legend><?php echo Translate::t ('reading_configuration'); ?></legend> - - <div class="form-group"> - <label class="group-name" for="posts_per_page"><?php echo 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->postsPerPage (); ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="sort_order"><?php echo Translate::t ('sort_order'); ?></label> - <div class="group-controls"> - <select name="sort_order" id="sort_order"> - <option value="low_to_high"<?php echo $this->conf->sortOrder () == 'low_to_high' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('newer_first'); ?></option> - <option value="high_to_low"<?php echo $this->conf->sortOrder () == 'high_to_low' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('older_first'); ?></option> - </select> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="view_mode"><?php echo Translate::t ('default_view'); ?></label> - <div class="group-controls"> - <select name="view_mode" id="view_mode"> - <option value="normal"<?php echo $this->conf->viewMode () == 'normal' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('normal_view'); ?></option> - <option value="reader"<?php echo $this->conf->viewMode () == 'reader' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('reader_view'); ?></option> - <option value="global"<?php echo $this->conf->viewMode () == 'global' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('global_view'); ?></option> + <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> - <label class="radio" for="radio_all"> - <input type="radio" name="default_view" id="radio_all" value="all"<?php echo $this->conf->defaultView () == 'all' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('show_all_articles'); ?> - </label> - <label class="radio" for="radio_not_read"> - <input type="radio" name="default_view" id="radio_not_read" value="not_read"<?php echo $this->conf->defaultView () == 'not_read' ? ' checked="checked"' : ''; ?> /> - <?php echo 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="yes"<?php echo $this->conf->autoLoadMore () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('auto_load_more'); ?> - <?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></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="yes"<?php echo $this->conf->displayPosts () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('display_articles_unfolded'); ?> - <?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></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="yes"<?php echo $this->conf->lazyload () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('img_with_lazyload'); ?> - <?php echo $this->conf->lazyload () == 'yes' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?> - </label> - </div> - </div> - - <div class="form-group"> - <label class="group-name"><?php echo 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="yes"<?php echo $this->conf->markWhenArticle () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('article_selected'); ?> - </label> - <label class="checkbox" for="check_open_site"> - <input type="checkbox" name="mark_open_site" id="check_open_site" value="yes"<?php echo $this->conf->markWhenSite () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('article_open_on_website'); ?> - </label> - <label class="checkbox" for="check_scroll"> - <input type="checkbox" name="mark_scroll" id="check_scroll" value="yes"<?php echo $this->conf->markWhenScroll () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('scroll'); ?> - </label> - </div> - </div> - - <div class="form-group"> - <label class="group-name"><?php echo 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="yes"<?php echo $this->conf->onread_jump_next () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('jump_next'); ?> - </label> - </div> - </div> - - <legend><?php echo Translate::t ('sharing'); ?></legend> - <div class="form-group"> - <label class="group-name" for="shaarli"><?php echo Translate::t ('your_shaarli'); ?></label> - <div class="group-controls"> - <input type="text" id="shaarli" name="shaarli" value="<?php echo $this->conf->urlShaarli (); ?>" placeholder="<?php echo Translate::t ('blank_to_disable'); ?>"/> - </div> - </div> - - <legend><?php echo Translate::t ('advanced'); ?></legend> + <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"> - <label class="group-name"></label> + <label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('html5_notif_timeout'); ?></label> <div class="group-controls"> - <a class="btn" href="<?php echo _url('entry', 'optimize'); ?>"> - <?php echo Translate::t('optimize_bdd'); ?> - </a> - <i class="icon i_help"></i> <?php echo Translate::t('optimize_todo_sometimes'); ?> + <input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo $this->conf->html5_notif_timeout; ?>" /> <?php echo Minz_Translate::t ('seconds_(0_means_no_timeout)'); ?> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button> + <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> diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index 49e2f3cf1..e96a28739 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -2,62 +2,55 @@ <?php if ($this->flux) { ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Translate::t ('filter'); ?></a> + <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 Translate::t ('damn'); ?></span> <?php echo Translate::t ('feed_in_error'); ?></p> + <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 ()); ?>"> - <legend><?php echo Translate::t ('informations'); ?></legend> - <div class="form-group"> - <label class="group-name" for="name"><?php echo Translate::t ('title'); ?></label> - <div class="group-controls"> - <input type="text" name="name" id="name" value="<?php echo $this->flux->name () ; ?>" /> - </div> - </div> + <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"><?php echo Translate::t ('website_url'); ?></label> + <label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label> <div class="group-controls"> - <span class="control"> - <?php echo $this->flux->website (); ?> - <a target="_blank" href="<?php echo $this->flux->website (); ?>"><i class="icon i_link"></i></a> - </span> + <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"><?php echo Translate::t ('feed_url'); ?></label> + <label class="group-name" for="description"><?php echo Minz_Translate::t ('feed_description'); ?></label> <div class="group-controls"> - <span class="control"> - <?php echo $this->flux->url (); ?> - <a target="_blank" href="<?php echo $this->flux->url (); ?>"><i class="icon i_link"></i></a> - </span> + <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"></label> + <label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label> <div class="group-controls"> - <a class="btn" href="<?php echo _url ('feed', 'actualize', 'id', $this->flux->id ()); ?>"> - <i class="icon i_refresh"></i> <?php echo Translate::t('actualize'); ?> - </a> + <div class="stick"> + <input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" /> + <a class="btn" target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a> + </div> </div> </div> <div class="form-group"> - <label class="group-name"><?php echo Translate::t ('number_articles'); ?></label> + <label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label> <div class="group-controls"> - <span class="control"><?php echo $this->flux->nbEntries (); ?></span> - <label class="checkbox" for="keep_history"> - <input type="checkbox" name="keep_history" id="keep_history" value="yes"<?php echo $this->flux->keepHistory () == 'yes' ? ' checked="checked"' : ''; ?> /> - <?php echo Translate::t ('keep_history'); ?> - </label> + <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 Translate::t ('category'); ?></label> + <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) { ?> @@ -66,54 +59,124 @@ </option> <?php } ?> </select> - <a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Translate::t ('categories_management'); ?></a> </div> </div> - - <legend><?php echo Translate::t ('advanced'); ?></legend> <div class="form-group"> - <label class="group-name" for="priority"><?php echo Translate::t ('show_in_all_flux'); ?></label> + <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 Translate::t ('yes'); ?> + <?php echo Minz_Translate::t ('yes'); ?> </label> </div> </div> <div class="form-group"> - <label class="group-name" for="path_entries"><?php echo Translate::t ('css_path_on_website'); ?></label> <div class="group-controls"> - <input type="text" name="path_entries" id="path_entries" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Translate::t ('blank_to_disable'); ?>" /> - <i class="icon i_help"></i> <?php echo Translate::t ('retrieve_truncated_feeds'); ?> + <a href="<?php echo _url('stats', 'repartition', 'id', $this->flux->id()); ?>"> + <?php echo _i('stats'); ?> <?php echo _t('stats'); ?> + </a> + </div> + </div> + <div class="form-group form-actions"> + <div class="group-controls"> + <button class="btn btn-important"><?php echo _t('save'); ?></button> + <button class="btn btn-attention confirm" + data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" + formaction="<?php echo _url('feed', 'delete', 'id', $this->flux->id ()); ?>" + formmethod="post"><?php echo _t('delete'); ?></button> </div> </div> + <legend><?php echo Minz_Translate::t ('archiving_configuration'); ?></legend> + + <div class="form-group"> + <div class="group-controls"> + <div class="stick"> + <input type="text" value="<?php echo _t('number_articles', $nbEntries); ?>" disabled="disabled" /> + <a class="btn" href="<?php echo _url('feed', 'actualize', 'id', $this->flux->id ()); ?>"> + <?php echo _i('refresh'); ?> <?php echo _t('actualize'); ?> + </a> + </div> + </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 Translate::t ('http_username'); ?></label> + <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" value="<?php echo $auth['username']; ?>" /> - <i class="icon i_help"></i> <?php echo Translate::t ('access_protected_feeds'); ?> + <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 Translate::t ('http_password'); ?></label> + <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" value="<?php echo $auth['password']; ?>" /> + <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 class="btn btn-important"><?php echo Translate::t ('save'); ?></button> - <button class="btn btn-attention confirm" formaction="<?php echo Url::display (array ('c' => 'feed', 'a' => 'delete', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Translate::t ('delete'); ?></button> + <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 $this->renderHelper ('confirm_action_script'); ?> - <?php } else { ?> -<div class="alert alert-warn"><span class="alert-head"><?php echo Translate::t ('no_selected_feed'); ?></span> <?php echo Translate::t ('think_to_add'); ?></div> +<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 } ?> diff --git a/app/views/configure/importExport.phtml b/app/views/configure/importExport.phtml deleted file mode 100644 index 4cc575356..000000000 --- a/app/views/configure/importExport.phtml +++ /dev/null @@ -1,37 +0,0 @@ -<?php if ($this->req == 'export') { ?> -<?php echo '<?xml version="1.0" encoding="UTF-8" ?>'; // résout bug sur certain serveur ?> -<!-- Generated by <?php echo Configuration::title (); ?> --> -<opml version="2.0"> - <head> - <title><?php echo Configuration::title (); ?> OPML Feed</title> - <dateCreated><?php echo date('D, d M Y H:i:s'); ?></dateCreated> - </head> - <body> -<?php echo opml_export ($this->categories); ?> - </body> -</opml> -<?php } else { ?> -<?php $this->partial ('aside_feed'); ?> - -<div class="post "> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> - - <form method="post" action="<?php echo Url::display (array ('c' => 'configure', 'a' => 'importExport', 'params' => array ('q' => 'import'))); ?>" enctype="multipart/form-data"> - <legend><?php echo Translate::t ('import_export_opml'); ?></legend> - <div class="form-group"> - <label class="group-name" for="file"><?php echo 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 Translate::t ('import'); ?></button> - <?php echo Translate::t ('or'); ?> - <a target="_blank" class="btn btn-important" href="<?php echo _url ('configure', 'importExport', 'q', 'export'); ?>"><?php echo Translate::t ('export'); ?></a> - </div> - </div> - </form> -</div> -<?php } ?> diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml new file mode 100644 index 000000000..e778ce078 --- /dev/null +++ b/app/views/configure/queries.phtml @@ -0,0 +1,97 @@ +<?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; + + $deprecated = (isset($this->query_get[$key]) && + $this->query_get[$key]['deprecated']); + ?> + + <?php if ($exist === 0) { ?> + <div class="alert alert-warn"> + <div class="alert-head"><?php echo _t('no_query_filter'); ?></div> + </div> + <?php } elseif ($deprecated) { ?> + <div class="alert alert-error"> + <div class="alert-head"><?php echo _t('query_deprecated'); ?></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'])) { ?> + <li class="item"><?php echo _t('query_search', $query['search']); ?></li> + <?php } ?> + + <?php if (isset($query['state'])) { ?> + <li class="item"><?php echo _t('query_state_' . $query['state']); ?></li> + <?php } ?> + + <?php if (isset($query['order'])) { ?> + <li class="item"><?php echo _t('query_order_' . strtolower($query['order'])); ?></li> + <?php } ?> + + <?php if (isset($query['get'])) { ?> + <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>
\ No newline at end of file diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml new file mode 100644 index 000000000..8b2da2a28 --- /dev/null +++ b/app/views/configure/reading.phtml @@ -0,0 +1,158 @@ +<?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; ?>" min="5" max="50" /> + <?php echo _i('help'); ?> <?php echo _t('number_divided_when_reader'); ?> + </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> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="view_mode"><?php echo _t('articles_to_display'); ?></label> + <div class="group-controls"> + <select name="default_view" id="default_view"> + <option value="<?php echo FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_NOT_READ ? ' selected="selected"' : ''; ?>><?php echo _t('show_adaptive'); ?></option> + <option value="<?php echo FreshRSS_Entry::STATE_ALL; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_ALL ? ' selected="selected"' : ''; ?>><?php echo _t('show_all_articles'); ?></option> + <option value="<?php echo FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ ? ' selected="selected"' : ''; ?>><?php echo _t('show_not_reads'); ?></option> + </select> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="hide_read_feeds"> + <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo $this->conf->hide_read_feeds ? ' checked="checked"' : ''; ?> /> + <?php echo Minz_Translate::t('hide_read_feeds'); ?> + </label> + </div> + </div> + + <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="display_categories"> + <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo $this->conf->display_categories ? ' checked="checked"' : ''; ?> /> + <?php echo Minz_Translate::t ('display_categories_unfolded'); ?> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + </label> + </div> + </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="auto_load_more"> + <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> /> + <?php echo Minz_Translate::t ('auto_load_more'); ?> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + </label> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="lazyload"> + <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> /> + <?php echo Minz_Translate::t ('img_with_lazyload'); ?> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + </label> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="reading_confirm"> + <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo $this->conf->reading_confirm ? ' checked="checked"' : ''; ?> /> + <?php echo Minz_Translate::t ('reading_confirm'); ?> + <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> diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml new file mode 100644 index 000000000..02ce331da --- /dev/null +++ b/app/views/configure/sharing.phtml @@ -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> diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index 01e66adb4..a4029b676 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -1,7 +1,7 @@ <?php $this->partial ('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <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) { ?> @@ -9,55 +9,119 @@ <?php } ?> </datalist> - <?php $s = $this->conf->shortcuts (); ?> + <?php $s = $this->conf->shortcuts; ?> <form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>"> - <legend><?php echo Translate::t ('shortcuts_management'); ?></legend> + <legend><?php echo Minz_Translate::t ('shortcuts'); ?></legend> - <noscript><p class="alert alert-error"><?php echo Translate::t ('javascript_for_shortcuts'); ?></p></noscript> + <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 Translate::t ('mark_read'); ?></label> + <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 Translate::t ('shift_for_all_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 Translate::t ('mark_favorite'); ?></label> + <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 Translate::t ('see_on_website'); ?></label> + <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="next_entry"><?php echo Translate::t ('next_article'); ?></label> + <label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label> <div class="group-controls"> - <input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" /> - <?php echo Translate::t ('shift_for_last'); ?> + <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="prev_entry"><?php echo Translate::t ('previous_article'); ?></label> + <label class="group-name" for="collapse_entry"><?php echo Minz_Translate::t ('collapse_article'); ?></label> <div class="group-controls"> - <input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" /> - <?php echo Translate::t ('shift_for_first'); ?> + <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"> + <label class="group-name" for="user_filter_shortcut"><?php echo Minz_Translate::t ('user_filter'); ?></label> + <div class="group-controls"> + <input type="text" id="user_filter_shortcut" name="shortcuts[user_filter]" list="keys" value="<?php echo $s['user_filter']; ?>" /> + <?php echo Minz_Translate::t ('user_filter_help'); ?> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="help_shortcut"><?php echo Minz_Translate::t ('help'); ?></label> + <div class="group-controls"> + <input type="text" id="help_shortcut" name="shortcuts[help]" list="keys" value="<?php echo $s['help']; ?>" /> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button> + <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> diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml new file mode 100644 index 000000000..272896fb2 --- /dev/null +++ b/app/views/configure/users.phtml @@ -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> diff --git a/app/views/entry/bookmark.phtml b/app/views/entry/bookmark.phtml index 1ff1c220c..c1fc32b7f 100755 --- a/app/views/entry/bookmark.phtml +++ b/app/views/entry/bookmark.phtml @@ -1,15 +1,16 @@ <?php +header('Content-Type: application/json; charset=UTF-8'); -if (Request::param ('is_favorite')) { - Request::_param ('is_favorite', 0); +if (Minz_Request::param ('is_favorite', true)) { + Minz_Request::_param ('is_favorite', 0); } else { - Request::_param ('is_favorite', 1); + Minz_Request::_param ('is_favorite', 1); } -$url = Url::display (array ( - 'c' => Request::controllerName (), - 'a' => Request::actionName (), - 'params' => Request::params (), +$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))); +echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_favorite') ? 'non-starred' : 'starred'))); diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml index 6d3313a89..9e79d4c07 100755 --- a/app/views/entry/read.phtml +++ b/app/views/entry/read.phtml @@ -1,15 +1,16 @@ <?php +header('Content-Type: application/json; charset=UTF-8'); -if (Request::param ('is_read')) { - Request::_param ('is_read', 0); +if (Minz_Request::param ('is_read', true)) { + Minz_Request::_param ('is_read', 0); } else { - Request::_param ('is_read', 1); + Minz_Request::_param ('is_read', 1); } -$url = Url::display (array ( - 'c' => Request::controllerName (), - 'a' => Request::actionName (), - 'params' => Request::params (), +$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))); +echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_read') ? 'unread' : 'read'))); diff --git a/app/views/error/index.phtml b/app/views/error/index.phtml index d5d090c72..ef4fbd39d 100644 --- a/app/views/error/index.phtml +++ b/app/views/error/index.phtml @@ -1,10 +1,9 @@ <div class="post"> <div class="alert alert-error"> <h1 class="alert-head"><?php echo $this->code; ?></h1> - <p> - <?php echo Translate::t ('page_not_found'); ?><br /> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <?php echo $this->errorMessage; ?><br /> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a> </p> </div> </div> diff --git a/app/views/feed/actualize.phtml b/app/views/feed/actualize.phtml new file mode 100644 index 000000000..d86bac9de --- /dev/null +++ b/app/views/feed/actualize.phtml @@ -0,0 +1 @@ +OK diff --git a/app/views/feed/add.phtml b/app/views/feed/add.phtml new file mode 100644 index 000000000..849dacac6 --- /dev/null +++ b/app/views/feed/add.phtml @@ -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 } ?> diff --git a/app/views/helpers/confirm_action_script.phtml b/app/views/helpers/confirm_action_script.phtml deleted file mode 100644 index f77006fcc..000000000 --- a/app/views/helpers/confirm_action_script.phtml +++ /dev/null @@ -1,5 +0,0 @@ -<script type="text/javascript"> - $('.confirm').click(function () { - return confirm("<?php echo Translate::t('confirm_action'); ?>"); - }); -</script>
\ No newline at end of file diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml new file mode 100644 index 000000000..ffdca1daa --- /dev/null +++ b/app/views/helpers/export/articles.phtml @@ -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); +?> diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml new file mode 100644 index 000000000..8622d9144 --- /dev/null +++ b/app/views/helpers/export/opml.phtml @@ -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' => htmlspecialchars_decode($feed->url()), + 'htmlUrl' => htmlspecialchars_decode($feed->website()), + 'description' => htmlspecialchars_decode($feed->description()), + ); + } +} + +echo libopml_render($opml_array); diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml new file mode 100644 index 000000000..1139eb446 --- /dev/null +++ b/app/views/helpers/javascript_vars.phtml @@ -0,0 +1,61 @@ +<?php + +echo '"use strict";', "\n"; + +$mark = $this->conf->mark_when; +echo 'var ', + 'help_url="', FRESHRSS_WIKI, '"', + ',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', + ',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'], '",', + 'user_filter:"', $s['user_filter'], '",', + 'help:"', $s['help'], '"', +"},\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_default="', Minz_Translate::t('confirm_action'), '"', ",\n"; +echo 'str_notif_title_articles="', Minz_Translate::t('notif_title_new_articles'), '"', ",\n"; +echo 'str_notif_body_articles="', Minz_Translate::t('notif_body_new_articles'), '"', ",\n"; +echo 'html5_notif_timeout=', $this->conf->html5_notif_timeout,",\n"; + + +$autoActualise = Minz_Session::param('actualize_feeds', false); +echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n"; diff --git a/app/views/helpers/logs_pagination.phtml b/app/views/helpers/logs_pagination.phtml index 9f1d6cb23..e3d14810e 100755 --- a/app/views/helpers/logs_pagination.phtml +++ b/app/views/helpers/logs_pagination.phtml @@ -1,7 +1,7 @@ <?php - $c = Request::controllerName (); - $a = Request::actionName (); - $params = Request::params (); + $c = Minz_Request::controllerName (); + $a = Minz_Request::actionName (); + $params = Minz_Request::params (); ?> <?php if ($this->nbPage > 1) { ?> @@ -9,14 +9,14 @@ <?php $params[$getteur] = 1; ?> <li class="item pager-first"> <?php if ($this->currentPage > 1) { ?> - <a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo Translate::t('first'); ?></a> + <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 Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Translate::t('previous'); ?></a> + <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Minz_Translate::t('previous'); ?></a> <?php } ?> </li> @@ -24,7 +24,7 @@ <?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 Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li> + <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 } ?> @@ -34,13 +34,13 @@ <?php $params[$getteur] = $this->currentPage + 1; ?> <li class="item pager-next"> <?php if ($this->currentPage < $this->nbPage) { ?> - <a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t('next'); ?> ›</a> + <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 Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t('last'); ?> »</a> + <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('last'); ?> »</a> <?php } ?> </li> </ul> diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml index 0018a951e..cea338364 100755 --- a/app/views/helpers/pagination.phtml +++ b/app/views/helpers/pagination.phtml @@ -1,20 +1,37 @@ <?php - $c = Request::controllerName (); - $a = Request::actionName (); - $params = Request::params (); + $c = Minz_Request::controllerName(); + $a = Minz_Request::actionName(); + $params = Minz_Request::params(); + $markReadUrl = Minz_Session::param('markReadUrl'); + Minz_Session::_param('markReadUrl', false); ?> +<form id="mark-read-pagination" method="post" style="display: none"></form> + <ul class="pagination"> <li class="item pager-next"> - <?php if ($this->next != '') { ?> - <?php $params[$getteur] = $this->next; ?> - <a id="load_more" href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t ('load_more'); ?></a> + <?php if (!empty($this->nextId)) { ?> + <?php + $params['next'] = $this->nextId; + $params['ajax'] = 1; + ?> + <a id="load_more" href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>"> + <?php echo _t('load_more'); ?> + </a> + <?php } elseif ($markReadUrl) { ?> + <button id="bigMarkAsRead" + class="as-link <?php echo $this->conf->reading_confirm ? 'confirm' : ''; ?>" + form="mark-read-pagination" + formaction="<?php echo $markReadUrl; ?>" + type="submit"> + <?php echo _t('nothing_to_load'); ?><br /> + <span class="bigTick">✓</span><br /> + <?php echo _t('mark_all_read'); ?> + </button> <?php } else { ?> - <div class="bigMarkAsRead"> - <p><?php echo Translate::t ('nothing_to_load'); ?></p> - <p class="bigTick">✔</p> - <p><?php echo Translate::t ('mark_all_read'); ?></p> - </div> + <a id="bigMarkAsRead" href="."> + <?php echo _t('nothing_to_load'); ?><br /> + </a> <?php } ?> </li> </ul> diff --git a/app/views/helpers/view/global_view.phtml b/app/views/helpers/view/global_view.phtml index 131152f27..72bcf4c73 100644 --- a/app/views/helpers/view/global_view.phtml +++ b/app/views/helpers/view/global_view.phtml @@ -1,30 +1,34 @@ <?php $this->partial ('nav_menu'); ?> -<div id="stream" class="global"> +<?php if (!empty($this->entries)) { ?> +<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 (); - $catNotRead = $cat->nbNotRead (); if (!empty ($feeds)) { ?> - <div class="category"> - <div class="cat_header"> - <a href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id (), 'output', 'normal'); ?>"> - <?php echo $cat->name(); ?><?php echo $catNotRead > 0 ? ' (' . $catNotRead . ')' : ''; ?> + <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 class="item"> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="" /> - - <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id (), 'output', 'normal'); ?>"> - <?php echo $not_read > 0 ? '<b>' : ''; ?> + <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(); ?> - <?php echo $not_read > 0 ? ' (' . $not_read . ')' : ''; ?> - <?php echo $not_read > 0 ? '</b>' : ''; ?> </a> </li> <?php } ?> @@ -37,6 +41,13 @@ </div> <div id="overlay"></div> -<div id="panel"> - <a class="close" href="#"><i class="icon i_close"></i></a> -</div>
\ No newline at end of file +<div id="panel"<?php echo $this->conf->display_posts ? '' : ' class="hide_posts"'; ?>> + <a class="close" href="#"><?php echo FreshRSS_Themes::icon('close'); ?></a> +</div> + +<?php } else { ?> +<div id="stream" class="prompt alert alert-warn global"> + <h2><?php echo _t('no_feed_to_display'); ?></h2> + <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> +</div> +<?php } ?> diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index d940e0dda..1dbf14f4c 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -3,170 +3,188 @@ $this->partial ('aside_flux'); $this->partial ('nav_menu'); -if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) { - $items = $this->entryPaginator->items (); -?> - -<div id="stream" class="normal"> - <?php - $display_today = true; - $display_yesterday = true; - $display_others = true; - ?> - <?php foreach ($items as $item) { ?> - - <?php if ($display_today && $item->isDay (Days::TODAY)) { ?> - <div class="day"> - <?php echo Translate::t ('today'); ?> - <span class="date"> - <?php echo timestamptodate (time (), false); ?></span> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_today = false; } ?> - <?php if ($display_yesterday && $item->isDay (Days::YESTERDAY)) { ?> - <div class="day"> - <?php echo Translate::t ('yesterday'); ?> - <span class="date"> - <?php echo timestamptodate (time () - 86400, false); ?></span> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_yesterday = false; } ?> - <?php if ($display_others && $item->isDay (Days::BEFORE_YESTERDAY)) { ?> - <div class="day"> - <?php echo Translate::t ('before_yesterday'); ?> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_others = false; } ?> +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; - <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 (!login_is_conf ($this->conf) || is_logged ()) { ?> - <li class="item manage"> - <?php if (!$item->isRead ()) { ?> - <a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', 1); ?>"> </a> - <?php } else { ?> - <a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', 0); ?>"> </a> - <?php } ?> + $content_width = $this->conf->content_width; +?> - <?php if (!$item->isFavorite ()) { ?> - <a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', 1); ?>"> </a> - <?php } else { ?> - <a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', 0); ?>"> </a> - <?php } ?> - </li> - <?php } ?> - <?php - $feed = HelperCategory::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> +<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 ($feed == null) { + $feed = $item->feed(true); + if ($feed == null) { + $feed = FreshRSS_Feed::example(); + } + } + ?><li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li> <li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li> - <li class="item date"><?php echo $item->date (); ?></li> - <li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"> </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"> - <h1 class="title"><?php echo $item->title (); ?></h1> - <?php $author = $item->author (); ?> - <?php echo $author != '' ? '<div class="author">' . Translate::t ('by_author', $author) . '</div>' : ''; ?> + <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 - if($this->conf->lazyload() == 'yes') { - echo lazyimg($item->content ()); - } else { - echo $item->content(); - } + $author = $item->author(); + echo $author != '' ? '<div class="author">' . Minz_Translate::t('by_author', $author) . '</div>' : '', + $lazyload && $hidePosts ? lazyimg($item->content()) : $item->content(); ?> </div> - - <ul class="horizontal-list bottom"> - <?php if (!login_is_conf ($this->conf) || is_logged ()) { ?> - <li class="item manage"> - <?php if (!$item->isRead ()) { ?> - <a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', 1); ?>"> </a> - <?php } else { ?> - <a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', 0); ?>"> </a> - <?php } ?> - - <?php if (!$item->isFavorite ()) { ?> - <a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', 1); ?>"> </a> - <?php } else { ?> - <a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', 0); ?>"> </a> - <?php } ?> - </li> - <?php } ?> - <li class="item"> - <?php $link = urlencode ($item->link ()); ?> - <?php $title = urlencode ($item->title () . ' - ' . $feed->name ()); ?> - <div class="dropdown"> + <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> - <i class="icon i_share"></i> <a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>"><?php echo Translate::t ('share'); ?></a> + <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 - $shaarli = $this->conf->urlShaarli (); - if ((!login_is_conf ($this->conf) || is_logged ()) && $shaarli) { - ?> - <li class="item"> - <a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&title=' . $title . '&source=bookmarklet'; ?>"> - Shaarli - </a> - </li> - <?php } ?> - <li class="item"> - <a href="mailto:?subject=<?php echo urldecode($title); ?>&body=<?php echo $link; ?>"> - <?php echo Translate::t ('by_email'); ?> - </a> - </li> - <li class="item"> - <a target="_blank" href="https://twitter.com/share?url=<?php echo $link; ?>&text=<?php echo $title; ?>"> - Twitter - </a> - </li> - <li class="item"> - <a target="_blank" href="https://www.facebook.com/sharer.php?u=<?php echo $link; ?>&t=<?php echo $title; ?>"> - Facebook - </a> - </li> - <li class="item"> - <a target="_blank" href="https://plus.google.com/share?url=<?php echo $link; ?>"> - Google+ - </a> - </li> + <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> - </li> - <?php $tags = $item->tags(); ?> - <?php if(!empty($tags)) { ?> - <li class="item"> + <?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> - <i class="icon i_tag"></i> <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>"><?php echo Translate::t ('related_tags'); ?></a> - + <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 } ?> + <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 } ?> - <li class="item date"><?php echo $item->date (); ?></li> - <li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"> </a></li> + </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->entryPaginator->render ('pagination.phtml', 'next'); ?> + + <?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 Translate::t ('no_feed_to_display'); ?></span> +<div id="stream" class="prompt alert alert-warn normal"> + <h2><?php echo _t('no_feed_to_display'); ?></h2> + <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> </div> -<?php } ?>
\ No newline at end of file +<?php } ?> diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml index 7b1e2bca0..c80dca519 100644 --- a/app/views/helpers/view/reader_view.phtml +++ b/app/views/helpers/view/reader_view.phtml @@ -1,48 +1,44 @@ <?php $this->partial ('nav_menu'); -if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) { - $items = $this->entryPaginator->items (); +if (!empty($this->entries)) { + $lazyload = $this->conf->lazyload; + $content_width = $this->conf->content_width; ?> <div id="stream" class="reader"> - <?php foreach ($items as $item) { ?> + <?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"> + <div class="content <?php echo $content_width; ?>"> <?php - $feed = HelperCategory::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache + $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> + <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 != '' ? Translate::t ('by_author', $author) . ' - ' : ''; ?> - <?php echo $item->date (); ?> - </div> + <div class="author"><?php + $author = $item->author(); + echo $author != '' ? Minz_Translate::t('by_author', $author) . ' — ' : '', + $item->date(); + ?></div> - <?php - if($this->conf->lazyload() == 'yes') { - echo lazyimg($item->content ()); - } else { - echo $item->content(); - } - ?> + <?php echo $item->content(); ?> </div> </div> </div> <?php } ?> - - <?php $this->entryPaginator->render ('pagination.phtml', 'next'); ?> + + <?php $this->renderHelper('pagination'); ?> </div> <?php } else { ?> -<div id="stream" class="alert alert-warn reader"> - <span class="alert-head"><?php echo Translate::t ('no_feed_to_display'); ?></span> +<div id="stream" class="prompt alert alert-warn reader"> + <h2><?php echo _t('no_feed_to_display'); ?></h2> + <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> </div> -<?php } ?>
\ No newline at end of file +<?php } ?> diff --git a/app/views/helpers/view/rss_view.phtml b/app/views/helpers/view/rss_view.phtml index 8ce636918..2c6ca610b 100755 --- a/app/views/helpers/view/rss_view.phtml +++ b/app/views/helpers/view/rss_view.phtml @@ -1,18 +1,17 @@ <?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 View::title(); ?></title> - <link><?php echo Url::display(); ?></link> - <description><?php echo Translate::t ('rss_feeds_of', View::title()); ?></description> + <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 _url ('index', 'index', 'output', 'rss'); ?>" rel="self" type="application/rss+xml" /> + <atom:link href="<?php echo Minz_Url::display ($this->url, 'html', true); ?>" rel="self" type="application/rss+xml" /> <?php -$items = $this->entryPaginator->items (); -foreach ($items as $item) { +foreach ($this->entries as $item) { ?> <item> - <title><?php echo htmlspecialchars(html_entity_decode($item->title (), ENT_NOQUOTES, 'UTF-8'), ENT_NOQUOTES, 'UTF-8'); ?></title> + <title><?php echo $item->title (); ?></title> <link><?php echo $item->link (); ?></link> <?php $author = $item->author (); ?> <?php if ($author != '') { ?> @@ -24,7 +23,7 @@ foreach ($items as $item) { <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 } ?> +<?php } ?> </channel> </rss> diff --git a/app/views/importExport/export.phtml b/app/views/importExport/export.phtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/views/importExport/export.phtml diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml new file mode 100644 index 000000000..35371faca --- /dev/null +++ b/app/views/importExport/index.phtml @@ -0,0 +1,61 @@ +<?php $this->partial('aside_feed'); ?> + +<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('importExport', 'import'); ?>" enctype="multipart/form-data"> + <legend><?php echo _t('import'); ?></legend> + <div class="form-group"> + <label class="group-name" for="file"> + <?php echo extension_loaded('zip') ? _t('file_to_import') : _t('file_to_import_no_zip'); ?> + </label> + <div class="group-controls"> + <input type="file" name="file" id="file" /> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('import'); ?></button> + </div> + </div> + </form> + + <?php if (count($this->feeds) > 0) { ?> + <form method="post" action="<?php echo _url('importExport', 'export'); ?>"> + <legend><?php echo _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 _t('export_opml'); ?> + </label> + + <label class="checkbox" for="export_starred"> + <input type="checkbox" name="export_starred" id="export_starred" value="1" <?php echo extension_loaded('zip') ? 'checked="checked"' : ''; ?> /> + <?php echo _t('export_starred'); ?> + </label> + + <?php + $select_args = ''; + if (extension_loaded('zip')) { + $select_args = ' size="' . min(10, count($this->feeds)) .'" multiple="multiple"'; + } + ?> + <select name="export_feeds[]"<?php echo $select_args; ?>> + <?php echo extension_loaded('zip') ? '' : '<option></option>'; ?> + <?php foreach ($this->feeds as $feed) { ?> + <option value="<?php echo $feed->id(); ?>"><?php echo $feed->name(); ?></option> + <?php } ?> + </select> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('export'); ?></button> + </div> + </div> + </form> + <?php } ?> +</div> diff --git a/app/views/index/about.phtml b/app/views/index/about.phtml index ca1254053..76ff804d8 100644 --- a/app/views/index/about.phtml +++ b/app/views/index/about.phtml @@ -1,24 +1,27 @@ <div class="post content"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> - <h1><?php echo Translate::t ('about_freshrss'); ?></h1> + <h1><?php echo Minz_Translate::t ('about_freshrss'); ?></h1> <dl class="infos"> - <dt><?php echo Translate::t ('project_website'); ?></dt> - <dd><a href="http://marienfressinaud.github.io/FreshRSS/">http://marienfressinaud.github.io/FreshRSS/</a></dd> + <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 Translate::t ('lead_developer'); ?></dt> - <dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> - <a href="http://marienfressinaud.fr"><?php echo Translate::t ('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 Translate::t ('bugs_reports'); ?></dt> - <dd><?php echo Translate::t ('github_or_email'); ?></dd> + <dt><?php echo Minz_Translate::t ('bugs_reports'); ?></dt> + <dd><?php echo Minz_Translate::t ('github_or_email'); ?></dd> - <dt><?php echo Translate::t ('license'); ?></dt> - <dd><?php echo Translate::t ('agpl3'); ?></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 Translate::t ('freshrss_description'); ?></p> + <p><?php echo Minz_Translate::t ('freshrss_description'); ?></p> - <h1><?php echo Translate::t ('credits'); ?></h1> - <p><?php echo Translate::t ('credits_content'); ?></p> + <h1><?php echo Minz_Translate::t ('credits'); ?></h1> + <p><?php echo Minz_Translate::t ('credits_content'); ?></p> </div> diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml new file mode 100644 index 000000000..b05cdced4 --- /dev/null +++ b/app/views/index/formLogin.phtml @@ -0,0 +1,46 @@ +<div class="prompt"> + <h1><?php echo _t('login'); ?></h1><?php + + switch (Minz_Configuration::authType()) { + case 'form': + ?><form id="crypto-form" method="post" action="<?php echo _url('index', 'formLogin'); ?>"> + <div> + <label for="username"><?php echo _t('username'); ?></label> + <input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" /> + </div> + <div> + <label for="passwordPlain"><?php echo _t('password'); ?></label> + <input type="password" id="passwordPlain" required="required" /> + <input type="hidden" id="challenge" name="challenge" /><br /> + <noscript><strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> + </div> + <div> + <label class="checkbox" for="keep_logged_in"> + <input type="checkbox" name="keep_logged_in" id="keep_logged_in" value="1" /> + <?php echo _t('keep_logged_in'); ?> + </label> + <br /> + </div> + <div> + <button id="loginButton" type="submit" class="btn btn-important"><?php echo _t('login'); ?></button> + </div> + </form><?php + break; + + case 'persona': + ?><p> + <a class="signin btn btn-important" href="#"> + <?php echo _i('login'); ?> + <?php echo _t('login_with_persona'); ?> + </a><br /><br /> + + <?php echo _i('help'); ?> + <small> + <a href="<?php echo _url('index', 'resetAuth'); ?>"><?php echo _t('login_persona_problem'); ?></a> + </small> + </p><?php + break; + } ?> + + <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p> +</div> diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 73e5fcc4d..1ff36ca8e 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,28 +1,25 @@ <?php -$output = Request::param ('output', 'normal'); -$token = $this->conf->token(); -$token_param = Request::param ('token', ''); -$token_is_ok = ($token != '' && $token == $token_param); +$output = Minz_Request::param ('output', 'normal'); -if(!login_is_conf ($this->conf) || - is_logged() || - $this->conf->anonAccess() == 'yes' || - ($output == 'rss' && $token_is_ok)) { - if($output == 'rss') { - $this->renderHelper ('view/rss_view'); - } elseif($output == 'reader') { +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') { + } 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 { -?> -<div class="post content"> - <h1><?php echo Translate::t ('forbidden_access'); ?></h1> - <p><?php echo Translate::t ('forbidden_access_description'); ?></p> -</div> -<?php -}
\ No newline at end of file + // Normally, it should not happen, but log it anyway + Minz_Log::record ('Something is wrong in ' . __FILE__ . ' line ' . __LINE__, Minz_Log::ERROR); +} diff --git a/app/views/index/logout.phtml b/app/views/index/logout.phtml new file mode 100644 index 000000000..a0aba9318 --- /dev/null +++ b/app/views/index/logout.phtml @@ -0,0 +1 @@ +OK
\ No newline at end of file diff --git a/app/views/index/logs.phtml b/app/views/index/logs.phtml index c72a84c86..1b77b39af 100644 --- a/app/views/index/logs.phtml +++ b/app/views/index/logs.phtml @@ -1,21 +1,25 @@ <div class="post content"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> - <h1><?php echo Translate::t ('logs'); ?></h1> + <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 ('d/m/Y - H:i:s', strtotime ($log->date ())); ?></span><?php echo $log->info (); ?></div> + <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 Translate::t ('logs_empty'); ?></p> + <p class="alert alert-warn"><?php echo Minz_Translate::t ('logs_empty'); ?></p> <?php } ?> -</div>
\ No newline at end of file +</div> diff --git a/app/views/index/resetAuth.phtml b/app/views/index/resetAuth.phtml new file mode 100644 index 000000000..6d4282c14 --- /dev/null +++ b/app/views/index/resetAuth.phtml @@ -0,0 +1,33 @@ +<div class="prompt"> + <h1><?php echo _t('auth_reset'); ?></h1> + + <?php if (!empty($this->message)) { ?> + <p class="alert <?php echo $this->message['status'] === 'bad' ? 'alert-error' : 'alert-warn'; ?>"> + <span class="alert-head"><?php echo $this->message['title']; ?></span><br /> + <?php echo $this->message['body']; ?> + </p> + <?php } ?> + + <?php if (!$this->no_form) { ?> + <form id="crypto-form" method="post" action="<?php echo _url('index', 'resetAuth'); ?>"> + <p class="alert alert-warn"> + <span class="alert-head"><?php echo _t('attention'); ?></span><br /> + <?php echo _t('auth_will_reset'); ?> + </p> + + <div> + <label for="username"><?php echo _t('username_admin'); ?></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 _t('password'); ?></label> + <input type="password" id="passwordPlain" required="required" /> + <input type="hidden" id="challenge" name="challenge" /><br /> + <noscript><strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> + </div> + <div> + <button id="loginButton" type="submit" class="btn btn-important"><?php echo _t('submit'); ?></button> + </div> + </form> + <?php } ?> +</div> diff --git a/app/views/javascript/actualize.phtml b/app/views/javascript/actualize.phtml index fa6e67ddb..74cef4998 100644 --- a/app/views/javascript/actualize.phtml +++ b/app/views/javascript/actualize.phtml @@ -1,45 +1,56 @@ -var feeds = new Array (); -<?php foreach ($this->feeds as $feed) { ?> -feeds.push ("<?php echo Url::display (array ('c' => 'feed', 'a' => 'actualize', 'params' => array ('id' => $feed->id (), 'ajax' => '1')), 'php'); ?>"); -<?php } ?> +"use strict"; +var feeds = [<?php foreach ($this->feeds as $feed) { ?>{<?php + ?>url: "<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'); ?>",<?php + ?>title: "<?php echo $feed->name(); ?>"<?php +?>},<?php } ?>], + feed_processed = 0, + feed_count = feeds.length; -function initProgressBar (init) { +function initProgressBar(init) { if (init) { - $("body").after ("\<div id=\"actualizeProgress\" class=\"actualizeProgress\">\ - <?php echo Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feeds.length + "</span><br />\ - <progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feeds.length + "\"></progress>\ + $("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\ + <?php echo _t('refresh'); ?><br /><span class=\"title\">/</span><br />\ + <span class=\"progress\">0 / " + feed_count + "</span>\ </div>"); } else { - window.location.reload (); + window.location.reload(); } } -function updateProgressBar (i) { - $("#actualizeProgressBar").val(i); - $("#actualizeProgress .progress").html (i + " / " + feeds.length); +function updateProgressBar(i, title_feed) { + $("#actualizeProgress .progress").html(i + " / " + feed_count); + $("#actualizeProgress .title").html(title_feed); } -function updateFeeds () { - initProgressBar (true); - - var i = 0; - for (var f in feeds) { - $.ajax ({ - type: 'POST', - url: feeds[f], - }).done (function (data) { - i++; - updateProgressBar (i); +function updateFeeds() { + if (feed_count === 0) { + openNotification("<?php echo _t('no_feed_to_refresh'); ?>", "good"); + ajax_loading = false; + return; + } + initProgressBar(true); - if (i == feeds.length) { - initProgressBar (false); - } - }); + for (var i = 0; i < 10; i++) { + updateFeed(); } } -$(document).ready (function () { - $("#actualize").click (function () { - updateFeeds (); - return false; +function updateFeed() { + var feed = feeds.pop(); + if (feed == undefined) { + return; + } + + $.ajax({ + type: 'POST', + url: feed['url'], + }).complete(function (data) { + feed_processed++; + updateProgressBar(feed_processed, feed['title']); + + if (feed_processed === feed_count) { + initProgressBar(false); + } else { + updateFeed(); + } }); -}); +} diff --git a/app/views/javascript/main.phtml b/app/views/javascript/main.phtml deleted file mode 100644 index 03c60cc52..000000000 --- a/app/views/javascript/main.phtml +++ /dev/null @@ -1,415 +0,0 @@ -<?php if ($this->conf->displayPosts () == 'no') { ?> -var hide_posts = true; -<?php } else { ?> -var hide_posts = false; -<?php } ?> - -<?php - $s = $this->conf->shortcuts (); - $mark = $this->conf->markWhen (); - $auto_load_more = $this->conf->autoLoadMore () -?> - -function is_reader_mode() { - var stream = $("#stream.reader"); - return stream.html() != null; -} - -function is_normal_mode() { - var stream = $("#stream.normal"); - return stream.html() != null; -} - -function is_global_mode() { - var stream = $("#stream.global"); - return stream.html() != null; -} - -function redirect (url, new_tab) { - if (url) { - if (new_tab) { - window.open (url); - } else { - location.href = url; - } - } -} - -function toggleContent (new_active, old_active) { - old_active.removeClass ("active"); - if (old_active[0] != new_active[0]) { - new_active.addClass ("active"); - } - - var box_to_move = "html,body"; - var relative_move = false; - if(is_global_mode()) { - box_to_move = "#panel"; - relative_move = true; - } - - var new_pos = new_active.position ().top, - old_scroll = $(box_to_move).scrollTop (), - new_scroll = old_scroll; - if (hide_posts) { - old_active.children (".flux_content").toggle (0); - - new_pos = new_active.position ().top; - old_scroll = $(box_to_move).scrollTop (); - - if(relative_move) { - new_pos += old_scroll; - } - - if (old_active[0] != new_active[0]) { - new_active.children (".flux_content").toggle (0, function () { - new_scroll = $(box_to_move).scrollTop (new_pos).scrollTop (); - }); - } - } else { - if(relative_move) { - new_pos += old_scroll; - } - - new_scroll = $(box_to_move).scrollTop (new_pos).scrollTop (); - } - - if ((new_scroll === old_scroll) && $.fn.lazyload) { - $(window).trigger ("scroll"); //When no scroll was done, generate fake scroll event for LazyLoad to load images - } - - <?php if ($mark['article'] == 'yes') { ?> - mark_read(new_active, true); - <?php } ?> -} - -function mark_read (active, only_not_read) { - if (active[0] === undefined || ( - only_not_read === true && !active.hasClass("not_read"))) { - return false; - } - - url = active.find ("a.read").attr ("href"); - if (url === undefined) { - return false; - } - - $.ajax ({ - type: 'POST', - url: url, - data : { ajax: true } - }).done (function (data) { - res = jQuery.parseJSON(data); - - active.find ("a.read").attr ("href", res.url); - - if (active.hasClass ("not_read")) { - active.removeClass ("not_read"); - } else if(only_not_read !== true || active.hasClass("not_read")) { - active.addClass ("not_read"); - } - }); -} - -function mark_favorite (active) { - if (active[0] === undefined) { - return false; - } - - url = active.find ("a.bookmark").attr ("href"); - if (url === undefined) { - return false; - } - - $.ajax ({ - type: 'POST', - url: url, - data : { ajax: true } - }).done (function (data) { - res = jQuery.parseJSON(data); - - active.find ("a.bookmark").attr ("href", res.url); - if (active.hasClass ("favorite")) { - active.removeClass ("favorite"); - } else { - active.addClass ("favorite"); - } - }); -} - -function prev_entry() { - old_active = $(".flux.active"); - last_active = $(".flux:last"); - new_active = old_active.prevAll (".flux:first"); - - if (new_active.hasClass("flux")) { - toggleContent (new_active, old_active); - } else if (old_active[0] === undefined && - new_active[0] === undefined) { - toggleContent (last_active, old_active); - } -} - -function next_entry() { - old_active = $(".flux.active"); - first_active = $(".flux:first"); - last_active = $(".flux:last"); - new_active = old_active.nextAll (".flux:first"); - - if (new_active.hasClass("flux")) { - toggleContent (new_active, old_active); - } else if (old_active[0] === undefined && - new_active[0] === undefined) { - toggleContent (first_active, old_active); - } - - <?php if ($auto_load_more !== 'yes') { ?> - if(last_active.attr("id") == new_active.attr("id")) { - load_more_posts (); - } - <?php } ?> -} - -function init_img () { - var maxWidth = $(".flux_content .content").width() / 2; - $(".flux_content .content img").each (function () { - if ($(this).width () > maxWidth) { - $(this).addClass("big"); - } - }); -} - -function inMarkViewport(flux, box_to_follow, relative_follow) { - var top = flux.position().top; - if(relative_follow) { - top += box_to_follow.scrollTop(); - } - var height = flux.height(); - var begin = top + 3 * height / 4; - var bot = Math.min(begin + 75, top + height); - - var windowTop = box_to_follow.scrollTop(); - var windowBot = windowTop + box_to_follow.height() / 2; - - return (windowBot >= begin && windowBot <= bot); -} - -function init_posts () { - init_img (); - <?php if($this->conf->lazyload() == 'yes') { ?> - if(is_global_mode()) { - $(".flux .content img").lazyload({ - container: $("#panel") - }); - } else { - $(".flux .content img").lazyload(); - } - <?php } ?> - - if (hide_posts) { - $(".flux:not(.active) .flux_content").hide (); - } - - var box_to_follow = $(window); - var relative_follow = false; - if(is_global_mode()) { - box_to_follow = $("#panel"); - relative_follow = true; - } - - <?php if ($mark['scroll'] == 'yes') { ?> - box_to_follow.scroll(function() { - $('.flux.not_read:visible').each(function() { - if($(this).children(".flux_content").is(':visible') && - inMarkViewport($(this), box_to_follow, relative_follow)) { - mark_read($(this), true); - } - }); - }); - <?php } ?> - - <?php if ($auto_load_more == 'yes') { ?> - box_to_follow.scroll(function() { - var load_more = $("#load_more"); - if (!load_more.is(':visible')) return; - var boxBot = box_to_follow.scrollTop() + box_to_follow.height(); - var load_more_top = load_more.position().top; - if(relative_follow) { - load_more_top += box_to_follow.scrollTop(); - } - - if(boxBot >= load_more_top) { - load_more_posts (); - } - }); - <?php } ?> -} - -function init_column_categories () { - if(!is_normal_mode()) { - return; - } - - $(".category").addClass ("stick"); - $(".categories .category .btn:first-child").width ("160px"); - $(".category").append ("<a class=\"btn dropdown-toggle\" href=\"#\"><i class=\"icon i_down\"></i></a>"); - - $(".category + .feeds").not(".active").hide(); - $(".category.active a.dropdown-toggle i").toggleClass ("i_up"); - - $(".category a.dropdown-toggle").click (function () { - $(this).children ().toggleClass ("i_up"); - $(this).parent ().next (".feeds").slideToggle(); - return false; - }); -} - -function init_shortcuts () { - // Touches de manipulation - shortcut.add("<?php echo $s['mark_read']; ?>", function () { - // on marque comme lu ou non lu - active = $(".flux.active"); - mark_read (active, false); - }, { - 'disable_in_input':true - }); - shortcut.add("shift+<?php echo $s['mark_read']; ?>", function () { - // on marque tout comme lu - url = $(".nav_menu a.read_all").attr ("href"); - redirect (url, false); - }, { - 'disable_in_input':true - }); - shortcut.add("<?php echo $s['mark_favorite']; ?>", function () { - // on marque comme favori ou non favori - active = $(".flux.active"); - mark_favorite (active); - }, { - 'disable_in_input':true - }); - - // Touches de navigation - shortcut.add("<?php echo $s['prev_entry']; ?>", prev_entry, { - 'disable_in_input':true - }); - shortcut.add("shift+<?php echo $s['prev_entry']; ?>", function () { - old_active = $(".flux.active"); - first = $(".flux:first"); - - if (first.hasClass("flux")) { - toggleContent (first, old_active); - } - }, { - 'disable_in_input':true - }); - shortcut.add("<?php echo $s['next_entry']; ?>", next_entry, { - 'disable_in_input':true - }); - shortcut.add("shift+<?php echo $s['next_entry']; ?>", function () { - old_active = $(".flux.active"); - last = $(".flux:last"); - - if (last.hasClass("flux")) { - toggleContent (last, old_active); - } - }, { - 'disable_in_input':true - }); - shortcut.add("<?php echo $s['go_website']; ?>", function () { - url_website = $(".flux.active .link a").attr ("href"); - - <?php if ($mark['site'] == 'yes') { ?> - $(".flux.active").each (function () { - mark_read($(this), true); - }); - <?php } ?> - - redirect (url_website, true); - }, { - 'disable_in_input':true - }); -} - -function init_stream_delegates(divStream) { - divStream.on('click', '.flux_header .item.title, .flux_header .item.date', function (e) { //flux_header_toggle - old_active = $(".flux.active"); - new_active = $(this).parent ().parent (); - if (e.target.tagName.toUpperCase() === 'A') { //Leave real links alone - <?php if ($mark['article'] == 'yes') { ?> - mark_read(new_active, true); - <?php } ?> - return true; - } - toggleContent (new_active, old_active); - }); - - divStream.on('click', '.flux a.read', function () { - active = $(this).parents (".flux"); - mark_read (active, false); - - return false; - }); - - divStream.on('click', '.flux a.bookmark', function () { - active = $(this).parents (".flux"); - mark_favorite (active); - - return false; - }); - - divStream.on('click', '.flux .content a', function () { - $(this).attr ('target', '_blank'); - }); - - divStream.on('click', '.item.title>a',function (e) { - if (e.ctrlKey) return true; //Allow default control-click behaviour such as open in backround-tab - $(this).parent ().click (); //Will perform toggle flux_content - return false; - }); - - divStream.on('click', '.bigMarkAsRead', function() { - url = $(".nav_menu a.read_all").attr ("href"); - redirect (url, false); - return false; - }); - - <?php if ($mark['site'] == 'yes') { ?> - divStream.on('click', '.flux .link a', function () { - mark_read($(this).parent().parent().parent(), true); - }); - <?php } ?> -} - -function init_nav_entries() { - $('.nav_entries a.previous_entry').click(function() { - prev_entry(); - return false; - }); - $('.nav_entries a.next_entry').click(function() { - next_entry(); - return false; - }); - $('.nav_entries a.up').click(function() { - var active_item = $(".flux.active"); - var windowTop = $(window).scrollTop(); - var item_top = active_item.position ().top; - - if(windowTop > item_top) { - $("html,body").scrollTop (item_top); - } else { - $("html,body").scrollTop (0); - } - return false; - }); -} - -$(document).ready (function () { - if(is_reader_mode()) { - hide_posts = false; - } - init_posts (); - init_column_categories (); - init_shortcuts (); - init_stream_delegates($('#stream')); - init_nav_entries(); -}); diff --git a/app/views/javascript/nbUnreadsPerFeed.phtml b/app/views/javascript/nbUnreadsPerFeed.phtml new file mode 100644 index 000000000..68f98ce9e --- /dev/null +++ b/app/views/javascript/nbUnreadsPerFeed.phtml @@ -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); diff --git a/app/views/javascript/nonce.phtml b/app/views/javascript/nonce.phtml new file mode 100644 index 000000000..4ac46c8fc --- /dev/null +++ b/app/views/javascript/nonce.phtml @@ -0,0 +1,2 @@ +<?php +echo json_encode(array('salt1' => $this->salt1, 'nonce' => $this->nonce)); diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml new file mode 100644 index 000000000..6f3d4a117 --- /dev/null +++ b/app/views/stats/idle.phtml @@ -0,0 +1,48 @@ +<?php $this->partial('aside_stats'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h1><?php echo _t('stats_idle'); ?></h1> + + <?php + $current_url = urlencode(Minz_Url::display( + array('c' => 'stats', 'a' => 'idle'), + 'php', true + )); + $nothing = true; + foreach ($this->idleFeeds as $period => $feeds) { + if (!empty($feeds)) { + $nothing = false; + ?> + <div class="stat"> + <h2><?php echo _t($period); ?></h2> + + <form id="form-delete" method="post" style="display: none"></form> + + <?php foreach ($feeds as $feed) { ?> + <ul class="horizontal-list"> + <li class="item"> + <div class="stick"> + <a class="btn" href="<?php echo _url('index', 'index', 'get', 'f_' . $feed['id']); ?>"><?php echo _i('link'); ?> <?php echo _t('filter'); ?></a> + <a class="btn" href="<?php echo _url('configure', 'feed', 'id', $feed['id']); ?>"><?php echo _i('configure'); ?> <?php echo _t('administration'); ?></a> + <button class="btn btn-attention confirm" form="form-delete" formaction="<?php echo _url('feed', 'delete', 'id', $feed['id'], 'r', $current_url); ?>"><?php echo _t('delete'); ?></button> + </div> + </li> + <li class="item"> + <span title="<?php echo timestamptodate($feed['last_date'], false); ?>"><?php echo $feed['name']; ?> (<?php echo _t('number_articles', $feed['nb_articles']); ?>)</span> + </li> + </ul> + <?php } ?> + </div> + <?php + } + } + + if ($nothing) { + ?> + <p class="alert alert-warn"> + <span class="alert-head"><?php echo _t('stats_no_idle'); ?></span> + </p> + <?php } ?> +</div> diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml new file mode 100644 index 000000000..412e77e16 --- /dev/null +++ b/app/views/stats/index.phtml @@ -0,0 +1,127 @@ +<?php $this->partial('aside_stats'); ?> + +<div class="post"> + <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 half"> + <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 half"> + <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><a href="<?php echo _url('stats', 'repartition', 'id', $feed['id']); ?>"><?php echo $feed['name']; ?></a></td> + <td><?php echo $feed['category']; ?></td> + <td class="numeric"><?php echo formatNumber($feed['count']); ?></td> + </tr> + <?php endforeach;?> + </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 half"> + <h2><?php echo _t ('stats_feed_per_category'); ?></h2> + <div id="statsFeedPerCategory" style="height: 300px"></div> + <div id="statsFeedPerCategoryLegend"></div> + </div><!-- + + --><div class="stat half"> + <h2><?php echo _t ('stats_entry_per_category'); ?></h2> + <div id="statsEntryPerCategory" style="height: 300px"></div> + <div id="statsEntryPerCategoryLegend"></div> + </div> +</div> + +<script> +"use strict"; +function initStats() { + if (!window.Flotr) { + if (window.console) { + console.log('FreshRSS waiting for Flotr…'); + } + window.setTimeout(initStats, 50); + return; + } + // Entry per day + Flotr.draw(document.getElementById('statsEntryPerDay'), + [<?php echo $this->count ?>], + { + grid: {verticalLines: false}, + bars: {horizontal: false, show: true}, + xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0}, + yaxis: {min: 0}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} + }); + // Feed per category + Flotr.draw(document.getElementById('statsFeedPerCategory'), + <?php echo $this->feedByCategory ?>, + { + grid: {verticalLines: false, horizontalLines: false}, + pie: {explode: 10, show: true, labelFormatter: function(){return '';}}, + xaxis: {showLabels: false}, + yaxis: {showLabels: false}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}}, + legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3} + }); + // Entry per category + Flotr.draw(document.getElementById('statsEntryPerCategory'), + <?php echo $this->entryByCategory ?>, + { + grid: {verticalLines: false, horizontalLines: false}, + pie: {explode: 10, show: true, labelFormatter: function(){return '';}}, + xaxis: {showLabels: false}, + yaxis: {showLabels: false}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ numberFormat(obj.y) + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}}, + legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3} + }); +} +initStats(); +</script> diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml new file mode 100644 index 000000000..b425c1458 --- /dev/null +++ b/app/views/stats/repartition.phtml @@ -0,0 +1,150 @@ +<?php $this->partial('aside_stats'); ?> + +<div class="post "> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h1><?php echo _t('stats_repartition'); ?></h1> + + <select id="feed_select"> + <option data-url="<?php echo _url('stats', 'repartition')?>"><?php echo _t('all_feeds')?></option> + <?php foreach ($this->categories as $category) { + $feeds = $category->feeds(); + if (!empty($feeds)) { + echo '<optgroup label="', $category->name(), '">'; + foreach ($feeds as $feed) { + if ($this->feed && $feed->id() == $this->feed->id()){ + echo '<option value="', $feed->id(), '" selected="selected" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>'; + } else { + echo '<option value="', $feed->id(), '" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>'; + } + } + echo '</optgroup>'; + } + }?> + </select> + + <?php if ($this->feed) {?> + <a class="btn" href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>"> + <?php echo _i('configure'); ?> <?php echo _t('administration'); ?> + </a> + <?php }?> + + <div class="stat"> + <h2><?php echo _t('stats_entry_per_hour'); ?></h2> + <div id="statsEntryPerHour" style="height: 300px"></div> + </div> + + <div class="stat half"> + <h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2> + <div id="statsEntryPerDayOfWeek" style="height: 300px"></div> + </div><!-- + + --><div class="stat half"> + <h2><?php echo _t('stats_entry_per_month'); ?></h2> + <div id="statsEntryPerMonth" style="height: 300px"></div> + </div> +</div> + +<script> +"use strict"; +function initStats() { + if (!window.Flotr) { + if (window.console) { + console.log('FreshRSS waiting for Flotr…'); + } + window.setTimeout(initStats, 50); + return; + } + // Entry per hour + var avg_h = []; + for (var i = -1; i <= 24; i++) { + avg_h.push([i, <?php echo $this->averageHour?>]); + } + Flotr.draw(document.getElementById('statsEntryPerHour'), + [{ + data: <?php echo $this->repartitionHour ?>, + bars: {horizontal: false, show: true} + }, { + data: avg_h, + lines: {show: true}, + label: <?php echo $this->averageHour?>, + yaxis: 2 + }], + { + grid: {verticalLines: false}, + xaxis: {noTicks: 23, + tickFormatter: function(x) { + var x = parseInt(x); + return x + 1; + }, + min: -0.9, + max: 23.9, + tickDecimals: 0}, + yaxis: {min: 0}, + y2axis: {showLabels: false}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} + }); + // Entry per day of week + var avg_dow = []; + for (var i = -1; i <= 7; i++) { + avg_dow.push([i, <?php echo $this->averageDayOfWeek?>]); + } + Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'), + [{ + data: <?php echo $this->repartitionDayOfWeek ?>, + bars: {horizontal: false, show: true} + }, { + data: avg_dow, + lines: {show: true}, + label: <?php echo $this->averageDayOfWeek?>, + yaxis: 2 + }], + { + grid: {verticalLines: false}, + xaxis: {noTicks: 6, + tickFormatter: function(x) { + var x = parseInt(x), + days = <?php echo $this->days?>; + return days[x]; + }, + min: -0.9, + max: 6.9, + tickDecimals: 0}, + yaxis: {min: 0}, + y2axis: {showLabels: false}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} + }); + // Entry per month + var avg_m = []; + for (var i = 0; i <= 13; i++) { + avg_m.push([i, <?php echo $this->averageMonth?>]); + } + Flotr.draw(document.getElementById('statsEntryPerMonth'), + [{ + data: <?php echo $this->repartitionMonth ?>, + bars: {horizontal: false, show: true} + }, { + data: avg_m, + lines: {show: true}, + label: <?php echo $this->averageMonth?>, + yaxis: 2 + }], + { + grid: {verticalLines: false}, + xaxis: {noTicks: 12, + tickFormatter: function(x) { + var x = parseInt(x), + months = <?php echo $this->months?>; + return months[(x - 1)]; + }, + min: 0.1, + max: 12.9, + tickDecimals: 0}, + yaxis: {min: 0}, + y2axis: {showLabels: false}, + mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} + }); + +} +initStats(); +</script> diff --git a/app/views/update/apply.phtml b/app/views/update/apply.phtml new file mode 100644 index 000000000..30566c7ab --- /dev/null +++ b/app/views/update/apply.phtml @@ -0,0 +1,9 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h1><?php echo _t('update_system'); ?></h1> + + <?php ask_info_update(); ?> +</div> diff --git a/app/views/update/index.phtml b/app/views/update/index.phtml new file mode 100644 index 000000000..401f6acd6 --- /dev/null +++ b/app/views/update/index.phtml @@ -0,0 +1,36 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h1><?php echo _t('update_system'); ?></h1> + + <p> + <?php echo _i('help'); ?> <?php echo _t('update_last', $this->last_update_time); ?> + </p> + + <?php if (!empty($this->message)) { ?> + <p class="alert <?php echo $this->message['status'] === 'bad' ? 'alert-error' : 'alert-warn'; ?>"> + <span class="alert-head"><?php echo $this->message['title']; ?></span> + <?php echo $this->message['body']; ?> + </p> + <?php } elseif ($this->check_last_hour) { ?> + <p class="alert alert-warn"> + <span class="alert-head"><?php echo _t('damn'); ?></span> + <?php echo _t('no_update'); ?> + </p> + <?php } ?> + + <?php + if (!$this->check_last_hour && + (empty($this->message) || $this->message['status'] !== 'good')) { + ?> + <p> + <a href="<?php echo _url('update', 'check'); ?>" class="btn"><?php echo _t('update_check'); ?></a> + </p> + <?php } ?> + + <?php if ($this->update_to_apply) { ?> + <a class="btn btn-important" href="<?php echo _url('update', 'apply'); ?>"><?php echo _t('update_apply'); ?></a> + <?php } ?> +</div> diff --git a/cache/.gitignore b/cache/.gitignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/cache/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/constants.php b/constants.php new file mode 100644 index 000000000..464e2da66 --- /dev/null +++ b/constants.php @@ -0,0 +1,25 @@ +<?php +define('FRESHRSS_VERSION', '0.8.0'); +define('FRESHRSS_WEBSITE', 'http://freshrss.org'); +define('FRESHRSS_UPDATE_WEBSITE', 'https://update.freshrss.org?v=' . FRESHRSS_VERSION); +define('FRESHRSS_WIKI', 'http://doc.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('UPDATE_FILENAME', DATA_PATH . '/update.php'); + 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()); diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 000000000..325fa75c6 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,10 @@ +application.ini +config.php +*_user.php +*.sqlite +touch.txt +no-cache.txt +*.bak.php +*.lock.txt +last_update.txt +update.php diff --git a/data/.htaccess b/data/.htaccess new file mode 100644 index 000000000..9e768397d --- /dev/null +++ b/data/.htaccess @@ -0,0 +1,3 @@ +Order Allow,Deny +Deny from all +Satisfy all diff --git a/data/cache/.gitignore b/data/cache/.gitignore new file mode 100644 index 000000000..0307e6493 --- /dev/null +++ b/data/cache/.gitignore @@ -0,0 +1 @@ +*.spc
\ No newline at end of file diff --git a/data/cache/index.html b/data/cache/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/cache/index.html @@ -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> diff --git a/data/do-install.txt b/data/do-install.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/data/do-install.txt diff --git a/data/favicons/.gitignore b/data/favicons/.gitignore new file mode 100644 index 000000000..5ec725522 --- /dev/null +++ b/data/favicons/.gitignore @@ -0,0 +1,2 @@ +*.ico +*.txt
\ No newline at end of file diff --git a/data/favicons/index.html b/data/favicons/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/favicons/index.html @@ -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> diff --git a/data/index.html b/data/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/index.html @@ -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> diff --git a/data/log/.gitignore b/data/log/.gitignore new file mode 100644 index 000000000..bf0824e59 --- /dev/null +++ b/data/log/.gitignore @@ -0,0 +1 @@ +*.log
\ No newline at end of file diff --git a/data/log/index.html b/data/log/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/log/index.html @@ -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> diff --git a/data/persona/.gitignore b/data/persona/.gitignore new file mode 100644 index 000000000..314f02b1b --- /dev/null +++ b/data/persona/.gitignore @@ -0,0 +1 @@ +*.txt
\ No newline at end of file diff --git a/data/persona/index.html b/data/persona/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/persona/index.html @@ -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> diff --git a/data/shares.php b/data/shares.php new file mode 100644 index 000000000..44176f1bf --- /dev/null +++ b/data/shares.php @@ -0,0 +1,75 @@ +<?php + +/* + * This is a configuration file. You shouldn't modify it unless you know what + * you are doing. If you want to add a share type, this is where you need to do + * it. + * + * For each share there is different configuration options. Here is the description + * of those options: + * - url is a mandatory option. It is a string representing the share URL. It + * supports 3 different placeholders for custom data. The ~URL~ placeholder + * represents the URL of the system used to share, it is configured by the + * user. The ~LINK~ placeholder represents the link of the shared article. + * The ~TITLE~ placeholder represents the title of the shared article. + * - transform is an array of transformation to apply on links and titles + * - help is a URL to a help page + */ + +return array( + 'shaarli' => array( + 'url' => '~URL~?post=~LINK~&title=~TITLE~&source=FreshRSS', + 'transform' => array('urlencode'), + 'help' => 'http://sebsauvage.net/wiki/doku.php?id=php:shaarli', + 'form' => 'advanced', + ), + 'blogotext' => array( + 'url' => '~URL~/admin/links.php?url=~LINK~', + 'transform' => array(), + 'help' => 'http://lehollandaisvolant.net/blogotext/fr/', + 'form' => 'advanced', + ), + 'wallabag' => array( + 'url' => '~URL~?action=add&url=~LINK~', + 'transform' => array( + 'link' => array('base64_encode'), + 'title' => array(), + ), + 'help' => 'http://www.wallabag.org/', + 'form' => 'advanced', + ), + 'diaspora' => array( + 'url' => '~URL~/bookmarklet?url=~LINK~&title=~TITLE~', + 'transform' => array('urlencode'), + 'help' => 'https://diasporafoundation.org/', + 'form' => 'advanced', + ), + 'twitter' => array( + 'url' => 'https://twitter.com/share?url=~LINK~&text=~TITLE~', + 'transform' => array('urlencode'), + 'form' => 'simple', + ), + 'g+' => array( + 'url' => 'https://plus.google.com/share?url=~LINK~', + 'transform' => array('urlencode'), + 'form' => 'simple', + ), + 'facebook' => array( + 'url' => 'https://www.facebook.com/sharer.php?u=~LINK~&t=~TITLE~', + 'transform' => array('urlencode'), + 'form' => 'simple', + ), + 'email' => array( + 'url' => 'mailto:?subject=~TITLE~&body=~LINK~', + 'transform' => array( + 'link' => array('urlencode'), + 'title' => array(), + ), + 'form' => 'simple', + ), + 'print' => array( + 'url' => '#', + 'transform' => array(), + 'form' => 'simple', + ), +); diff --git a/data/tokens/.gitignore b/data/tokens/.gitignore new file mode 100644 index 000000000..2211df63d --- /dev/null +++ b/data/tokens/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/data/tokens/index.html b/data/tokens/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/tokens/index.html @@ -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> diff --git a/index.html b/index.html new file mode 100644 index 000000000..6ac025960 --- /dev/null +++ b/index.html @@ -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=p/" /> +<title>Redirection</title> +<meta name="robots" content="noindex,nofollow" /> +</head> + +<body> +<p><a href="p/">FreshRSS</a></p> +</body> +</html> @@ -1,2 +1,3 @@ <?php -header('Location: public/');
\ No newline at end of file +header('Location: p/', true, 301); +include('index.html'); diff --git a/lib/.htaccess b/lib/.htaccess new file mode 100644 index 000000000..9e768397d --- /dev/null +++ b/lib/.htaccess @@ -0,0 +1,3 @@ +Order Allow,Deny +Deny from all +Satisfy all diff --git a/lib/JSON.php b/lib/JSON.php new file mode 100644 index 000000000..8dc8a6f01 --- /dev/null +++ b/lib/JSON.php @@ -0,0 +1,933 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ +/** + * Converts to and from JSON format. + * + * JSON (JavaScript Object Notation) is a lightweight data-interchange + * format. It is easy for humans to read and write. It is easy for machines + * to parse and generate. It is based on a subset of the JavaScript + * Programming Language, Standard ECMA-262 3rd Edition - December 1999. + * This feature can also be found in Python. JSON is a text format that is + * completely language independent but uses conventions that are familiar + * to programmers of the C-family of languages, including C, C++, C#, Java, + * JavaScript, Perl, TCL, and many others. These properties make JSON an + * ideal data-interchange language. + * + * This package provides a simple encoder and decoder for JSON notation. It + * is intended for use with client-side Javascript applications that make + * use of HTTPRequest to perform server communication functions - data can + * be encoded into JSON notation for use in a client-side javascript, or + * decoded from incoming Javascript requests. JSON format is native to + * Javascript, and can be directly eval()'ed with no further parsing + * overhead + * + * All strings should be in ASCII or UTF-8 format! + * + * LICENSE: Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: Redistributions of source code must retain the + * above copyright notice, this list of conditions and the following + * disclaimer. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * @category + * @package Services_JSON + * @author Michal Migurski <mike-json@teczno.com> + * @author Matt Knapp <mdknapp[at]gmail[dot]com> + * @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> + * @copyright 2005 Michal Migurski + * @version CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $ + * @license http://www.opensource.org/licenses/bsd-license.php + * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 + */ + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_SLICE', 1); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_STR', 2); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_ARR', 3); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_OBJ', 4); + +/** + * Marker constant for Services_JSON::decode(), used to flag stack state + */ +define('SERVICES_JSON_IN_CMT', 5); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_LOOSE_TYPE', 16); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_SUPPRESS_ERRORS', 32); + +/** + * Behavior switch for Services_JSON::decode() + */ +define('SERVICES_JSON_USE_TO_JSON', 64); + +/** + * Converts to and from JSON format. + * + * Brief example of use: + * + * <code> + * // create a new instance of Services_JSON + * $json = new Services_JSON(); + * + * // convert a complexe value to JSON notation, and send it to the browser + * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); + * $output = $json->encode($value); + * + * print($output); + * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] + * + * // accept incoming POST data, assumed to be in JSON notation + * $input = file_get_contents('php://input', 1000000); + * $value = $json->decode($input); + * </code> + */ +class Services_JSON +{ + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + * - SERVICES_JSON_USE_TO_JSON: call toJSON when serializing objects + * It serializes the return value from the toJSON call rather + * than the object it'self, toJSON can return associative arrays, + * strings or numbers, if you return an object, make sure it does + * not have a toJSON method, otherwise an error will occur. + */ + function Services_JSON($use = 0) + { + $this->use = $use; + $this->_mb_strlen = function_exists('mb_strlen'); + $this->_mb_convert_encoding = function_exists('mb_convert_encoding'); + $this->_mb_substr = function_exists('mb_substr'); + } + // private - cache the mbstring lookup results.. + var $_mb_strlen = false; + var $_mb_substr = false; + var $_mb_convert_encoding = false; + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if($this->_mb_convert_encoding) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if($this->_mb_convert_encoding) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch($this->strlen8($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format (and sends JSON Header) + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var) + { + header('Content-type: application/json'); + return $this->encodeUnsafe($var); + } + /** + * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!) + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encodeUnsafe($var) + { + // see bug #16908 - regarding numeric locale printing + $lc = setlocale(LC_NUMERIC, 0); + setlocale(LC_NUMERIC, 'C'); + $ret = $this->_encode($var); + setlocale(LC_NUMERIC, $lc); + return $ret; + + } + /** + * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function _encode($var) + { + + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = $this->strlen8($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + if ($c+1 >= $strlen_var) { + $c += 1; + $ascii .= '?'; + break; + } + + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + if ($c+2 >= $strlen_var) { + $c += 2; + $ascii .= '?'; + break; + } + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + @ord($var{$c + 1}), + @ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + if ($c+3 >= $strlen_var) { + $c += 3; + $ascii .= '?'; + break; + } + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFC) == 0xF8): + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + if ($c+4 >= $strlen_var) { + $c += 4; + $ascii .= '?'; + break; + } + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4})); + $c += 4; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xFE) == 0xFC): + if ($c+5 >= $strlen_var) { + $c += 5; + $ascii .= '?'; + break; + } + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3}), + ord($var{$c + 4}), + ord($var{$c + 5})); + $c += 5; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + } + } + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + } + + // treat it like a regular array + $elements = array_map(array($this, '_encode'), $var); + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . join(',', $elements) . ']'; + + case 'object': + + // support toJSON methods. + if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) { + // this may end up allowing unlimited recursion + // so we check the return value to make sure it's not got the same method. + $recode = $var->toJSON(); + + if (method_exists($recode, 'toJSON')) { + + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(class_name($var). + " toJSON returned an object with a toJSON method."); + + } + + return $this->_encode( $recode ); + } + + $vars = get_object_vars($var); + + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . join(',', $properties) . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->_encode($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->_encode(strval($name)) . ':' . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = $this->substr8($str, 0, 1); + $chrs = $this->substr8($str, 1, -1); + $utf8 = ''; + $strlen_chrs = $this->strlen8($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2))) + . chr(hexdec($this->substr8($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= $this->substr8($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array('what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = $this->substr8($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = $this->strlen8($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = $this->substr8($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = $this->substr8($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B")); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) { + // found a quote, we're in a string, and it's not escaped + // we know that it's not escaped becase there is _not_ an + // odd number of backslashes at the end of the string so far + array_pop($stk); + //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } + + /** + * Calculates length of string in bytes + * @param string + * @return integer length + */ + function strlen8( $str ) + { + if ( $this->_mb_strlen ) { + return mb_strlen( $str, "8bit" ); + } + return strlen( $str ); + } + + /** + * Returns part of a string, interpreting $start and $length as number of bytes. + * @param string + * @param integer start + * @param integer length + * @return integer length + */ + function substr8( $string, $start, $length=false ) + { + if ( $length === false ) { + $length = $this->strlen8( $string ) - $start; + } + if ( $this->_mb_substr ) { + return mb_substr( $string, $start, $length, "8bit" ); + } + return substr( $string, $start, $length ); + } + +} + +if (class_exists('PEAR_Error')) { + + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } + +} else { + + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } + +} diff --git a/lib/minz/ActionController.php b/lib/Minz/ActionController.php index ab9389dbd..b47c54554 100755..100644 --- a/lib/minz/ActionController.php +++ b/lib/Minz/ActionController.php @@ -7,18 +7,14 @@ /** * La classe ActionController représente le contrôleur de l'application */ -class ActionController { - protected $router; +class Minz_ActionController { protected $view; /** * Constructeur - * @param $controller nom du controller - * @param $action nom de l'action à lancer */ - public function __construct ($router) { - $this->router = $router; - $this->view = new View (); + public function __construct () { + $this->view = new Minz_View (); $this->view->attributeParams (); } diff --git a/lib/Minz/ActionException.php b/lib/Minz/ActionException.php new file mode 100644 index 000000000..c566a076f --- /dev/null +++ b/lib/Minz/ActionException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ActionException extends Minz_Exception { + public function __construct ($controller_name, $action_name, $code = self::ERROR) { + $message = '`' . $action_name . '` cannot be invoked on `' + . $controller_name . '`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/BadConfigurationException.php b/lib/Minz/BadConfigurationException.php new file mode 100644 index 000000000..a7b77d687 --- /dev/null +++ b/lib/Minz/BadConfigurationException.php @@ -0,0 +1,9 @@ +<?php +class Minz_BadConfigurationException extends Minz_Exception { + public function __construct ($part_missing, $code = self::ERROR) { + $message = '`' . $part_missing + . '` in the configuration file is missing or is misconfigured'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php new file mode 100644 index 000000000..4e9da58b4 --- /dev/null +++ b/lib/Minz/Configuration.php @@ -0,0 +1,375 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Configuration permet de gérer la configuration de l'application + */ +class Minz_Configuration { + const CONF_PATH_NAME = '/config.php'; + + /** + * VERSION est la version actuelle de MINZ + */ + const VERSION = '1.3.1.freshrss'; // version spéciale FreshRSS + + /** + * valeurs possibles pour l'"environment" + * SILENT rend l'application muette (pas de log) + * PRODUCTION est recommandée pour une appli en production + * (log les erreurs critiques) + * DEVELOPMENT log toutes les erreurs + */ + const SILENT = 0; + const PRODUCTION = 1; + const DEVELOPMENT = 2; + + /** + * définition des variables de configuration + * $salt une chaîne de caractères aléatoires (obligatoire) + * $environment gère le niveau d'affichage pour log et erreurs + * $base_url le chemin de base pour accéder à l'application + * $title le nom de l'application + * $language la langue par défaut de l'application + * $db paramètres pour la base de données (tableau) + * - host le serveur de la base + * - user nom d'utilisateur + * - password mot de passe de l'utilisateur + * - base le nom de la base de données + */ + private static $salt = ''; + private static $environment = Minz_Configuration::PRODUCTION; + private static $base_url = ''; + private static $title = ''; + private static $language = 'en'; + private static $default_user = ''; + private static $allow_anonymous = false; + private static $allow_anonymous_refresh = false; + private static $auth_type = 'none'; + private static $api_enabled = false; + private static $unsafe_autologin_enabled = false; + + private static $db = array ( + 'type' => 'mysql', + 'host' => '', + 'user' => '', + 'password' => '', + 'base' => '', + 'prefix' => '', + ); + + /* + * Getteurs + */ + public static function salt () { + return self::$salt; + } + public static function environment ($str = false) { + $env = self::$environment; + + if ($str) { + switch (self::$environment) { + case self::SILENT: + $env = 'silent'; + break; + case self::DEVELOPMENT: + $env = 'development'; + break; + case self::PRODUCTION: + default: + $env = 'production'; + } + } + + return $env; + } + public static function baseUrl () { + return self::$base_url; + } + public static function title () { + return self::$title; + } + public static function language () { + return self::$language; + } + public static function dataBase () { + return self::$db; + } + public static function defaultUser () { + return self::$default_user; + } + public static function isAdmin($currentUser) { + return $currentUser === self::$default_user; + } + public static function allowAnonymous() { + return self::$allow_anonymous; + } + public static function allowAnonymousRefresh() { + return self::$allow_anonymous_refresh; + } + public static function authType() { + return self::$auth_type; + } + public static function needsLogin() { + return self::$auth_type !== 'none'; + } + public static function canLogIn() { + return self::$auth_type === 'form' || self::$auth_type === 'persona'; + } + public static function apiEnabled() { + return self::$api_enabled; + } + public static function unsafeAutologinEnabled() { + return self::$unsafe_autologin_enabled; + } + + public static function _allowAnonymous($allow = false) { + self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); + } + public static function _allowAnonymousRefresh($allow = false) { + self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous(); + } + public static function _authType($value) { + $value = strtolower($value); + switch ($value) { + case 'form': + case 'http_auth': + case 'persona': + case 'none': + self::$auth_type = $value; + break; + } + self::_allowAnonymous(self::$allow_anonymous); + } + + public static function _enableApi($value = false) { + self::$api_enabled = (bool)$value; + } + public static function _enableAutologin($value = false) { + self::$unsafe_autologin_enabled = (bool)$value; + } + + /** + * Initialise les variables de configuration + * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + public static function init () { + try { + self::parseFile (); + self::setReporting (); + } catch (Minz_FileNotExistException $e) { + throw $e; + } catch (Minz_BadConfigurationException $e) { + throw $e; + } + } + + public static function writeFile() { + $ini_array = array( + 'general' => array( + 'environment' => self::environment(true), + 'salt' => self::$salt, + 'base_url' => self::$base_url, + 'title' => self::$title, + 'default_user' => self::$default_user, + 'allow_anonymous' => self::$allow_anonymous, + 'allow_anonymous_refresh' => self::$allow_anonymous_refresh, + 'auth_type' => self::$auth_type, + 'api_enabled' => self::$api_enabled, + 'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled, + ), + 'db' => self::$db, + ); + @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); + $result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, "<?php\n return " . var_export($ini_array, true) . ';'); + if (function_exists('opcache_invalidate')) { + opcache_invalidate(DATA_PATH . self::CONF_PATH_NAME); //Clear PHP 5.5+ cache for include + } + return (bool)$result; + } + + /** + * Parse un fichier de configuration + * @exception Minz_PermissionDeniedException si le CONF_PATH_NAME n'est pas accessible + * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + */ + private static function parseFile () { + $ini_array = include(DATA_PATH . self::CONF_PATH_NAME); + + if (!is_array($ini_array)) { + throw new Minz_PermissionDeniedException ( + DATA_PATH . self::CONF_PATH_NAME, + Minz_Exception::ERROR + ); + } + + // [general] est obligatoire + if (!isset ($ini_array['general'])) { + throw new Minz_BadConfigurationException ( + '[general]', + Minz_Exception::ERROR + ); + } + $general = $ini_array['general']; + + // salt est obligatoire + if (!isset ($general['salt'])) { + if (isset($general['sel_application'])) { //v0.6 + $general['salt'] = $general['sel_application']; + } else { + throw new Minz_BadConfigurationException ( + 'salt', + Minz_Exception::ERROR + ); + } + } + self::$salt = $general['salt']; + + if (isset ($general['environment'])) { + switch ($general['environment']) { + case 'silent': + self::$environment = Minz_Configuration::SILENT; + break; + case 'development': + self::$environment = Minz_Configuration::DEVELOPMENT; + break; + case 'production': + self::$environment = Minz_Configuration::PRODUCTION; + break; + default: + if ($general['environment'] >= 0 && + $general['environment'] <= 2) { + // fallback 0.7-beta + self::$environment = $general['environment']; + } else { + throw new Minz_BadConfigurationException ( + 'environment', + Minz_Exception::ERROR + ); + } + } + + } + if (isset ($general['base_url'])) { + self::$base_url = $general['base_url']; + } + + if (isset ($general['title'])) { + self::$title = $general['title']; + } + if (isset ($general['language'])) { + self::$language = $general['language']; + } + if (isset ($general['default_user'])) { + self::$default_user = $general['default_user']; + } + if (isset ($general['auth_type'])) { + self::_authType($general['auth_type']); + } + if (isset ($general['allow_anonymous'])) { + self::$allow_anonymous = ( + ((bool)($general['allow_anonymous'])) && + ($general['allow_anonymous'] !== 'no') + ); + } + if (isset ($general['allow_anonymous_refresh'])) { + self::$allow_anonymous_refresh = ( + ((bool)($general['allow_anonymous_refresh'])) && + ($general['allow_anonymous_refresh'] !== 'no') + ); + } + if (isset ($general['api_enabled'])) { + self::$api_enabled = ( + ((bool)($general['api_enabled'])) && + ($general['api_enabled'] !== 'no') + ); + } + if (isset ($general['unsafe_autologin_enabled'])) { + self::$unsafe_autologin_enabled = ( + ((bool)($general['unsafe_autologin_enabled'])) && + ($general['unsafe_autologin_enabled'] !== 'no') + ); + } + + // Base de données + if (isset ($ini_array['db'])) { + $db = $ini_array['db']; + if (empty($db['type'])) { + throw new Minz_BadConfigurationException ( + 'type', + Minz_Exception::ERROR + ); + } + switch ($db['type']) { + case 'mysql': + if (empty($db['host'])) { + throw new Minz_BadConfigurationException ( + 'host', + Minz_Exception::ERROR + ); + } + if (empty($db['user'])) { + throw new Minz_BadConfigurationException ( + 'user', + Minz_Exception::ERROR + ); + } + if (!isset($db['password'])) { + throw new Minz_BadConfigurationException ( + 'password', + Minz_Exception::ERROR + ); + } + if (empty($db['base'])) { + throw new Minz_BadConfigurationException ( + 'base', + Minz_Exception::ERROR + ); + } + self::$db['host'] = $db['host']; + self::$db['user'] = $db['user']; + self::$db['password'] = $db['password']; + self::$db['base'] = $db['base']; + if (isset($db['prefix'])) { + self::$db['prefix'] = $db['prefix']; + } + break; + case 'sqlite': + self::$db['host'] = ''; + self::$db['user'] = ''; + self::$db['password'] = ''; + self::$db['base'] = ''; + self::$db['prefix'] = ''; + break; + default: + throw new Minz_BadConfigurationException ( + 'type', + Minz_Exception::ERROR + ); + break; + } + self::$db['type'] = $db['type']; + } + } + + private static function setReporting() { + switch (self::$environment) { + case self::PRODUCTION: + error_reporting(E_ALL); + ini_set('display_errors','Off'); + ini_set('log_errors', 'On'); + break; + case self::DEVELOPMENT: + error_reporting(E_ALL); + ini_set('display_errors','On'); + ini_set('log_errors', 'On'); + break; + case self::SILENT: + error_reporting(0); + break; + } + } +} diff --git a/lib/Minz/ControllerNotActionControllerException.php b/lib/Minz/ControllerNotActionControllerException.php new file mode 100644 index 000000000..535a1377e --- /dev/null +++ b/lib/Minz/ControllerNotActionControllerException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ControllerNotActionControllerException extends Minz_Exception { + public function __construct ($controller_name, $code = self::ERROR) { + $message = 'Controller `' . $controller_name + . '` isn\'t instance of ActionController'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/ControllerNotExistException.php b/lib/Minz/ControllerNotExistException.php new file mode 100644 index 000000000..523867d11 --- /dev/null +++ b/lib/Minz/ControllerNotExistException.php @@ -0,0 +1,9 @@ +<?php +class Minz_ControllerNotExistException extends Minz_Exception { + public function __construct ($controller_name, $code = self::ERROR) { + $message = 'Controller `' . $controller_name + . '` doesn\'t exist'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/CurrentPagePaginationException.php b/lib/Minz/CurrentPagePaginationException.php new file mode 100644 index 000000000..74214d879 --- /dev/null +++ b/lib/Minz/CurrentPagePaginationException.php @@ -0,0 +1,8 @@ +<?php +class Minz_CurrentPagePaginationException extends Minz_Exception { + public function __construct ($page) { + $message = 'Page number `' . $page . '` doesn\'t exist'; + + parent::__construct ($message, self::ERROR); + } +} diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php new file mode 100644 index 000000000..f62a92911 --- /dev/null +++ b/lib/Minz/Dispatcher.php @@ -0,0 +1,117 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action + * déterminée dans la Request + * C'est un singleton + */ +class Minz_Dispatcher { + const CONTROLLERS_PATH_NAME = '/Controllers'; + + /* singleton */ + private static $instance = null; + private static $needsReset; + + private $controller; + + /** + * Récupère l'instance du Dispatcher + */ + public static function getInstance () { + if (self::$instance === null) { + self::$instance = new Minz_Dispatcher (); + } + return self::$instance; + } + + /** + * Lance le controller indiqué dans Request + * Remplit le body de Response à partir de la Vue + * @exception Minz_Exception + */ + public function run () { + do { + self::$needsReset = false; + + try { + $this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller'); + $this->controller->init (); + $this->controller->firstAction (); + if (!self::$needsReset) { + $this->launchAction ( + Minz_Request::actionName () + . 'Action' + ); + } + $this->controller->lastAction (); + + if (!self::$needsReset) { + $this->controller->view ()->build (); + } + } catch (Minz_Exception $e) { + throw $e; + } + } while (self::$needsReset); + } + + /** + * Informe le contrôleur qu'il doit recommancer car la requête a été modifiée + */ + public static function reset() { + self::$needsReset = true; + } + + /** + * Instancie le Controller + * @param $controller_name le nom du controller à instancier + * @exception ControllerNotExistException le controller n'existe pas + * @exception ControllerNotActionControllerException controller n'est + * > pas une instance de ActionController + */ + private function createController ($controller_name) { + $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' + . $controller_name . '.php'; + + if (!class_exists ($controller_name)) { + throw new Minz_ControllerNotExistException ( + $controller_name, + Minz_Exception::ERROR + ); + } + $this->controller = new $controller_name (); + + if (! ($this->controller instanceof Minz_ActionController)) { + throw new Minz_ControllerNotActionControllerException ( + $controller_name, + Minz_Exception::ERROR + ); + } + } + + /** + * Lance l'action sur le controller du dispatcher + * @param $action_name le nom de l'action + * @exception ActionException si on ne peut pas exécuter l'action sur + * le controller + */ + private function launchAction ($action_name) { + if (!is_callable (array ( + $this->controller, + $action_name + ))) { + throw new Minz_ActionException ( + get_class ($this->controller), + $action_name, + Minz_Exception::ERROR + ); + } + call_user_func (array ( + $this->controller, + $action_name + )); + } +} diff --git a/lib/minz/Error.php b/lib/Minz/Error.php index 0e8c2f60b..c8222a430 100755..100644 --- a/lib/minz/Error.php +++ b/lib/Minz/Error.php @@ -1,5 +1,5 @@ <?php -/** +/** * MINZ - Copyright 2011 Marien Fressinaud * Sous licence AGPL3 <http://www.gnu.org/licenses/> */ @@ -7,7 +7,7 @@ /** * La classe Error permet de lancer des erreurs HTTP */ -class Error { +class Minz_Error { public function __construct () { } /** @@ -21,43 +21,59 @@ class Error { */ public static function error ($code = 404, $logs = array (), $redirect = false) { $logs = self::processLogs ($logs); - $error_filename = APP_PATH . '/controllers/errorController.php'; - + $error_filename = APP_PATH . '/Controllers/errorController.php'; + + switch ($code) { + case 200 : + header('HTTP/1.1 200 OK'); + break; + case 403 : + header('HTTP/1.1 403 Forbidden'); + break; + case 404 : + header('HTTP/1.1 404 Not Found'); + break; + case 500 : + header('HTTP/1.1 500 Internal Server Error'); + break; + case 503 : + header('HTTP/1.1 503 Service Unavailable'); + break; + default : + header('HTTP/1.1 500 Internal Server Error'); + } + if (file_exists ($error_filename)) { $params = array ( 'code' => $code, 'logs' => $logs ); - - Response::setHeader ($code); + if ($redirect) { - Request::forward (array ( + Minz_Request::forward (array ( 'c' => 'error' ), true); } else { - Request::forward (array ( + Minz_Request::forward (array ( 'c' => 'error', 'params' => $params ), false); } } else { - $text = '<h1>An error occured</h1>'."\n"; - + echo '<h1>An error occured</h1>' . "\n"; + if (!empty ($logs)) { - $text .= '<ul>'."\n"; + echo '<ul>' . "\n"; foreach ($logs as $log) { - $text .= '<li>' . $log . '</li>'."\n"; + echo '<li>' . $log . '</li>' . "\n"; } - $text .= '</ul>'."\n"; + echo '</ul>' . "\n"; } - - Response::setHeader ($code); - Response::setBody ($text); - Response::send (); + exit (); } } - + /** * Permet de retourner les logs de façon à n'avoir que * ceux que l'on veut réellement @@ -66,12 +82,12 @@ class Error { * > en fonction de l'environment */ private static function processLogs ($logs) { - $env = Configuration::environment (); + $env = Minz_Configuration::environment (); $logs_ok = array (); $error = array (); $warning = array (); $notice = array (); - + if (isset ($logs['error'])) { $error = $logs['error']; } @@ -81,14 +97,14 @@ class Error { if (isset ($logs['notice'])) { $notice = $logs['notice']; } - - if ($env == Configuration::PRODUCTION) { + + if ($env == Minz_Configuration::PRODUCTION) { $logs_ok = $error; } - if ($env == Configuration::DEVELOPMENT) { + if ($env == Minz_Configuration::DEVELOPMENT) { $logs_ok = array_merge ($error, $warning, $notice); } - + return $logs_ok; } } diff --git a/lib/Minz/Exception.php b/lib/Minz/Exception.php new file mode 100644 index 000000000..b5e71e0d8 --- /dev/null +++ b/lib/Minz/Exception.php @@ -0,0 +1,16 @@ +<?php +class Minz_Exception extends Exception { + const ERROR = 0; + const WARNING = 10; + const NOTICE = 20; + + public function __construct ($message, $code = self::ERROR) { + if ($code != Minz_Exception::ERROR + && $code != Minz_Exception::WARNING + && $code != Minz_Exception::NOTICE) { + $code = Minz_Exception::ERROR; + } + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/FileNotExistException.php b/lib/Minz/FileNotExistException.php new file mode 100644 index 000000000..f8dfbdf66 --- /dev/null +++ b/lib/Minz/FileNotExistException.php @@ -0,0 +1,8 @@ +<?php +class Minz_FileNotExistException extends Minz_Exception { + public function __construct ($file_name, $code = self::ERROR) { + $message = 'File not found: `' . $file_name.'`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php new file mode 100644 index 000000000..f13882801 --- /dev/null +++ b/lib/Minz/FrontController.php @@ -0,0 +1,117 @@ +<?php +# ***** BEGIN LICENSE BLOCK ***** +# MINZ - a free PHP Framework like Zend Framework +# Copyright (C) 2011 Marien Fressinaud +# +# 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/>. +# +# ***** END LICENSE BLOCK ***** + +/** + * La classe FrontController est le Dispatcher du framework, elle lance l'application + * Elle est appelée en général dans le fichier index.php à la racine du serveur + */ +class Minz_FrontController { + protected $dispatcher; + + /** + * Constructeur + * Initialise le dispatcher, met à jour la Request + */ + public function __construct () { + if (LOG_PATH === false) { + $this->killApp ('Path not found: LOG_PATH'); + } + + try { + Minz_Configuration::init (); + + Minz_Request::init (); + + $url = $this->buildUrl(); + $url['params'] = array_merge ( + $url['params'], + Minz_Request::fetchPOST () + ); + Minz_Request::forward ($url); + } catch (Minz_Exception $e) { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + $this->killApp ($e->getMessage ()); + } + + $this->dispatcher = Minz_Dispatcher::getInstance(); + } + + /** + * Retourne un tableau représentant l'url passée par la barre d'adresses + * @return tableau représentant l'url + */ + private function buildUrl() { + $url = array (); + + $url['c'] = Minz_Request::fetchGET ( + 'c', + Minz_Request::defaultControllerName () + ); + $url['a'] = Minz_Request::fetchGET ( + 'a', + Minz_Request::defaultActionName () + ); + $url['params'] = Minz_Request::fetchGET (); + + // post-traitement + unset ($url['params']['c']); + unset ($url['params']['a']); + + return $url; + } + + /** + * Démarre l'application (lance le dispatcher et renvoie la réponse) + */ + public function run () { + try { + $this->dispatcher->run(); + } catch (Minz_Exception $e) { + try { + Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + } catch (Minz_PermissionDeniedException $e) { + $this->killApp ($e->getMessage ()); + } + + if ($e instanceof Minz_FileNotExistException || + $e instanceof Minz_ControllerNotExistException || + $e instanceof Minz_ControllerNotActionControllerException || + $e instanceof Minz_ActionException) { + Minz_Error::error ( + 404, + array ('error' => array ($e->getMessage ())), + true + ); + } else { + $this->killApp (); + } + } + } + + /** + * Permet d'arrêter le programme en urgence + */ + private function killApp ($txt = '') { + if ($txt == '') { + $txt = 'See logs files'; + } + exit ('### Application problem ###<br />'."\n".$txt); + } +} diff --git a/lib/Minz/Helper.php b/lib/Minz/Helper.php new file mode 100644 index 000000000..f4a547c4e --- /dev/null +++ b/lib/Minz/Helper.php @@ -0,0 +1,33 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Helper représente une aide pour des tâches récurrentes + */ +class Minz_Helper { + /** + * Annule les effets des magic_quotes pour une variable donnée + * @param $var variable à traiter (tableau ou simple variable) + */ + public static function stripslashes_r($var) { + if (is_array($var)){ + return array_map(array('Minz_Helper', 'stripslashes_r'), $var); + } else { + return stripslashes($var); + } + } + + /** + * Wrapper for htmlspecialchars. + * Force UTf-8 value and can be used on array too. + */ + public static function htmlspecialchars_utf8($var) { + if (is_array($var)) { + return array_map(array('Minz_Helper', 'htmlspecialchars_utf8'), $var); + } + return htmlspecialchars($var, ENT_COMPAT, 'UTF-8'); + } +} diff --git a/lib/minz/Log.php b/lib/Minz/Log.php index c6f23d900..d3eaec2ae 100755..100644 --- a/lib/minz/Log.php +++ b/lib/Minz/Log.php @@ -1,5 +1,5 @@ <?php -/** +/** * MINZ - Copyright 2011 Marien Fressinaud * Sous licence AGPL3 <http://www.gnu.org/licenses/> */ @@ -12,12 +12,14 @@ class Minz_Log { * Les différents niveau de log * ERROR erreurs bloquantes de l'application * WARNING erreurs pouvant géner le bon fonctionnement, mais non bloquantes - * NOTICE messages d'informations, affichés pour le déboggage + * NOTICE erreurs mineures ou messages d'informations + * DEBUG Informations affichées pour le déboggage */ - const ERROR = 0; - const WARNING = 10; - const NOTICE = 20; - + const ERROR = 2; + const WARNING = 4; + const NOTICE = 8; + const DEBUG = 16; + /** * Enregistre un message dans un fichier de log spécifique * Message non loggué si @@ -29,15 +31,15 @@ class Minz_Log { * @param $file_name fichier de log, par défaut LOG_PATH/application.log */ public static function record ($information, $level, $file_name = null) { - $env = Configuration::environment (); - - if (! ($env == Configuration::SILENT - || ($env == Configuration::PRODUCTION - && ($level == Minz_Log::WARNING || $level == Minz_Log::NOTICE)))) { - if (is_null ($file_name)) { - $file_name = LOG_PATH . '/application.log'; + $env = Minz_Configuration::environment (); + + if (! ($env === Minz_Configuration::SILENT + || ($env === Minz_Configuration::PRODUCTION + && ($level >= Minz_Log::NOTICE)))) { + if ($file_name === null) { + $file_name = LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'; } - + switch ($level) { case Minz_Log::ERROR : $level_label = 'error'; @@ -48,30 +50,19 @@ class Minz_Log { case Minz_Log::NOTICE : $level_label = 'notice'; break; + case Minz_Log::DEBUG : + $level_label = 'debug'; + break; default : $level_label = 'unknown'; } - - if ($env == Configuration::PRODUCTION) { - $file = @fopen ($file_name, 'a'); - } else { - $file = fopen ($file_name, 'a'); - } - - if ($file !== false) { - $log = '[' . date('r') . ']'; - $log .= ' [' . $level_label . ']'; - $log .= ' --- ' . $information . "\n"; - fwrite ($file, $log); - fclose ($file); - } else { - Error::error ( - 500, - array ('error' => array ( - 'Permission is denied for `' - . $file_name . '`') - ) - ); + + $log = '[' . date('r') . ']' + . ' [' . $level_label . ']' + . ' --- ' . $information . "\n"; + + if (file_put_contents($file_name, $log, FILE_APPEND | LOCK_EX) === false) { + throw new Minz_PermissionDeniedException($file_name, Minz_Exception::ERROR); } } } @@ -86,7 +77,24 @@ class Minz_Log { $msg_get = str_replace("\n", '', '$_GET content : ' . print_r($_GET, true)); $msg_post = str_replace("\n", '', '$_POST content : ' . print_r($_POST, true)); - self::record($msg_get, Minz_Log::NOTICE, $file_name); - self::record($msg_post, Minz_Log::NOTICE, $file_name); + self::record($msg_get, Minz_Log::DEBUG, $file_name); + self::record($msg_post, Minz_Log::DEBUG, $file_name); + } + + /** + * Some helpers to Minz_Log::record() method + * Parameters are the same of those of the record() method. + */ + public static function debug($msg, $file_name = null) { + self::record($msg, Minz_Log::DEBUG, $file_name); + } + public static function notice($msg, $file_name = null) { + self::record($msg, Minz_Log::NOTICE, $file_name); + } + public static function warning($msg, $file_name = null) { + self::record($msg, Minz_Log::WARNING, $file_name); + } + public static function error($msg, $file_name = null) { + self::record($msg, Minz_Log::ERROR, $file_name); } } diff --git a/lib/minz/Model.php b/lib/Minz/Model.php index 37fc19ed1..adbaba942 100755..100644 --- a/lib/minz/Model.php +++ b/lib/Minz/Model.php @@ -7,6 +7,6 @@ /** * La classe Model représente un modèle de l'application (représentation MVC) */ -class Model { +class Minz_Model { } diff --git a/lib/Minz/ModelArray.php b/lib/Minz/ModelArray.php new file mode 100644 index 000000000..ff23dbc83 --- /dev/null +++ b/lib/Minz/ModelArray.php @@ -0,0 +1,81 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php + */ +class Minz_ModelArray { + /** + * $filename est le nom du fichier + */ + protected $filename; + + /** + * Ouvre le fichier indiqué, charge le tableau dans $array et le $filename + * @param $filename le nom du fichier à ouvrir contenant un tableau + * Remarque : $array sera obligatoirement un tableau + */ + public function __construct ($filename) { + $this->filename = $filename; + } + + protected function loadArray() { + if (!file_exists($this->filename)) { + throw new Minz_FileNotExistException($this->filename, Minz_Exception::WARNING); + } + elseif (($handle = $this->getLock()) === false) { + throw new Minz_PermissionDeniedException($this->filename); + } else { + $data = include($this->filename); + $this->releaseLock($handle); + + if ($data === false) { + throw new Minz_PermissionDeniedException($this->filename); + } elseif (!is_array($data)) { + $data = array(); + } + return $data; + } + } + + /** + * Sauve le tableau $array dans le fichier $filename + **/ + protected function writeArray($array) { + if (file_put_contents($this->filename, "<?php\n return " . var_export($array, 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 + } + return true; + } + + private function getLock() { + $handle = fopen($this->filename, 'r'); + if ($handle === false) { + return false; + } + + $count = 50; + while (!flock($handle, LOCK_SH) && $count > 0) { + $count--; + usleep(1000); + } + + if ($count > 0) { + return $handle; + } else { + fclose($handle); + return false; + } + } + + private function releaseLock($handle) { + flock($handle, LOCK_UN); + fclose($handle); + } +} diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php new file mode 100644 index 000000000..b4bfca746 --- /dev/null +++ b/lib/Minz/ModelPdo.php @@ -0,0 +1,124 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * La classe Model_sql représente le modèle interragissant avec les bases de données + * Seul la connexion MySQL est prise en charge pour le moment + */ +class Minz_ModelPdo { + + /** + * Partage la connexion à la base de données entre toutes les instances. + */ + public static $useSharedBd = true; + private static $sharedBd = null; + private static $sharedPrefix; + protected static $sharedDbType; + + /** + * $bd variable représentant la base de données + */ + protected $bd; + + protected $prefix; + + public function dbType() { + return self::$sharedDbType; + } + + /** + * Créé la connexion à la base de données à l'aide des variables + * HOST, BASE, USER et PASS définies dans le fichier de configuration + */ + public function __construct($currentUser = null) { + if (self::$useSharedBd && self::$sharedBd != null && $currentUser === null) { + $this->bd = self::$sharedBd; + $this->prefix = self::$sharedPrefix; + return; + } + + $db = Minz_Configuration::dataBase(); + + if ($currentUser === null) { + $currentUser = Minz_Session::param('currentUser', '_'); + } + + try { + $type = $db['type']; + if ($type === 'mysql') { + $string = 'mysql:host=' . $db['host'] + . ';dbname=' . $db['base'] + . ';charset=utf8'; + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + ); + $this->prefix = $db['prefix'] . $currentUser . '_'; + } elseif ($type === 'sqlite') { + $string = 'sqlite:' . DATA_PATH . '/' . $currentUser . '.sqlite'; + $driver_options = array( + //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + $this->prefix = ''; + } else { + throw new Minz_PDOConnectionException( + 'Invalid database type!', + $db['user'], Minz_Exception::ERROR + ); + } + self::$sharedDbType = $type; + self::$sharedPrefix = $this->prefix; + + $this->bd = new MinzPDO( + $string, + $db['user'], + $db['password'], + $driver_options + ); + if ($type === 'sqlite') { + $this->bd->exec('PRAGMA foreign_keys = ON;'); + } + self::$sharedBd = $this->bd; + } catch (Exception $e) { + throw new Minz_PDOConnectionException( + $string, + $db['user'], Minz_Exception::ERROR + ); + } + } + + public function beginTransaction() { + $this->bd->beginTransaction(); + } + public function commit() { + $this->bd->commit(); + } + public function rollBack() { + $this->bd->rollBack(); + } + + public static function clean() { + self::$sharedBd = null; + self::$sharedPrefix = ''; + } +} + +class MinzPDO extends PDO { + private static function check($statement) { + if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { + invalidateHttpCache(); + } + } + + public function prepare($statement, $driver_options = array()) { + MinzPDO::check($statement); + return parent::prepare($statement, $driver_options); + } + + public function exec($statement) { + MinzPDO::check($statement); + return parent::exec($statement); + } +} diff --git a/lib/Minz/PDOConnectionException.php b/lib/Minz/PDOConnectionException.php new file mode 100644 index 000000000..faf2e0fe9 --- /dev/null +++ b/lib/Minz/PDOConnectionException.php @@ -0,0 +1,9 @@ +<?php +class Minz_PDOConnectionException extends Minz_Exception { + public function __construct ($string_connection, $user, $code = self::ERROR) { + $message = 'Access to database is denied for `' . $user . '`' + . ' (`' . $string_connection . '`)'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/minz/Paginator.php b/lib/Minz/Paginator.php index 1a8376e75..5858e76a5 100755..100644 --- a/lib/minz/Paginator.php +++ b/lib/Minz/Paginator.php @@ -7,7 +7,7 @@ /** * La classe Paginator permet de gérer la pagination de l'application facilement */ -class Paginator { +class Minz_Paginator { /** * $items tableau des éléments à afficher/gérer */ diff --git a/lib/Minz/PermissionDeniedException.php b/lib/Minz/PermissionDeniedException.php new file mode 100644 index 000000000..61be530d3 --- /dev/null +++ b/lib/Minz/PermissionDeniedException.php @@ -0,0 +1,8 @@ +<?php +class Minz_PermissionDeniedException extends Minz_Exception { + public function __construct ($file_name, $code = self::ERROR) { + $message = 'Permission is denied for `' . $file_name.'`'; + + parent::__construct ($message, $code); + } +} diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php new file mode 100644 index 000000000..f7a24c026 --- /dev/null +++ b/lib/Minz/Request.php @@ -0,0 +1,227 @@ +<?php +/** + * MINZ - Copyright 2011 Marien Fressinaud + * Sous licence AGPL3 <http://www.gnu.org/licenses/> +*/ + +/** + * Request représente la requête http + */ +class Minz_Request { + private static $controller_name = ''; + private static $action_name = ''; + private static $params = array(); + + private static $default_controller_name = 'index'; + private static $default_action_name = 'index'; + + /** + * Getteurs + */ + public static function controllerName() { + return self::$controller_name; + } + public static function actionName() { + return self::$action_name; + } + public static function params() { + return self::$params; + } + public static function param($key, $default = false, $specialchars = false) { + if (isset(self::$params[$key])) { + $p = self::$params[$key]; + if (is_object($p) || $specialchars) { + return $p; + } else { + return Minz_Helper::htmlspecialchars_utf8($p); + } + } else { + return $default; + } + } + public static function defaultControllerName() { + return self::$default_controller_name; + } + public static function defaultActionName() { + return self::$default_action_name; + } + + /** + * Setteurs + */ + public static function _controllerName($controller_name) { + self::$controller_name = $controller_name; + } + public static function _actionName($action_name) { + self::$action_name = $action_name; + } + public static function _params($params) { + if (!is_array($params)) { + $params = array($params); + } + + self::$params = $params; + } + public static function _param($key, $value = false) { + if ($value === false) { + unset(self::$params[$key]); + } else { + self::$params[$key] = $value; + } + } + + /** + * Initialise la Request + */ + public static function init() { + self::magicQuotesOff(); + } + + /** + * Retourn le nom de domaine du site + */ + public static function getDomainName() { + return $_SERVER['HTTP_HOST']; + } + + /** + * Détermine la base de l'url + * @return la base de l'url + */ + public static function getBaseUrl() { + $defaultBaseUrl = Minz_Configuration::baseUrl(); + if (!empty($defaultBaseUrl)) { + return $defaultBaseUrl; + } elseif (isset($_SERVER['REQUEST_URI'])) { + return dirname($_SERVER['REQUEST_URI']) . '/'; + } else { + return '/'; + } + } + + /** + * Récupère l'URI de la requête + * @return l'URI + */ + public static function getURI() { + if (isset($_SERVER['REQUEST_URI'])) { + $base_url = self::getBaseUrl(); + $uri = $_SERVER['REQUEST_URI']; + + $len_base_url = strlen($base_url); + $real_uri = substr($uri, $len_base_url); + } else { + $real_uri = ''; + } + + return $real_uri; + } + + /** + * Relance une requête + * @param $url l'url vers laquelle est relancée la requête + * @param $redirect si vrai, force la redirection http + * > sinon, le dispatcher recharge en interne + */ + public static function forward($url = array(), $redirect = false) { + if (!is_array($url)) { + header('Location: ' . $url); + exit(); + } + + $url = Minz_Url::checkUrl($url); + + if ($redirect) { + header('Location: ' . Minz_Url::display($url, 'php')); + exit(); + } else { + self::_controllerName($url['c']); + self::_actionName($url['a']); + self::_params(array_merge( + self::$params, + $url['params'] + )); + Minz_Dispatcher::reset(); + } + } + + + /** + * Wrappers good notifications + redirection + * @param $msg notification content + * @param $url url array to where we should be forwarded + */ + public static function good($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + public static function bad($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + + /** + * Permet de récupérer une variable de type $_GET + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_GET[$param] + * $_GET si $param = false + * $default si $_GET[$param] n'existe pas + */ + public static function fetchGET($param = false, $default = false) { + if ($param === false) { + return $_GET; + } elseif (isset($_GET[$param])) { + return $_GET[$param]; + } else { + return $default; + } + } + + /** + * Permet de récupérer une variable de type $_POST + * @param $param nom de la variable + * @param $default valeur par défaut à attribuer à la variable + * @return $_POST[$param] + * $_POST si $param = false + * $default si $_POST[$param] n'existe pas + */ + public static function fetchPOST($param = false, $default = false) { + if ($param === false) { + return $_POST; + } elseif (isset($_POST[$param])) { + return $_POST[$param]; + } else { + return $default; + } + } + + /** + * Méthode désactivant les magic_quotes pour les variables + * $_GET + * $_POST + * $_COOKIE + */ + private static function magicQuotesOff() { + if (get_magic_quotes_gpc()) { + $_GET = Minz_Helper::stripslashes_r($_GET); + $_POST = Minz_Helper::stripslashes_r($_POST); + $_COOKIE = Minz_Helper::stripslashes_r($_COOKIE); + } + } + + public static function isPost() { + return isset($_SERVER['REQUEST_METHOD']) && + $_SERVER['REQUEST_METHOD'] === 'POST'; + } +} diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php new file mode 100644 index 000000000..af4de75bb --- /dev/null +++ b/lib/Minz/Session.php @@ -0,0 +1,93 @@ +<?php + +/** + * La classe Session gère la session utilisateur + */ +class Minz_Session { + /** + * Initialise la session, avec un nom + * Le nom de session est utilisé comme nom pour les cookies et les URLs(i.e. PHPSESSID). + * Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif + */ + public static function init($name) { + $cookie = session_get_cookie_params(); + self::keepCookie($cookie['lifetime']); + + // démarre la session + session_name($name); + session_start(); + } + + + /** + * Permet de récupérer une variable de session + * @param $p le paramètre à récupérer + * @return la valeur de la variable de session, false si n'existe pas + */ + public static function param($p, $default = false) { + return isset($_SESSION[$p]) ? $_SESSION[$p] : $default; + } + + + /** + * Permet de créer ou mettre à jour une variable de session + * @param $p le paramètre à créer ou modifier + * @param $v la valeur à attribuer, false pour supprimer + */ + public static function _param($p, $v = false) { + if ($v === false) { + unset($_SESSION[$p]); + } else { + $_SESSION[$p] = $v; + } + } + + + /** + * Permet d'effacer une session + * @param $force si à false, n'efface pas le paramètre de langue + */ + public static function unset_session($force = false) { + $language = self::param('language'); + + session_destroy(); + $_SESSION = array(); + + if (!$force) { + self::_param('language', $language); + Minz_Translate::reset(); + } + } + + + /** + * Spécifie la durée de vie des cookies + * @param $l la durée de vie + */ + public static function keepCookie($l) { + $cookie_dir = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI']; + session_set_cookie_params($l, $cookie_dir, '', false, true); + } + + + /** + * Régénère un id de session. + * Utile pour appeler session_set_cookie_params après session_start() + */ + public static function regenerateID() { + session_regenerate_id(true); + } + + public static function deleteLongTermCookie($name) { + setcookie($name, '', 1, '', '', false, true); + } + + public static function setLongTermCookie($name, $value, $expire) { + setcookie($name, $value, $expire, '', '', false, true); + } + + public static function getLongTermCookie($name) { + return isset($_COOKIE[$name]) ? $_COOKIE[$name] : null; + } + +} diff --git a/lib/minz/Translate.php b/lib/Minz/Translate.php index e8cbe4852..8c2f90041 100644 --- a/lib/minz/Translate.php +++ b/lib/Minz/Translate.php @@ -8,7 +8,7 @@ * La classe Translate se charge de la traduction * Utilise les fichiers du répertoire /app/i18n/ */ -class Translate { +class Minz_Translate { /** * $language est la langue à afficher */ @@ -18,28 +18,28 @@ class Translate { * $translates est le tableau de correspondance * $key => $traduction */ - private static $translates = array (); + private static $translates = array(); /** * Inclus le fichier de langue qui va bien * l'enregistre dans $translates */ - public static function init () { - $l = Configuration::language (); - self::$language = Session::param ('language', $l); + public static function init() { + $l = Minz_Configuration::language(); + self::$language = Minz_Session::param('language', $l); $l_path = APP_PATH . '/i18n/' . self::$language . '.php'; - if (file_exists ($l_path)) { - self::$translates = include ($l_path); + if (file_exists($l_path)) { + self::$translates = include($l_path); } } /** * Alias de init */ - public static function reset () { - self::init (); + public static function reset() { + self::init(); } /** @@ -48,24 +48,32 @@ class Translate { * @return la valeur correspondante à la clé * > si non présente dans le tableau, on retourne la clé elle-même */ - public static function t ($key) { + public static function t($key) { $translate = $key; - if (isset (self::$translates[$key])) { + if (isset(self::$translates[$key])) { $translate = self::$translates[$key]; } - $args = func_get_args (); + $args = func_get_args(); unset($args[0]); - return vsprintf ($translate, $args); + return vsprintf($translate, $args); } /** * Retourne la langue utilisée actuellement * @return la langue */ - public static function language () { + public static function language() { return self::$language; } } + +function _t($key) { + $args = func_get_args(); + unset($args[0]); + array_unshift($args, $key); + + return call_user_func_array('Minz_Translate::t', $args); +} diff --git a/lib/minz/Url.php b/lib/Minz/Url.php index c1c3e9a0f..e9f9a69ba 100755..100644 --- a/lib/minz/Url.php +++ b/lib/Minz/Url.php @@ -3,10 +3,9 @@ /** * La classe Url permet de gérer les URL à travers MINZ */ -class Url { +class Minz_Url { /** - * Affiche une Url formatée selon que l'on utilise l'url_rewriting ou non - * si oui, on cherche dans la table de routage la correspondance pour formater + * Affiche une Url formatée * @param $url l'url à formater définie comme un tableau : * $url['c'] = controller * $url['a'] = action @@ -16,50 +15,46 @@ class Url { * @param $encodage pour indiquer comment encoder les & (& ou & pour html) * @return l'url formatée */ - public static function display ($url = array (), $encodage = 'html') { - $url = self::checkUrl ($url); - + public static function display ($url = array (), $encodage = 'html', $absolute = false) { + $isArray = is_array($url); + + if ($isArray) { + $url = self::checkUrl ($url); + } + $url_string = ''; - - if (is_array ($url) && isset ($url['protocol'])) { - $protocol = $url['protocol']; - } else { - if(isset($_SERVER['HTTPS']) && $_SERVER["HTTPS"] == 'on') { - $protocol = 'https'; + + if ($absolute) { + if ($isArray && isset ($url['protocol'])) { + $protocol = $url['protocol']; + } elseif (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') { + $protocol = 'https:'; } else { - $protocol = 'http'; + $protocol = 'http:'; } + $url_string = $protocol . '//' . Minz_Request::getDomainName () . Minz_Request::getBaseUrl (); + } else { + $url_string = $isArray ? '.' : PUBLIC_RELATIVE; } - $url_string .= $protocol . '://'; - - $url_string .= Request::getDomainName (); - - $url_string .= Request::getBaseUrl (); - - if (is_array ($url)) { - $router = new Router (); - - if (Configuration::useUrlRewriting ()) { - $url_string .= $router->printUriRewrited ($url); - } else { - $url_string .= self::printUri ($url, $encodage); - } + + if ($isArray) { + $url_string .= self::printUri ($url, $encodage); } else { $url_string .= $url; } - + return $url_string; } /** - * Construit l'URI d'une URL sans url rewriting + * Construit l'URI d'une URL * @param l'url sous forme de tableau * @param $encodage pour indiquer comment encoder les & (& ou & pour html) * @return l'uri sous la forme ?key=value&key2=value2 */ private static function printUri ($url, $encodage) { $uri = ''; - $separator = '/?'; + $separator = '?'; if($encodage == 'html') { $and = '&'; @@ -68,13 +63,13 @@ class Url { } if (isset ($url['c']) - && $url['c'] != Request::defaultControllerName ()) { + && $url['c'] != Minz_Request::defaultControllerName ()) { $uri .= $separator . 'c=' . $url['c']; $separator = $and; } if (isset ($url['a']) - && $url['a'] != Request::defaultActionName ()) { + && $url['a'] != Minz_Request::defaultActionName ()) { $uri .= $separator . 'a=' . $url['a']; $separator = $and; } @@ -99,10 +94,10 @@ class Url { if (is_array ($url)) { if (!isset ($url['c'])) { - $url_checked['c'] = Request::defaultControllerName (); + $url_checked['c'] = Minz_Request::defaultControllerName (); } if (!isset ($url['a'])) { - $url_checked['a'] = Request::defaultActionName (); + $url_checked['a'] = Minz_Request::defaultActionName (); } if (!isset ($url['params'])) { $url_checked['params'] = array (); @@ -126,5 +121,5 @@ function _url ($controller, $action) { $params[$args[$i]] = $args[$i + 1]; } - return Url::display (array ('c' => $controller, 'a' => $action, 'params' => $params)); + return Minz_Url::display (array ('c' => $controller, 'a' => $action, 'params' => $params)); } diff --git a/lib/minz/View.php b/lib/Minz/View.php index 1bfa0a61f..a0dec1824 100755..100644 --- a/lib/minz/View.php +++ b/lib/Minz/View.php @@ -7,13 +7,13 @@ /** * La classe View représente la vue de l'application */ -class View { +class Minz_View { const VIEWS_PATH_NAME = '/views'; const LAYOUT_PATH_NAME = '/layout'; const LAYOUT_FILENAME = '/layout.phtml'; private $view_filename = ''; - private $use_layout = false; + private $use_layout = null; private static $title = ''; private static $styles = array (); @@ -26,24 +26,28 @@ class View { * Détermine si on utilise un layout ou non */ public function __construct () { + $this->change_view(Minz_Request::controllerName(), + Minz_Request::actionName()); + self::$title = Minz_Configuration::title (); + } + + /** + * Change le fichier de vue en fonction d'un controller / action + */ + public function change_view($controller_name, $action_name) { $this->view_filename = APP_PATH . self::VIEWS_PATH_NAME . '/' - . Request::controllerName () . '/' - . Request::actionName () . '.phtml'; - - if (file_exists (APP_PATH - . self::LAYOUT_PATH_NAME - . self::LAYOUT_FILENAME)) { - $this->use_layout = true; - } - - self::$title = Configuration::title (); + . $controller_name . '/' + . $action_name . '.phtml'; } /** * Construit la vue */ public function build () { + if ($this->use_layout === null) { //TODO: avoid file_exists and require views to be explicit + $this->use_layout = file_exists (APP_PATH . self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME); + } if ($this->use_layout) { $this->buildLayout (); } else { @@ -66,10 +70,8 @@ class View { * Affiche la Vue en elle-même */ public function render () { - if (file_exists ($this->view_filename)) { - include ($this->view_filename); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($this->view_filename)) === false) { + Minz_Log::record ('File not found: `' . $this->view_filename . '`', Minz_Log::NOTICE); } @@ -84,10 +86,8 @@ class View { . self::LAYOUT_PATH_NAME . '/' . $part . '.phtml'; - if (file_exists ($fic_partial)) { - include ($fic_partial); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($fic_partial)) === false) { + Minz_Log::record ('File not found: `' . $fic_partial . '`', Minz_Log::WARNING); } @@ -102,16 +102,24 @@ class View { . '/views/helpers/' . $helper . '.phtml'; - if (file_exists ($fic_helper)) { - include ($fic_helper); - } else { - Minz_Log::record ('File doesn\'t exist : `' + if ((include($fic_helper)) === false) {; + Minz_Log::record ('File not found: `' . $fic_helper . '`', Minz_Log::WARNING); } } /** + * Retourne renderHelper() dans une chaîne + * @param $helper l'élément à traîter + */ + public function helperToString($helper) { + ob_start(); + $this->renderHelper($helper); + return ob_get_clean(); + } + + /** * Permet de choisir si on souhaite utiliser le layout * @param $use true si on souhaite utiliser le layout, false sinon */ @@ -150,9 +158,9 @@ class View { $styles .= '<!--[if ' . $cond . ']>'; } - $styles .= '<link rel="stylesheet" type="text/css"'; - $styles .= ' media="' . $style['media'] . '"'; - $styles .= ' href="' . $style['url'] . '" />'; + $styles .= '<link rel="stylesheet" ' . + ($style['media'] === 'all' ? '' : 'media="' . $style['media'] . '" ') . + 'href="' . $style['url'] . '" />'; if ($cond) { $styles .= '<![endif]-->'; @@ -190,9 +198,14 @@ class View { $scripts .= '<!--[if ' . $cond . ']>'; } - $scripts .= '<script type="text/javascript"'; - $scripts .= ' src="' . $script['url'] . '">'; - $scripts .= '</script>'; + $scripts .= '<script src="' . $script['url'] . '"'; + if ($script['defer']) { + $scripts .= ' defer="defer"'; + } + if ($script['async']) { + $scripts .= ' async="async"'; + } + $scripts .= '></script>'; if ($cond) { $scripts .= '<![endif]-->'; @@ -203,16 +216,20 @@ class View { return $scripts; } - public static function prependScript ($url, $cond = false) { + public static function prependScript ($url, $cond = false, $defer = true, $async = true) { array_unshift(self::$scripts, array ( 'url' => $url, - 'cond' => $cond + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, )); } - public static function appendScript ($url, $cond = false) { + public static function appendScript ($url, $cond = false, $defer = true, $async = true) { self::$scripts[] = array ( 'url' => $url, - 'cond' => $cond + 'cond' => $cond, + 'defer' => $defer, + 'async' => $async, ); } @@ -223,7 +240,7 @@ class View { self::$params[$key] = $value; } public function attributeParams () { - foreach (View::$params as $key => $value) { + foreach (Minz_View::$params as $key => $value) { $this->$key = $value; } } diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php index b33c635f1..06c100f59 100644 --- a/lib/SimplePie/SimplePie.php +++ b/lib/SimplePie/SimplePie.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev-FreshRSS * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -50,7 +50,7 @@ define('SIMPLEPIE_NAME', 'SimplePie'); /** * SimplePie Version */ -define('SIMPLEPIE_VERSION', '1.3.1'); +define('SIMPLEPIE_VERSION', '1.4-dev-FreshRSS'); /** * SimplePie Build @@ -446,6 +446,13 @@ class SimplePie public $feed_url; /** + * @var string Original feed URL, or new feed URL iff HTTP 301 Moved Permanently + * @see SimplePie::subscribe_url() + * @access private + */ + public $permanent_url = null; //FreshRSS + + /** * @var object Instance of SimplePie_File to use as a feed * @see SimplePie::set_file() * @access private @@ -602,6 +609,13 @@ class SimplePie public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); /** + * @var array Stores the default attributes to add to different tags by add_attributes(). + * @see SimplePie::add_attributes() + * @access private + */ + public $add_attributes = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none')); //FreshRSS + + /** * @var array Stores the default tags to be stripped by strip_htmltags(). * @see SimplePie::strip_htmltags() * @access private @@ -637,7 +651,7 @@ class SimplePie if (func_num_args() > 0) { $level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING; - trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level); + trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', $level); $args = func_get_args(); switch (count($args)) { @@ -728,6 +742,7 @@ class SimplePie else { $this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1)); + $this->permanent_url = $this->feed_url; //FreshRSS } } @@ -742,6 +757,7 @@ class SimplePie if ($file instanceof SimplePie_File) { $this->feed_url = $file->url; + $this->permanent_url = $this->feed_url; //FreshRSS $this->file =& $file; return true; } @@ -1073,6 +1089,7 @@ class SimplePie $this->strip_comments(false); $this->strip_htmltags(false); $this->strip_attributes(false); + $this->add_attributes(false); $this->set_image_handler(false); } } @@ -1119,6 +1136,15 @@ class SimplePie $this->sanitize->strip_attributes($attribs); } + public function add_attributes($attribs = '') + { + if ($attribs === '') + { + $attribs = $this->add_attributes; + } + $this->sanitize->add_attributes($attribs); + } + /** * Set the output encoding * @@ -1196,13 +1222,27 @@ class SimplePie } /** + * Enable throwing exceptions + * + * @param boolean $enable Should we throw exceptions, or use the old-style error property? + */ + public function enable_exceptions($enable = true) + { + $this->enable_exceptions = $enable; + } + + function cleanMd5($rss) { //FreshRSS + return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss)); + } + + /** * Initialize the feed object * * This is what makes everything happen. Period. This is where all of the * configuration options get processed, feeds are fetched, cached, and * parsed, and all of that other good stuff. * - * @return boolean True if successful, false otherwise + * @return positive integer with modification time if using cache, boolean true if otherwise successful, false otherwise */ public function init() { @@ -1281,13 +1321,17 @@ class SimplePie // Fetch the data via SimplePie_File into $this->raw_data if (($fetched = $this->fetch_data($cache)) === true) { - return true; + return $this->data['mtime']; //FreshRSS } elseif ($fetched === false) { return false; } list($headers, $sniffed) = $fetched; + + if (isset($this->data['md5'])) { //FreshRSS + $md5 = $this->data['md5']; + } } // Set up array of possible encodings @@ -1296,7 +1340,7 @@ class SimplePie // First check to see if input has been overridden. if ($this->input_encoding !== false) { - $encodings[] = $this->input_encoding; + $encodings[] = strtoupper($this->input_encoding); } $application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity'); @@ -1311,14 +1355,20 @@ class SimplePie { $encodings[] = strtoupper($charset[1]); } - $encodings = array_merge($encodings, $this->registry->call('Misc', 'xml_encoding', array($this->raw_data, &$this->registry))); - $encodings[] = 'UTF-8'; + else + { + $encodings[] = ''; //FreshRSS: Let the DOM parser decide first + } } elseif (in_array($sniffed, $text_types) || substr($sniffed, 0, 5) === 'text/' && substr($sniffed, -4) === '+xml') { if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset)) { - $encodings[] = $charset[1]; + $encodings[] = strtoupper($charset[1]); + } + else + { + $encodings[] = ''; //FreshRSS: Let the DOM parser decide first } $encodings[] = 'US-ASCII'; } @@ -1341,13 +1391,14 @@ class SimplePie foreach ($encodings as $encoding) { // Change the encoding to UTF-8 (as we always use UTF-8 internally) - if ($utf8_data = $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8'))) + if ($utf8_data = (empty($encoding) || $encoding === 'UTF-8') ? $this->raw_data : //FreshRSS + $this->registry->call('Misc', 'change_encoding', array($this->raw_data, $encoding, 'UTF-8'))) { // Create new parser $parser = $this->registry->create('Parser'); // If it's parsed fine - if ($parser->parse($utf8_data, 'UTF-8')) + if ($parser->parse($utf8_data, empty($encoding) ? '' : 'UTF-8')) //FreshRSS { $this->data = $parser->get_data(); if (!($this->get_type() & ~SIMPLEPIE_TYPE_NONE)) @@ -1362,6 +1413,8 @@ class SimplePie $this->data['headers'] = $headers; } $this->data['build'] = SIMPLEPIE_BUILD; + $this->data['mtime'] = time(); //FreshRSS + $this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5; //FreshRSS // Cache the file if caching is enabled if ($cache && !$cache->save($this)) @@ -1437,7 +1490,7 @@ class SimplePie elseif ($cache->mtime() + $this->cache_duration < time()) { // If we have last-modified and/or etag set - if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) + //if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) //FreshRSS removed { $headers = array( 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1', @@ -1451,7 +1504,7 @@ class SimplePie $headers['if-none-match'] = $this->data['headers']['etag']; } - $file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen)); + $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen)); //FreshRSS if ($file->success) { @@ -1463,7 +1516,20 @@ class SimplePie } else { - unset($file); + $this->error = $file->error; //FreshRSS + return !empty($this->data); //FreshRSS + //unset($file); //FreshRSS removed + } + } + { //FreshRSS + $md5 = $this->cleanMd5($file->body); + if ($this->data['md5'] === $md5) { + syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . $this->feed_url); + $cache->touch(); + return true; //Content unchanged even though server did not send a 304 + } else { + syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . $this->feed_url); + $this->data['md5'] = $md5; } } } @@ -1531,6 +1597,8 @@ class SimplePie if ($cache) { $this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD); + $this->data['mtime'] = time(); //FreshRSS + $this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5; //FreshRSS if (!$cache->save($this)) { trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING); @@ -1543,7 +1611,7 @@ class SimplePie } $this->raw_data = $file->body; - + $this->permanent_url = $file->permanent_url; //FreshRSS $headers = $file->headers; $sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file)); $sniffed = $sniffer->get_type(); @@ -1729,26 +1797,39 @@ class SimplePie /** * Get the URL for the feed + * + * When the 'permanent' mode is enabled, returns the original feed URL, + * except in the case of an `HTTP 301 Moved Permanently` status response, + * in which case the location of the first redirection is returned. * - * May or may not be different from the URL passed to {@see set_feed_url()}, + * When the 'permanent' mode is disabled (default), + * may or may not be different from the URL passed to {@see set_feed_url()}, * depending on whether auto-discovery was used. * * @since Preview Release (previously called `get_feed_url()` since SimplePie 0.8.) - * @todo If we have a perm redirect we should return the new URL - * @todo When we make the above change, let's support <itunes:new-feed-url> as well + * @todo Support <itunes:new-feed-url> * @todo Also, |atom:link|@rel=self + * @param bool $permanent Permanent mode to return only the original URL or the first redirection + * iff it is a 301 redirection * @return string|null */ - public function subscribe_url() + public function subscribe_url($permanent = false) { - if ($this->feed_url !== null) + if ($permanent) //FreshRSS { - return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); + if ($this->permanent_url !== null) + { + return $this->sanitize($this->permanent_url, SIMPLEPIE_CONSTRUCT_IRI); + } } else { - return null; + if ($this->feed_url !== null) + { + return $this->sanitize($this->feed_url, SIMPLEPIE_CONSTRUCT_IRI); + } } + return null; } /** @@ -1963,7 +2044,21 @@ class SimplePie */ public function sanitize($data, $type, $base = '') { - return $this->sanitize->sanitize($data, $type, $base); + try + { + return $this->sanitize->sanitize($data, $type, $base); + } + catch (SimplePie_Exception $e) + { + if (!$this->enable_exceptions) + { + $this->error = $e->getMessage(); + $this->registry->call('Misc', 'error', array($this->error, E_USER_WARNING, $e->getFile(), $e->getLine())); + return ''; + } + + throw $e; + } } /** diff --git a/lib/SimplePie/SimplePie/Author.php b/lib/SimplePie/SimplePie/Author.php index bbf3812ff..19563c5cc 100644 --- a/lib/SimplePie/SimplePie/Author.php +++ b/lib/SimplePie/SimplePie/Author.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache.php b/lib/SimplePie/SimplePie/Cache.php index 75586d749..86b618693 100644 --- a/lib/SimplePie/SimplePie/Cache.php +++ b/lib/SimplePie/SimplePie/Cache.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/Base.php b/lib/SimplePie/SimplePie/Cache/Base.php index 937e34631..d3f353961 100644 --- a/lib/SimplePie/SimplePie/Cache/Base.php +++ b/lib/SimplePie/SimplePie/Cache/Base.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/DB.php b/lib/SimplePie/SimplePie/Cache/DB.php index ac509ae08..d728a9a6d 100644 --- a/lib/SimplePie/SimplePie/Cache/DB.php +++ b/lib/SimplePie/SimplePie/Cache/DB.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/File.php b/lib/SimplePie/SimplePie/Cache/File.php index 5797b3aed..3b163545b 100644 --- a/lib/SimplePie/SimplePie/Cache/File.php +++ b/lib/SimplePie/SimplePie/Cache/File.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Cache/Memcache.php b/lib/SimplePie/SimplePie/Cache/Memcache.php index fd4478060..23b1c9367 100644 --- a/lib/SimplePie/SimplePie/Cache/Memcache.php +++ b/lib/SimplePie/SimplePie/Cache/Memcache.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -95,10 +95,8 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base 'prefix' => 'simplepie_', ), ); - $parsed = SimplePie_Cache::parse_URL($location); - $this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host']; - $this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port']; - $this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']); + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + $this->name = $this->options['extras']['prefix'] . md5("$name:$type"); $this->cache = new Memcache(); @@ -147,7 +145,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base if ($data !== false) { - // essentially ignore the mtime because Memcache expires on it's own + // essentially ignore the mtime because Memcache expires on its own return time(); } @@ -165,7 +163,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base if ($data !== false) { - return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration); + return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']); } return false; diff --git a/lib/SimplePie/SimplePie/Cache/MySQL.php b/lib/SimplePie/SimplePie/Cache/MySQL.php index d53ebc117..511ef6d3a 100644 --- a/lib/SimplePie/SimplePie/Cache/MySQL.php +++ b/lib/SimplePie/SimplePie/Cache/MySQL.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -96,7 +96,8 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB 'prefix' => '', ), ); - $this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); + + $this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location)); // Path is prefixed with a "/" $this->options['dbname'] = substr($this->options['path'], 1); @@ -136,7 +137,7 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB if (!in_array($this->options['extras']['prefix'] . 'items', $db)) { - $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); + $query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))'); if ($query === false) { $this->mysql = null; diff --git a/lib/SimplePie/SimplePie/Caption.php b/lib/SimplePie/SimplePie/Caption.php index 52922c5d9..a77b02ef1 100644 --- a/lib/SimplePie/SimplePie/Caption.php +++ b/lib/SimplePie/SimplePie/Caption.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Category.php b/lib/SimplePie/SimplePie/Category.php index ad0407b4e..c6a273989 100644 --- a/lib/SimplePie/SimplePie/Category.php +++ b/lib/SimplePie/SimplePie/Category.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Content/Type/Sniffer.php b/lib/SimplePie/SimplePie/Content/Type/Sniffer.php index 20d053dca..a32f47f59 100644 --- a/lib/SimplePie/SimplePie/Content/Type/Sniffer.php +++ b/lib/SimplePie/SimplePie/Content/Type/Sniffer.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Copyright.php b/lib/SimplePie/SimplePie/Copyright.php index 57c535a64..09f22f8df 100644 --- a/lib/SimplePie/SimplePie/Copyright.php +++ b/lib/SimplePie/SimplePie/Copyright.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Core.php b/lib/SimplePie/SimplePie/Core.php index 46d996628..7cf34876f 100644 --- a/lib/SimplePie/SimplePie/Core.php +++ b/lib/SimplePie/SimplePie/Core.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Credit.php b/lib/SimplePie/SimplePie/Credit.php index d3a3442ad..50aef1c68 100644 --- a/lib/SimplePie/SimplePie/Credit.php +++ b/lib/SimplePie/SimplePie/Credit.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php index 069e8d8e5..cde06c884 100644 --- a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php +++ b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Enclosure.php b/lib/SimplePie/SimplePie/Enclosure.php index 55674379c..fa0217800 100644 --- a/lib/SimplePie/SimplePie/Enclosure.php +++ b/lib/SimplePie/SimplePie/Enclosure.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -942,7 +942,7 @@ class SimplePie_Enclosure * - `height` (integer): The height of the embedded media. Accepts any * numeric pixel value (such as `360`) or `auto`. Defaults to `auto`, * and it is recommended that you use this default. - * - `loop` (boolean): Do you want the media to loop when its done? + * - `loop` (boolean): Do you want the media to loop when it's done? * Defaults to `false`. * - `mediaplayer` (string): The location of the included * `mediaplayer.swf` file. This allows for the playback of Flash Video diff --git a/lib/SimplePie/SimplePie/File.php b/lib/SimplePie/SimplePie/File.php index 063ad955e..b1bbe4420 100644 --- a/lib/SimplePie/SimplePie/File.php +++ b/lib/SimplePie/SimplePie/File.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -64,6 +64,7 @@ class SimplePie_File var $redirects = 0; var $error; var $method = SIMPLEPIE_FILE_SOURCE_NONE; + var $permanent_url; //FreshRSS public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false) { @@ -74,9 +75,11 @@ class SimplePie_File $url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']); } $this->url = $url; + $this->permanent_url = $url; //FreshRSS $this->useragent = $useragent; if (preg_match('/^http(s)?:\/\//i', $url)) { + syslog(LOG_INFO, 'SimplePie GET ' . $url); //FreshRSS if ($useragent === null) { $useragent = ini_get('user_agent'); @@ -107,7 +110,7 @@ class SimplePie_File curl_setopt($fp, CURLOPT_REFERER, $url); curl_setopt($fp, CURLOPT_USERAGENT, $useragent); curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2); - curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); //FreshRSS if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>=')) { curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1); @@ -141,7 +144,10 @@ class SimplePie_File { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $previousStatusCode = $this->status_code; + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS + return; } } } @@ -223,7 +229,10 @@ class SimplePie_File { $this->redirects++; $location = SimplePie_Misc::absolutize_url($this->headers['location'], $url); - return $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $previousStatusCode = $this->status_code; + $this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen); + $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS + return; } if (isset($this->headers['content-encoding'])) { @@ -283,7 +292,7 @@ class SimplePie_File else { $this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS; - if (!$this->body = file_get_contents($url)) + if (empty($url) || !($this->body = file_get_contents($url))) { $this->error = 'file_get_contents could not read the file'; $this->success = false; diff --git a/lib/SimplePie/SimplePie/HTTP/Parser.php b/lib/SimplePie/SimplePie/HTTP/Parser.php index bff2222b2..2fc3a6779 100644 --- a/lib/SimplePie/SimplePie/HTTP/Parser.php +++ b/lib/SimplePie/SimplePie/HTTP/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/IRI.php b/lib/SimplePie/SimplePie/IRI.php index d3198c04f..ed0574701 100644 --- a/lib/SimplePie/SimplePie/IRI.php +++ b/lib/SimplePie/SimplePie/IRI.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Item.php b/lib/SimplePie/SimplePie/Item.php index a77574b37..7bd96c15f 100644 --- a/lib/SimplePie/SimplePie/Item.php +++ b/lib/SimplePie/SimplePie/Item.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -821,7 +821,7 @@ class SimplePie_Item if (!empty($this->data['updated']['raw'])) { $parser = $this->registry->call('Parse_Date', 'get'); - $this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']); + $this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']); } else { @@ -1080,7 +1080,7 @@ class SimplePie_Item * * @since Beta 2 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4). - * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists). + * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists). * @return array|null List of SimplePie_Enclosure items */ public function get_enclosures() diff --git a/lib/SimplePie/SimplePie/Locator.php b/lib/SimplePie/SimplePie/Locator.php index 57e910c22..90ee7a302 100644 --- a/lib/SimplePie/SimplePie/Locator.php +++ b/lib/SimplePie/SimplePie/Locator.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -277,7 +277,7 @@ class SimplePie_Locator $parsed = $this->registry->call('Misc', 'parse_url', array($href)); if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme'])) { - if ($this->base_location < $link->getLineNo()) + if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo()) { $href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base)); } diff --git a/lib/SimplePie/SimplePie/Misc.php b/lib/SimplePie/SimplePie/Misc.php index 621f2c062..5a263a2e5 100644 --- a/lib/SimplePie/SimplePie/Misc.php +++ b/lib/SimplePie/SimplePie/Misc.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -79,6 +79,10 @@ class SimplePie_Misc public static function absolutize_url($relative, $base) { + if (substr($relative, 0, 2) === '//') //FreshRSS: disable absolutize_url for "//www.example.net" which will pick HTTP or HTTPS automatically + { + return $relative; + } $iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative); if ($iri === false) { @@ -124,7 +128,7 @@ class SimplePie_Misc { $attribs[$j][2] = $attribs[$j][1]; } - $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); + $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); //FreshRSS } } } @@ -138,7 +142,7 @@ class SimplePie_Misc foreach ($element['attribs'] as $key => $value) { $key = strtolower($key); - $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; + $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; //FreshRSS } if ($element['self_closing']) { @@ -224,6 +228,23 @@ class SimplePie_Misc } } + public static function array_merge_recursive($array1, $array2) + { + foreach ($array2 as $key => $value) + { + if (is_array($value)) + { + $array1[$key] = SimplePie_Misc::array_merge_recursive($array1[$key], $value); + } + else + { + $array1[$key] = $value; + } + } + + return $array1; + } + public static function parse_url($url) { $iri = new SimplePie_IRI($url); @@ -2157,36 +2178,12 @@ function embed_wmedia(width, height, link) { /** * Get the SimplePie build timestamp * - * Uses the git index if it exists, otherwise uses the modification time - * of the newest file. + * Return SimplePie.php modification time. */ public static function get_build() { - $root = dirname(dirname(__FILE__)); - if (file_exists($root . '/.git/index')) - { - return filemtime($root . '/.git/index'); - } - elseif (file_exists($root . '/SimplePie')) - { - $time = 0; - foreach (glob($root . '/SimplePie/*.php') as $file) - { - if (($mtime = filemtime($file)) > $time) - { - $time = $mtime; - } - } - return $time; - } - elseif (file_exists(dirname(__FILE__) . '/Core.php')) - { - return filemtime(dirname(__FILE__) . '/Core.php'); - } - else - { - return filemtime(__FILE__); - } + $mtime = @filemtime(dirname(dirname(__FILE__)) . '/SimplePie.php'); //FreshRSS + return $mtime ? $mtime : filemtime(__FILE__); } /** diff --git a/lib/SimplePie/SimplePie/Net/IPv6.php b/lib/SimplePie/SimplePie/Net/IPv6.php index da80d8aca..2ff1afc90 100644 --- a/lib/SimplePie/SimplePie/Net/IPv6.php +++ b/lib/SimplePie/SimplePie/Net/IPv6.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Parse/Date.php b/lib/SimplePie/SimplePie/Parse/Date.php index d51f500d3..ef800f125 100644 --- a/lib/SimplePie/SimplePie/Parse/Date.php +++ b/lib/SimplePie/SimplePie/Parse/Date.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Parser.php b/lib/SimplePie/SimplePie/Parser.php index d698552ca..7fb7bd9be 100644 --- a/lib/SimplePie/SimplePie/Parser.php +++ b/lib/SimplePie/SimplePie/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -77,55 +77,86 @@ class SimplePie_Parser public function parse(&$data, $encoding) { - // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character - if (strtoupper($encoding) === 'US-ASCII') - { - $this->encoding = 'UTF-8'; - } - else - { - $this->encoding = $encoding; - } + $xmlEncoding = ''; - // Strip BOM: - // UTF-32 Big Endian BOM - if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") - { - $data = substr($data, 4); - } - // UTF-32 Little Endian BOM - elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") - { - $data = substr($data, 4); - } - // UTF-16 Big Endian BOM - elseif (substr($data, 0, 2) === "\xFE\xFF") - { - $data = substr($data, 2); - } - // UTF-16 Little Endian BOM - elseif (substr($data, 0, 2) === "\xFF\xFE") + if (!empty($encoding)) { - $data = substr($data, 2); - } - // UTF-8 BOM - elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") - { - $data = substr($data, 3); + // Use UTF-8 if we get passed US-ASCII, as every US-ASCII character is a UTF-8 character + if (strtoupper($encoding) === 'US-ASCII') + { + $this->encoding = 'UTF-8'; + } + else + { + $this->encoding = $encoding; + } + + // Strip BOM: + // UTF-32 Big Endian BOM + if (substr($data, 0, 4) === "\x00\x00\xFE\xFF") + { + $data = substr($data, 4); + } + // UTF-32 Little Endian BOM + elseif (substr($data, 0, 4) === "\xFF\xFE\x00\x00") + { + $data = substr($data, 4); + } + // UTF-16 Big Endian BOM + elseif (substr($data, 0, 2) === "\xFE\xFF") + { + $data = substr($data, 2); + } + // UTF-16 Little Endian BOM + elseif (substr($data, 0, 2) === "\xFF\xFE") + { + $data = substr($data, 2); + } + // UTF-8 BOM + elseif (substr($data, 0, 3) === "\xEF\xBB\xBF") + { + $data = substr($data, 3); + } + + if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) + { + $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); + if ($declaration->parse()) + { + $xmlEncoding = strtoupper($declaration->encoding); //FreshRSS + $data = substr($data, $pos + 2); + $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + } + else + { + $this->error_string = 'SimplePie bug! Please report this!'; + return false; + } + } } - if (substr($data, 0, 5) === '<?xml' && strspn(substr($data, 5, 1), "\x09\x0A\x0D\x20") && ($pos = strpos($data, '?>')) !== false) + if ($xmlEncoding === '' || $xmlEncoding === 'UTF-8') //FreshRSS: case of no explicit HTTP encoding, and lax UTF-8 { - $declaration = $this->registry->create('XML_Declaration_Parser', array(substr($data, 5, $pos - 5))); - if ($declaration->parse()) + try { - $data = substr($data, $pos + 2); - $data = '<?xml version="' . $declaration->version . '" encoding="' . $encoding . '" standalone="' . (($declaration->standalone) ? 'yes' : 'no') . '"?>' . $data; + $dom = new DOMDocument(); + $dom->recover = true; + $dom->strictErrorChecking = false; + @$dom->loadXML($data); + $this->encoding = $encoding = $dom->encoding = 'UTF-8'; + $data2 = $dom->saveXML(); + if (function_exists('mb_convert_encoding')) + { + $data2 = mb_convert_encoding($data2, 'UTF-8', 'UTF-8'); + } + if (strlen($data2) > (strlen($data) / 2.0)) + { + $data = $data2; + } + unset($data2); } - else + catch (Exception $e) { - $this->error_string = 'SimplePie bug! Please report this!'; - return false; } } diff --git a/lib/SimplePie/SimplePie/Rating.php b/lib/SimplePie/SimplePie/Rating.php index 8689e5dfb..b5fe80516 100644 --- a/lib/SimplePie/SimplePie/Rating.php +++ b/lib/SimplePie/SimplePie/Rating.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Registry.php b/lib/SimplePie/SimplePie/Registry.php index 1072cdebb..bd9c1f535 100755 --- a/lib/SimplePie/SimplePie/Registry.php +++ b/lib/SimplePie/SimplePie/Registry.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Restriction.php b/lib/SimplePie/SimplePie/Restriction.php index 4ba371bfb..a1d59916d 100644 --- a/lib/SimplePie/SimplePie/Restriction.php +++ b/lib/SimplePie/SimplePie/Restriction.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/Sanitize.php b/lib/SimplePie/SimplePie/Sanitize.php index 83a274ced..168a5e2e8 100644 --- a/lib/SimplePie/SimplePie/Sanitize.php +++ b/lib/SimplePie/SimplePie/Sanitize.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon @@ -62,6 +62,7 @@ class SimplePie_Sanitize var $strip_htmltags = array('base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', 'iframe', 'input', 'marquee', 'meta', 'noscript', 'object', 'param', 'script', 'style'); var $encode_instead_of_strip = false; var $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc'); + var $add_attributes = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none')); //FreshRSS var $strip_comments = false; var $output_encoding = 'UTF-8'; var $enable_cache = true; @@ -179,6 +180,25 @@ class SimplePie_Sanitize } } + public function add_attributes($attribs = array('audio' => array('preload' => 'none'), 'iframe' => array('sandbox' => 'allow-scripts allow-same-origin'), 'video' => array('preload' => 'none'))) + { + if ($attribs) + { + if (is_array($attribs)) + { + $this->add_attributes = $attribs; + } + else + { + $this->add_attributes = explode(',', $attribs); + } + } + else + { + $this->add_attributes = false; + } + } + public function strip_comments($strip = false) { $this->strip_comments = (bool) $strip; @@ -247,6 +267,10 @@ class SimplePie_Sanitize if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML)) { + if (!class_exists('DOMDocument')) + { + throw new SimplePie_Exception('DOMDocument not found, unable to use sanitizer'); + } $document = new DOMDocument(); $document->encoding = 'UTF-8'; $data = $this->preprocess($data, $type); @@ -255,10 +279,11 @@ class SimplePie_Sanitize $document->loadHTML($data); restore_error_handler(); + $xpath = new DOMXPath($document); //FreshRSS + // Strip comments if ($this->strip_comments) { - $xpath = new DOMXPath($document); $comments = $xpath->query('//comment()'); foreach ($comments as $comment) @@ -274,7 +299,7 @@ class SimplePie_Sanitize { foreach ($this->strip_htmltags as $tag) { - $this->strip_tag($tag, $document, $type); + $this->strip_tag($tag, $document, $xpath, $type); } } @@ -282,7 +307,15 @@ class SimplePie_Sanitize { foreach ($this->strip_attributes as $attrib) { - $this->strip_attr($attrib, $document); + $this->strip_attr($attrib, $xpath); + } + } + + if ($this->add_attributes) + { + foreach ($this->add_attributes as $tag => $valuePairs) + { + $this->add_attr($tag, $valuePairs, $document); } } @@ -310,7 +343,7 @@ class SimplePie_Sanitize } else { - $file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); + $file = $this->registry->create('File', array($img->getAttribute('src'), $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen)); $headers = $file->headers; if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300))) @@ -452,9 +485,8 @@ class SimplePie_Sanitize } } - protected function strip_tag($tag, $document, $type) + protected function strip_tag($tag, $document, $xpath, $type) { - $xpath = new DOMXPath($document); $elements = $xpath->query('body//' . $tag); if ($this->encode_instead_of_strip) { @@ -537,9 +569,8 @@ class SimplePie_Sanitize } } - protected function strip_attr($attrib, $document) + protected function strip_attr($attrib, $xpath) { - $xpath = new DOMXPath($document); $elements = $xpath->query('//*[@' . $attrib . ']'); foreach ($elements as $element) @@ -547,4 +578,16 @@ class SimplePie_Sanitize $element->removeAttribute($attrib); } } + + protected function add_attr($tag, $valuePairs, $document) + { + $elements = $document->getElementsByTagName($tag); + foreach ($elements as $element) + { + foreach ($valuePairs as $attrib => $value) + { + $element->setAttribute($attrib, $value); + } + } + } } diff --git a/lib/SimplePie/SimplePie/Source.php b/lib/SimplePie/SimplePie/Source.php index 51d8e6c25..2613798fd 100644 --- a/lib/SimplePie/SimplePie/Source.php +++ b/lib/SimplePie/SimplePie/Source.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/XML/Declaration/Parser.php b/lib/SimplePie/SimplePie/XML/Declaration/Parser.php index aec19f10a..589e452a2 100644 --- a/lib/SimplePie/SimplePie/XML/Declaration/Parser.php +++ b/lib/SimplePie/SimplePie/XML/Declaration/Parser.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie/SimplePie/gzdecode.php b/lib/SimplePie/SimplePie/gzdecode.php index 52e024ea9..6e65f0811 100644 --- a/lib/SimplePie/SimplePie/gzdecode.php +++ b/lib/SimplePie/SimplePie/gzdecode.php @@ -33,7 +33,7 @@ * POSSIBILITY OF SUCH DAMAGE. * * @package SimplePie - * @version 1.3.1 + * @version 1.4-dev * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue * @author Ryan Parman * @author Geoffrey Sneddon diff --git a/lib/SimplePie_autoloader.php b/lib/SimplePie_autoloader.php deleted file mode 100644 index 3f67635b0..000000000 --- a/lib/SimplePie_autoloader.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * SimplePie - * - * A PHP-Based RSS and Atom Feed Framework. - * Takes the hard work out of managing a complete RSS/Atom solution. - * - * Copyright (c) 2004-2009, Ryan Parman, Geoffrey Sneddon, Ryan McCue, and contributors - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * * Neither the name of the SimplePie Team nor the names of its contributors may be used - * to endorse or promote products derived from this software without specific prior - * written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS - * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY - * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS - * AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR - * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * @package SimplePie - * @version 1.3.1 - * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue - * @author Ryan Parman - * @author Geoffrey Sneddon - * @author Ryan McCue - * @link http://simplepie.org/ SimplePie - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - */ - - -// autoloader -spl_autoload_register(array(new SimplePie_Autoloader(), 'autoload')); - -if (!class_exists('SimplePie')) -{ - trigger_error('Autoloader not registered properly', E_USER_ERROR); -} - -/** - * Autoloader class - * - * @package SimplePie - * @subpackage API - */ -class SimplePie_Autoloader -{ - /** - * Constructor - */ - public function __construct() - { - $this->path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'SimplePie'; - } - - /** - * Autoloader - * - * @param string $class The name of the class to attempt to load. - */ - public function autoload($class) - { - // Only load the class if it starts with "SimplePie" - if (strpos($class, 'SimplePie') !== 0) - { - return; - } - - $filename = $this->path . DIRECTORY_SEPARATOR . str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; - include $filename; - } -} diff --git a/lib/http-conditional.php b/lib/http-conditional.php new file mode 100644 index 000000000..59fbef41f --- /dev/null +++ b/lib/http-conditional.php @@ -0,0 +1,212 @@ +<?php +/* + Enable support for HTTP/1.x conditional requests in PHP. + Goal: Optimisation + - If the client sends a HEAD request, avoid transferring data and return the correct headers. + - If the client already has the same version in its cache, avoid transferring data again (304 Not Modified). + - Possibility to control cache for client and proxies (public or private policy, life time). + - When $feedMode is set to true, in the case of a RSS/ATOM feed, + it puts a timestamp in the global variable $clientCacheDate to allow the sending of only the articles newer than the client's cache. + - When $compression is set to true, compress the data before sending it to the client and persitent connections are allowed. + - When $session is set to true, automatically checks if $_SESSION has been modified during the last generation the document. + + Interface: + - function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false) + [Required] $UnixTimeStamp: Date of the last modification of the data to send to the client (Unix Timestamp format). + [Implied] $cacheSeconds=0: Lifetime in seconds of the document. If $cacheSeconds<0, cache is disabled. If $cacheSeconds==0, the document will be revalidated each time it is accessed. If $cacheSeconds>0, the document will be cashed and not revalidated against the server for this delay. + [Implied] $cachePrivacy=0: 0=private, 1=normal (public), 2=forced public. When public, it allows a cashed document ($cacheSeconds>0) to be shared by several users. + [Implied] $feedMode=false: Special RSS/ATOM feeds. When true, it sets $cachePrivacy to 0 (private), does not use the modification time of the script itself, and puts the date of the client's cache (or a old date from 1980) in the global variable $clientCacheDate. + [implied] $compression=false: Enable the compression and allows persistant connections (automatic detection of the capacities of the client). + [implied] $session=false: To be turned on when sessions are used. Checks if the data contained in $_SESSION has been modified during the last generation the document. + Returns: True if the connection can be closed (e.g.: the client has already the lastest version), false if the new content has to be send to the client. + + Typical use: + <?php + require_once('http-conditional.php'); + //Date of the last modification of the content (Unix Timestamp format). + //Examples: query the database, or last modification of a static file. + $dateLastModification=...; + if (httpConditional($dateLastModification)) + { + ... //Close database connections, and other cleaning. + exit(); //No need to send anything + } + //Do not send any text to the client before this line. + ... //Rest of the script, just as you would do normally. + ?> + + Version 1.7 beta, 2013-12-02, http://alexandre.alapetite.fr/doc-alex/php-http-304/ + + ------------------------------------------------------------------ + Written by Alexandre Alapetite, http://alexandre.alapetite.fr/cv/ + + Copyright 2004-2013, Licence: Creative Commons "Attribution-ShareAlike 2.0 France" BY-SA (FR), + http://creativecommons.org/licenses/by-sa/2.0/fr/ + http://alexandre.alapetite.fr/divers/apropos/#by-sa + - Attribution. You must give the original author credit + - Share Alike. If you alter, transform, or build upon this work, + you may distribute the resulting work only under a license identical to this one + (Can be included in GPL/LGPL projects) + - The French law is authoritative + - Any of these conditions can be waived if you get permission from Alexandre Alapetite + - Please send to Alexandre Alapetite the modifications you make, + in order to improve this file for the benefit of everybody + + If you want to distribute this code, please do it as a link to: + http://alexandre.alapetite.fr/doc-alex/php-http-304/ +*/ + +//In RSS/ATOM feedMode, contains the date of the clients last update. +$clientCacheDate=0; //Global public variable because PHP4 does not allow conditional arguments by reference +$_sessionMode=false; //Global private variable + +function httpConditional($UnixTimeStamp,$cacheSeconds=0,$cachePrivacy=0,$feedMode=false,$compression=false,$session=false) +{//Credits: http://alexandre.alapetite.fr/doc-alex/php-http-304/ + //RFC2616 HTTP/1.1: http://www.w3.org/Protocols/rfc2616/rfc2616.html + //RFC1945 HTTP/1.0: http://www.w3.org/Protocols/rfc1945/rfc1945.txt + + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + if ((!$feedMode)&&(($modifScript=filemtime($scriptName))>$UnixTimeStamp)) + $UnixTimeStamp=$modifScript; + $UnixTimeStamp=min($UnixTimeStamp,time()); + $is304=true; + $is412=false; + $nbCond=0; + + //rfc2616-sec3.html#sec3.3.1 + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + $dateCacheClient='Thu, 10 Jan 1980 20:30:40 GMT'; + + //rfc2616-sec14.html#sec14.19 //='"0123456789abcdef0123456789abcdef"' + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + if ($session&&isset($_SESSION)) + { + global $_sessionMode; + $_sessionMode=$session; + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + } + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + if ((!$is412)&&isset($_SERVER['HTTP_IF_MATCH'])) + {//rfc2616-sec14.html#sec14.24 + $etagsClient=stripslashes($_SERVER['HTTP_IF_MATCH']); + $is412=(($etagClient!=='*')&&(strpos($etagsClient,$etagServer)===false)); + } + if ($is304&&isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.25 //rfc1945.txt + $nbCond++; + $dateCacheClient=$_SERVER['HTTP_IF_MODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is304=($dateCacheClient==$dateLastModif); + } + if ($is304&&isset($_SERVER['HTTP_IF_NONE_MATCH'])) + {//rfc2616-sec14.html#sec14.26 + $nbCond++; + $etagClient=stripslashes($_SERVER['HTTP_IF_NONE_MATCH']); + $is304=(($etagClient===$etagServer)||($etagClient==='*')); + } + if ((!$is412)&&isset($_SERVER['HTTP_IF_UNMODIFIED_SINCE'])) + {//rfc2616-sec14.html#sec14.28 + $dateCacheClient=$_SERVER['HTTP_IF_UNMODIFIED_SINCE']; + $p=strpos($dateCacheClient,';'); + if ($p!==false) + $dateCacheClient=substr($dateCacheClient,0,$p); + $is412=($dateCacheClient!==$dateLastModif); + } + if ($feedMode) + {//Special RSS/ATOM + global $clientCacheDate; + $clientCacheDate=@strtotime($dateCacheClient); + $cachePrivacy=0; + } + + if ($is412) + {//rfc2616-sec10.html#sec10.4.13 + header('HTTP/1.1 412 Precondition Failed'); + header('Cache-Control: private, max-age=0, must-revalidate'); + header('Content-Type: text/plain'); + echo "HTTP/1.1 Error 412 Precondition Failed: Precondition request failed positive evaluation\n"; + return true; + } + elseif ($is304&&($nbCond>0)) + {//rfc2616-sec10.html#sec10.3.5 + header('HTTP/1.0 304 Not Modified'); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //Comment this line under IIS + return true; + } + else + {//rfc2616-sec10.html#sec10.2.1 + //rfc2616-sec14.html#sec14.3 + if ($compression) ob_start('_httpConditionalCallBack'); //Will check HTTP_ACCEPT_ENCODING + //header('HTTP/1.0 200 OK'); + if ($cacheSeconds<0) + { + $cache='private, no-cache, no-store, must-revalidate'; + //header('Expires: 0'); + header('Pragma: no-cache'); + } + else + { + if ($cacheSeconds===0) + { + $cache='private, must-revalidate, '; + //header('Expires: 0'); + } + elseif ($cachePrivacy===0) $cache='private, '; + elseif ($cachePrivacy===2) $cache='public, '; + else $cache=''; + $cache.='max-age='.floor($cacheSeconds); + } + //header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T',time()+$cacheSeconds)); //HTTP/1.0 //rfc2616-sec14.html#sec14.21 + header('Cache-Control: '.$cache); //rfc2616-sec14.html#sec14.9 + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); + if ($feedMode) header('Connection: close'); //rfc2616-sec14.html#sec14.10 //Comment this line under IIS + return $_SERVER['REQUEST_METHOD']==='HEAD'; //rfc2616-sec9.html#sec9.4 + } +} + +function _httpConditionalCallBack($buffer,$mode=5) +{//Private function automatically called at the end of the script when compression is enabled + //rfc2616-sec14.html#sec14.11 + //You can adjust the level of compression with zlib.output_compression_level in php.ini + if (extension_loaded('zlib')&&(!ini_get('zlib.output_compression'))) + { + $buffer2=ob_gzhandler($buffer,$mode); //Will check HTTP_ACCEPT_ENCODING and put correct headers such as Vary //rfc2616-sec14.html#sec14.44 + if (strlen($buffer2)>1) //When ob_gzhandler succeeded + $buffer=$buffer2; + } + header('Content-Length: '.strlen($buffer)); //Allows persistant connections //rfc2616-sec14.html#sec14.13 + return $buffer; +} + +function httpConditionalRefresh($UnixTimeStamp) +{//Update HTTP headers if the content has just been modified by the client's request + //See an example on http://alexandre.alapetite.fr/doc-alex/compteur/ + if (headers_sent()) return false; + + if (isset($_SERVER['SCRIPT_FILENAME'])) $scriptName=$_SERVER['SCRIPT_FILENAME']; + elseif (isset($_SERVER['PATH_TRANSLATED'])) $scriptName=$_SERVER['PATH_TRANSLATED']; + else return false; + + $dateLastModif=gmdate('D, d M Y H:i:s \G\M\T',$UnixTimeStamp); + + if (isset($_SERVER['QUERY_STRING'])) $myQuery='?'.$_SERVER['QUERY_STRING']; + else $myQuery=''; + global $_sessionMode; + if ($_sessionMode&&isset($_SESSION)) + $myQuery.=print_r($_SESSION,true).session_name().'='.session_id(); + $etagServer='"'.md5($scriptName.$myQuery.'#'.$dateLastModif).'"'; + + header('Last-Modified: '.$dateLastModif); + header('Etag: '.$etagServer); +} diff --git a/lib/lib_date.php b/lib/lib_date.php new file mode 100644 index 000000000..9533711e3 --- /dev/null +++ b/lib/lib_date.php @@ -0,0 +1,131 @@ +<?php +/** + * Author: Alexandre Alapetite http://alexandre.alapetite.fr + * 2014-06-01 + * License: GNU AGPLv3 http://www.gnu.org/licenses/agpl-3.0.html + * + * Parser of ISO 8601 time intervals http://en.wikipedia.org/wiki/ISO_8601#Time_intervals + * Examples: "2014-02/2014-04", "2014-02/04", "2014-06", "P1M" + */ + +/* +example('2014-03'); +example('201403'); +example('2014-03-30'); +example('2014-05-30T13'); +example('2014-05-30T13:30'); +example('2014-02/2014-04'); +example('2014-02--2014-04'); +example('2014-02/04'); +example('2014-02-03/05'); +example('2014-02-03T22:00/22:15'); +example('2014-02-03T22:00/15'); +example('2014-03/'); +example('/2014-03'); +example('2014-03/P1W'); +example('P1W/2014-05-25T23:59:59'); +example('P1Y/'); +example('P1Y'); +example('P2M/'); +example('P3W/'); +example('P4D/'); +example('PT5H/'); +example('PT6M/'); +example('PT7S/'); +example('P1DT1H/'); + +function example($dateInterval) { + $dateIntervalArray = parseDateInterval($dateInterval); + echo $dateInterval, "\t=>\t", + $dateIntervalArray[0] == null ? 'null' : @date('c', $dateIntervalArray[0]), '/', + $dateIntervalArray[1] == null ? 'null' : @date('c', $dateIntervalArray[1]), "\n"; +} +*/ + +function _dateFloor($isoDate) { + $x = explode('T', $isoDate, 2); + $t = isset($x[1]) ? str_pad($x[1], 6, '0') : '000000'; + return str_pad($x[0], 8, '01') . 'T' . $t; +} + +function _dateCeiling($isoDate) { + $x = explode('T', $isoDate, 2); + $t = isset($x[1]) && strlen($x[1]) > 1 ? str_pad($x[1], 6, '59') : '235959'; + switch (strlen($x[0])) { + case 4: + return $x[0] . '1231T' . $t; + case 6: + $d = @strtotime($x[0] . '01'); + return $x[0] . date('t', $d) . 'T' . $t; + default: + return $x[0] . 'T' . $t; + } +} + +function _noDelimit($isoDate) { + return $isoDate === null || $isoDate === '' ? null : + str_replace(array('-', ':'), '', $isoDate); //FIXME: Bug with negative time zone +} + +function _dateRelative($d1, $d2) { + if ($d2 === null) { + return $d1 !== null && $d1[0] !== 'P' ? $d1 : null; + } elseif ($d2 !== '' && $d2[0] != 'P' && $d1 !== null && $d1[0] !== 'P') { + $y2 = substr($d2, 0, 4); + if (strlen($y2) < 4 || !ctype_digit($y2)) { //Does not start by a year + $d2 = _noDelimit($d2); + return substr($d1, 0, -strlen($d2)) . $d2; //Add prefix from $d1 + } + } + return _noDelimit($d2); +} + +/** + * Parameter $dateInterval is a string containing an ISO 8601 time interval. + * Returns an array with the minimum and maximum Unix timestamp of this interval, + * or null if open interval, or false if error. + */ +function parseDateInterval($dateInterval) { + $dateInterval = trim($dateInterval); + $dateInterval = str_replace('--', '/', $dateInterval); + $dateInterval = strtoupper($dateInterval); + $min = null; + $max = null; + $x = explode('/', $dateInterval, 2); + $d1 = _noDelimit($x[0]); + $d2 = _dateRelative($d1, count($x) > 1 ? $x[1] : null); + if ($d1 !== null && $d1[0] !== 'P') { + $min = @strtotime(_dateFloor($d1)); + } + if ($d2 !== null) { + if ($d2[0] === 'P') { + try { + $di2 = new DateInterval($d2); + $dt1 = @date_create(); //new DateTime() would create an Exception if the default time zone is not defined + if ($min !== null && $min !== false) { + $dt1->setTimestamp($min); + } + $max = $dt1->add($di2)->getTimestamp() - 1; + } catch (Exception $e) { + $max = false; + } + } elseif ($d1 === null || $d1[0] !== 'P') { + $max = @strtotime(_dateCeiling($d2)); + } else { + $max = @strtotime($d2); + } + } + if ($d1 !== null && $d1[0] === 'P') { + try { + $di1 = new DateInterval($d1); + $dt2 = @date_create(); + if ($max !== null && $max !== false) { + $dt2->setTimestamp($max); + } + $min = $dt2->sub($di1)->getTimestamp() + 1; + } catch (Exception $e) { + $min = false; + } + } + return array($min, $max); +} diff --git a/lib/lib_opml.php b/lib/lib_opml.php new file mode 100644 index 000000000..16a9921ea --- /dev/null +++ b/lib/lib_opml.php @@ -0,0 +1,231 @@ +<?php + +/* * + * lib_opml is a free library to manage OPML format in PHP. + * It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html). + * Basically it means "text" attribute for outline elements is required. + * + * lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php) + * + * Usages: + * > include('lib_opml.php'); + * > $filename = 'my_opml_file.xml'; + * > $opml_array = libopml_parse_file($filename); + * > print_r($opml_array); + * + * > $opml_string = [...]; + * > $opml_array = libopml_parse_string($opml_string); + * > print_r($opml_array); + * + * > $opml_array = [...]; + * > $opml_string = libopml_render($opml_array); + * > $opml_object = libopml_render($opml_array, true); + * > echo $opml_string; + * > print_r($opml_object); + * + * If parsing fails for any reason (e.g. not an XML string, does not match with + * the specifications), a LibOPML_Exception is raised. + * + * Author: Marien Fressinaud <dev@marienfressinaud.fr> + * Url: https://github.com/marienfressinaud/lib_opml + * Version: 0.1 + * Date: 2014-03-29 + * License: public domain + * + * */ + +class LibOPML_Exception extends Exception {} + + +// These elements are optional +define('HEAD_ELEMENTS', serialize(array( + 'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail', + 'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop', + 'windowLeft', 'windowBottom', 'windowRight' +))); + + +function libopml_parse_outline($outline_xml) { + $outline = array(); + + // An outline may contain any kind of attributes but "text" attribute is + // required ! + $text_is_present = false; + foreach ($outline_xml->attributes() as $key => $value) { + $outline[$key] = (string)$value; + + if ($key === 'text') { + $text_is_present = true; + } + } + + if (!$text_is_present) { + throw new LibOPML_Exception( + 'Outline does not contain any text attribute' + ); + } + + foreach ($outline_xml->children() as $key => $value) { + // An outline may contain any number of outline children + if ($key === 'outline') { + $outline['@outlines'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + return $outline; +} + + +function libopml_parse_string($xml) { + $dom = new DOMDocument(); + $dom->recover = true; + $dom->strictErrorChecking = false; + $dom->loadXML($xml); + $dom->encoding = 'UTF-8'; + + $opml = simplexml_import_dom($dom); + + if (!$opml) { + throw new LibOPML_Exception(); + } + + $array = array( + 'version' => (string)$opml['version'], + 'head' => array(), + 'body' => array() + ); + + // First, we get all "head" elements. Head is required but its sub-elements + // are optional. + foreach ($opml->head->children() as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $array['head'][$key] = (string)$value; + } else { + throw new LibOPML_Exception( + $key . 'is not part of OPML format' + ); + } + } + + // Then, we get body oulines. Body must contain at least one outline + // element. + $at_least_one_outline = false; + foreach ($opml->body->children() as $key => $value) { + if ($key === 'outline') { + $at_least_one_outline = true; + $array['body'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + if (!$at_least_one_outline) { + throw new LibOPML_Exception( + 'Body must contain at least one outline element' + ); + } + + return $array; +} + + +function libopml_parse_file($filename) { + $file_content = file_get_contents($filename); + + if ($file_content === false) { + throw new LibOPML_Exception( + $filename . ' cannot be found' + ); + } + + return libopml_parse_string($file_content); +} + + +function libopml_render_outline($parent_elt, $outline) { + // Outline MUST be an array! + if (!is_array($outline)) { + throw new LibOPML_Exception( + 'Outline element must be defined as array' + ); + } + + $outline_elt = $parent_elt->addChild('outline'); + $text_is_present = false; + foreach ($outline as $key => $value) { + // Only outlines can be an array and so we consider children are also + // outline elements. + if ($key === '@outlines' && is_array($value)) { + foreach ($value as $outline_child) { + libopml_render_outline($outline_elt, $outline_child); + } + } elseif (is_array($value)) { + throw new LibOPML_Exception( + 'Type of outline elements cannot be array: ' . $key + ); + } else { + // Detect text attribute is present, that's good :) + if ($key === 'text') { + $text_is_present = true; + } + + $outline_elt->addAttribute($key, $value); + } + } + + if (!$text_is_present) { + throw new LibOPML_Exception( + 'You must define at least a text element for all outlines' + ); + } +} + + +function libopml_render($array, $as_xml_object = false) { + $opml = new SimpleXMLElement('<opml version="2.0"></opml>'); + + // Create head element. $array['head'] is optional but head element will + // exist in the final XML object. + $head = $opml->addChild('head'); + if (isset($array['head'])) { + foreach ($array['head'] as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $head->addChild($key, $value); + } + } + } + + // Check body is set and contains at least one element + if (!isset($array['body'])) { + throw new LibOPML_Exception( + '$array must contain a body element' + ); + } + if (count($array['body']) <= 0) { + throw new LibOPML_Exception( + 'Body element must contain at least one element (array)' + ); + } + + // Create outline elements + $body = $opml->addChild('body'); + foreach ($array['body'] as $outline) { + libopml_render_outline($body, $outline); + } + + // And return the final result + if ($as_xml_object) { + return $opml; + } else { + $dom = dom_import_simplexml($opml)->ownerDocument; + $dom->formatOutput = true; + $dom->encoding = 'UTF-8'; + return $dom->saveXML(); + } +} diff --git a/lib/lib_phpQuery.php b/lib/lib_phpQuery.php index 4aefb70fe..194efa0c6 100644 --- a/lib/lib_phpQuery.php +++ b/lib/lib_phpQuery.php @@ -280,7 +280,7 @@ class DOMDocumentWrapper { // @see http://www.w3.org/International/O-HTTP-charset if (! $documentCharset) { $documentCharset = 'ISO-8859-1'; - $addDocumentCharset = true; + $addDocumentCharset = true; } // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding' // Worse, some pages can have mixed encodings... we'll try not to worry about that @@ -567,7 +567,7 @@ class DOMDocumentWrapper { // if ($fake === false) // throw new Exception("Error loading documentFragment markup"); // else -// $return = array_merge($return, +// $return = array_merge($return, // $this->import($fake->root->childNodes) // ); // } else { @@ -969,24 +969,24 @@ interface ICallbackNamed { } /** * Callback class introduces currying-like pattern. - * + * * Example: * function foo($param1, $param2, $param3) { * var_dump($param1, $param2, $param3); * } - * $fooCurried = new Callback('foo', - * 'param1 is now statically set', + * $fooCurried = new Callback('foo', + * 'param1 is now statically set', * new CallbackParam, new CallbackParam * ); * phpQuery::callbackRun($fooCurried, * array('param2 value', 'param3 value' * ); - * - * Callback class is supported in all phpQuery methods which accepts callbacks. + * + * Callback class is supported in all phpQuery methods which accepts callbacks. * * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> - * + * * @TODO??? return fake forwarding function created via create_function * @TODO honor paramStructure */ @@ -995,7 +995,7 @@ class Callback public $callback = null; public $params = null; protected $name; - public function __construct($callback, $param1 = null, $param2 = null, + public function __construct($callback, $param1 = null, $param2 = null, $param3 = null) { $params = func_get_args(); $params = array_slice($params, 1); @@ -1024,11 +1024,11 @@ class Callback } /** * Shorthand for new Callback(create_function(...), ...); - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackBody extends Callback { - public function __construct($paramList, $code, $param1 = null, $param2 = null, + public function __construct($paramList, $code, $param1 = null, $param2 = null, $param3 = null) { $params = func_get_args(); $params = array_slice($params, 2); @@ -1038,7 +1038,7 @@ class CallbackBody extends Callback { } /** * Callback type which on execution returns reference passed during creation. - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackReturnReference extends Callback @@ -1060,7 +1060,7 @@ class CallbackReturnReference extends Callback } /** * Callback type which on execution returns value passed during creation. - * + * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> */ class CallbackReturnValue extends Callback @@ -1087,7 +1087,7 @@ class CallbackReturnValue extends Callback } /** * CallbackParameterToReference can be used when we don't really want a callback, - * only parameter passed to it. CallbackParameterToReference takes first + * only parameter passed to it. CallbackParameterToReference takes first * parameter's value and passes it to reference. * * @author Tobiasz Cudnik <tobiasz.cudnik/gmail.com> @@ -1095,7 +1095,7 @@ class CallbackReturnValue extends Callback class CallbackParameterToReference extends Callback { /** * @param $reference - * @TODO implement $paramIndex; + * @TODO implement $paramIndex; * param index choose which callback param will be passed to reference */ public function __construct(&$reference){ @@ -2994,7 +2994,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param $code * @return unknown_type */ @@ -3005,7 +3005,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param $code * @return unknown_type */ @@ -4034,7 +4034,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param <type> $key * @param <type> $value */ @@ -4051,7 +4051,7 @@ class phpQueryObject } /** * Enter description here... - * + * * @param <type> $key */ public function removeData($key) { @@ -4392,8 +4392,8 @@ if (!function_exists('mb_substr_count')) */ abstract class phpQuery { /** - * XXX: Workaround for mbstring problems - * + * XXX: Workaround for mbstring problems + * * @var bool */ public static $mbstringSupport = true; diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 41e95fc90..31c9cdbc1 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -1,208 +1,246 @@ <?php -// vérifie qu'on est connecté -function is_logged () { - return Session::param ('mail') != false; +if (!function_exists('json_decode')) { + require_once('JSON.php'); + function json_decode($var) { + $JSON = new Services_JSON; + return (array)($JSON->decode($var)); + } } -// vérifie que le système d'authentification est configuré -function login_is_conf ($conf) { - return $conf->mailLogin () != false; +if (!function_exists('json_encode')) { + require_once('JSON.php'); + function json_encode($var) { + $JSON = new Services_JSON; + return $JSON->encodeUnsafe($var); + } } -// tiré de Shaarli de Seb Sauvage -function small_hash ($txt) { - $t = rtrim (base64_encode (hash ('crc32', $txt, true)), '='); - $t = str_replace ('+', '-', $t); // Get rid of characters which need encoding in URLs. - $t = str_replace ('/', '_', $t); - $t = str_replace ('=', '@', $t); - - return $t; +//<Auto-loading> +function classAutoloader($class) { + if (strpos($class, 'FreshRSS') === 0) { + $components = explode('_', $class); + switch (count($components)) { + case 1: + include(APP_PATH . '/' . $components[0] . '.php'); + return; + case 2: + include(APP_PATH . '/Models/' . $components[1] . '.php'); + return; + case 3: //Controllers, Exceptions + @include(APP_PATH . '/' . $components[2] . 's/' . $components[1] . $components[2] . '.php'); + return; + } + } elseif (strpos($class, 'Minz') === 0) { + include(LIB_PATH . '/' . str_replace('_', '/', $class) . '.php'); + } elseif (strpos($class, 'SimplePie') === 0) { + include(LIB_PATH . '/SimplePie/' . str_replace('_', '/', $class) . '.php'); + } } -function timestamptodate ($t, $hour = true) { - $month = Translate::t (date('M', $t)); - if ($hour) { - $date = Translate::t ('format_date_hour', $month); +spl_autoload_register('classAutoloader'); +//</Auto-loading> + +function checkUrl($url) { + if (empty ($url)) { + return ''; + } + if (!preg_match ('#^https?://#i', $url)) { + $url = 'http://' . $url; + } + if (filter_var($url, FILTER_VALIDATE_URL) || + (version_compare(PHP_VERSION, '5.3.3', '<') && (strpos($url, '-') > 0) && //PHP bug #51192 + ($url === filter_var($url, FILTER_SANITIZE_URL)))) { + return $url; } else { - $date = Translate::t ('format_date', $month); + return false; } - - return date ($date, $t); } -function sortEntriesByDate ($entry1, $entry2) { - return $entry2->date (true) - $entry1->date (true); -} -function sortReverseEntriesByDate ($entry1, $entry2) { - return $entry1->date (true) - $entry2->date (true); +function formatNumber($n, $precision = 0) { + return str_replace(' ', ' ', //Espace insécable //TODO: remplacer par une espace _fine_ insécable + number_format($n, $precision, '.', ' ')); //number_format does not seem to be Unicode-compatible } -function get_domain ($url) { - return parse_url($url, PHP_URL_HOST); +function formatBytes($bytes, $precision = 2, $system = 'IEC') { + if ($system === 'IEC') { + $base = 1024; + $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB'); + } elseif ($system === 'SI') { + $base = 1000; + $units = array('B', 'KB', 'MB', 'GB', 'TB'); + } + $bytes = max(intval($bytes), 0); + $pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base)); + $pow = min($pow, count($units) - 1); + $bytes /= pow($base, $pow); + return formatNumber($bytes, $precision) . ' ' . $units[$pow]; } -function opml_export ($cats) { - $txt = ''; - - foreach ($cats as $cat) { - $txt .= '<outline text="' . $cat['name'] . '">' . "\n"; - - foreach ($cat['feeds'] as $feed) { - $txt .= "\t" . '<outline text="' . cleanText ($feed->name ()) . '" type="rss" xmlUrl="' . htmlentities ($feed->url (), ENT_COMPAT, 'UTF-8') . '" htmlUrl="' . htmlentities ($feed->website (), ENT_COMPAT, 'UTF-8') . '" />' . "\n"; - } - - $txt .= '</outline>' . "\n"; +function timestamptodate ($t, $hour = true) { + $month = Minz_Translate::t (date('M', $t)); + if ($hour) { + $date = Minz_Translate::t ('format_date_hour', $month); + } else { + $date = Minz_Translate::t ('format_date', $month); } - - return $txt; -} -function cleanText ($text) { - return preg_replace ('/&[\w]+;/', '', $text); + return @date ($date, $t); } -function opml_import ($xml) { - $opml = @simplexml_load_string ($xml); - - if (!$opml) { - throw new OpmlException (); - } - - $catDAO = new CategoryDAO(); - $catDAO->checkDefault(); - $defCat = $catDAO->getDefault(); - - $categories = array (); - $feeds = array (); - - foreach ($opml->body->outline as $outline) { - if (!isset ($outline['xmlUrl'])) { - // Catégorie - $title = ''; - - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - - if ($title) { - // Permet d'éviter les soucis au niveau des id : - // ceux-ci sont générés en fonction de la date, - // un flux pourrait être dans une catégorie X avec l'id Y - // alors qu'il existe déjà la catégorie X mais avec l'id Z - // Y ne sera pas ajouté et le flux non plus vu que l'id - // de sa catégorie n'exisera pas - $catDAO = new CategoryDAO (); - $cat = $catDAO->searchByName ($title); - if ($cat === false) { - $cat = new Category ($title); - } - $categories[] = $cat; - - $feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ())); - } +function html_only_entity_decode($text) { + static $htmlEntitiesOnly = null; + if ($htmlEntitiesOnly === null) { + if (version_compare(PHP_VERSION, '5.3.4') >= 0) { + $htmlEntitiesOnly = array_flip(array_diff( + get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES, 'UTF-8'), //Decode HTML entities + get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES, 'UTF-8') //Preserve XML entities + )); } else { - // Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut - $feeds[] = getFeed ($outline, $defCat->id()); + $htmlEntitiesOnly = array_map('utf8_encode', array_flip(array_diff( + get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES), //Decode HTML entities + get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES) //Preserve XML entities + ))); } } - - return array ($categories, $feeds); + return strtr($text, $htmlEntitiesOnly); } -/** - * import all feeds of a given outline tag - */ -function getFeedsOutline ($outline, $cat_id) { - $feeds = array (); - - foreach ($outline->children () as $child) { - if (isset ($child['xmlUrl'])) { - $feeds[] = getFeed ($child, $cat_id); - } else { - $feeds = array_merge( - $feeds, - getFeedsOutline ($child, $cat_id) - ); - } - } - - return $feeds; +function customSimplePie() { + $simplePie = new SimplePie(); + $simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); + $simplePie->set_cache_location(CACHE_PATH); + $simplePie->set_cache_duration(800); + $simplePie->strip_htmltags(array( + 'base', 'blink', 'body', 'doctype', 'embed', + 'font', 'form', 'frame', 'frameset', 'html', + 'link', 'input', 'marquee', 'meta', 'noscript', + 'object', 'param', 'plaintext', 'script', 'style', + )); + $simplePie->strip_attributes(array_merge($simplePie->strip_attributes, array( + 'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup', + 'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur', + 'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless'))); + $simplePie->add_attributes(array( + 'img' => array('lazyload' => '', 'postpone' => ''), //http://www.w3.org/TR/resource-priorities/ + 'audio' => array('lazyload' => '', 'postpone' => '', 'preload' => 'none'), + 'iframe' => array('lazyload' => '', 'postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'), + 'video' => array('lazyload' => '', 'postpone' => '', 'preload' => 'none'), + )); + $simplePie->set_url_replacements(array( + 'a' => 'href', + 'area' => 'href', + 'audio' => 'src', + 'blockquote' => 'cite', + 'del' => 'cite', + 'form' => 'action', + 'iframe' => 'src', + 'img' => array( + 'longdesc', + 'src' + ), + 'input' => 'src', + 'ins' => 'cite', + 'q' => 'cite', + 'source' => 'src', + 'track' => 'src', + 'video' => array( + 'poster', + 'src', + ), + )); + return $simplePie; } -function getFeed ($outline, $cat_id) { - $url = (string) $outline['xmlUrl']; - $title = ''; - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; +function sanitizeHTML($data, $base = '') { + static $simplePie = null; + if ($simplePie == null) { + $simplePie = customSimplePie(); + $simplePie->init(); } - $feed = new Feed ($url); - $feed->_category ($cat_id); - $feed->_name ($title); - return $feed; + return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_HTML, $base)); } - /* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */ function get_content_by_parsing ($url, $path) { + require_once (LIB_PATH . '/lib_phpQuery.php'); + + syslog(LOG_INFO, 'FreshRSS GET ' . $url); $html = file_get_contents ($url); - + if ($html) { $doc = phpQuery::newDocument ($html); $content = $doc->find ($path); - $content->find ('*')->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - $content->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - return $content->__toString (); + return sanitizeHTML($content->__toString(), $url); } else { throw new Exception (); } } -/* Télécharge le favicon d'un site, le place sur le serveur et retourne l'URL */ -function dowload_favicon ($website, $id) { - $url = 'http://g.etfv.co/' . $website; - $favicons_dir = PUBLIC_PATH . '/data/favicons'; - $dest = $favicons_dir . '/' . $id . '.ico'; - $favicon_url = '/data/favicons/' . $id . '.ico'; +/** + * Add support of image lazy loading + * Move content from src attribute to data-original + * @param content is the text we want to parse + */ +function lazyimg($content) { + return preg_replace( + '/<((?:img|iframe)[^>]+?)src=[\'"]([^"\']+)[\'"]([^>]*)>/i', + '<$1src="' . Minz_Url::display('/themes/icons/grey.gif') . '" data-original="$2"$3>', + $content + ); +} - if (!is_dir ($favicons_dir)) { - if (!mkdir ($favicons_dir, 0755, true)) { - return $url; - } +function uTimeString() { + $t = @gettimeofday(); + return $t['sec'] . str_pad($t['usec'], 6, '0'); +} + +function uSecString() { + $t = @gettimeofday(); + return str_pad($t['usec'], 6, '0'); +} + +function invalidateHttpCache() { + Minz_Session::_param('touch', uTimeString()); + return touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'); +} + +function usernameFromPath($userPath) { + if (preg_match('%/([A-Za-z0-9]{1,16})_user\.php$%', $userPath, $matches)) { + return $matches[1]; + } else { + return ''; } +} - if (!file_exists ($dest)) { - $c = curl_init ($url); - curl_setopt ($c, CURLOPT_HEADER, false); - curl_setopt ($c, CURLOPT_RETURNTRANSFER, true); - curl_setopt ($c, CURLOPT_BINARYTRANSFER, true); - $imgRaw = curl_exec ($c); - - if (curl_getinfo ($c, CURLINFO_HTTP_CODE) == 200) { - $file = fopen ($dest, 'w'); - if ($file === false) { - return $url; - } - - fwrite ($file, $imgRaw); - fclose ($file); - } else { - return $url; - } +function listUsers() { + return array_map('usernameFromPath', glob(DATA_PATH . '/*_user.php')); +} - curl_close ($c); +function httpAuthUser() { + return isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : ''; +} + +function cryptAvailable() { + if (version_compare(PHP_VERSION, '5.3.3', '>=')) { + try { + $hash = '$2y$04$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG'; + return $hash === @crypt('password', $hash); + } catch (Exception $e) { + } } + return false; +} - return $favicon_url; +function is_referer_from_same_domain() { + if (empty($_SERVER['HTTP_REFERER'])) { + return false; + } + $host = parse_url(((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https://' : 'http://') . + (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'])); + $referer = parse_url($_SERVER['HTTP_REFERER']); + if (empty($host['scheme']) || empty($referer['scheme']) || $host['scheme'] !== $referer['scheme'] || + empty($host['host']) || empty($referer['host']) || $host['host'] !== $referer['host']) { + return false; + } + return (isset($host['port']) ? $host['port'] : 0) === (isset($referer['port']) ? $referer['port'] : 0); } diff --git a/lib/lib_text.php b/lib/lib_text.php deleted file mode 100644 index 9792e191e..000000000 --- a/lib/lib_text.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -function bbDecode($string) { - - $find = array( - "'\[b\](.*?)\[/b\]'is", - "'\[u\](.*?)\[/u\]'is", - "'\[i\](.*?)\[/i\]'is", - "'\[s\](.*?)\[/s\]'is", - "'\[code\](.*?)\[/code\]'is", - "'\[quote\](.*?)\[/quote\]'is", - "'\[quote=(.*?)\](.*?)\[/quote\]'is", - "'\[span=(.*?)\](.*?)\[/span\]'i", - "'\[div=(.*?)\](.*?)\[/div\]'is", - "'\[h\](.*?)\[/h\]'i", - "'\[url\](.*?)\[/url\]'i", - "'\[url=(.*?)\](.*?)\[/url\]'i", - "'\[video\](.*?)\[/video\]'i", - "'\[video width=(.*?) height=(.*?)\](.*?)\[/video\]'i", - "'\[img\](.*?)\[/img\]'i", - "'\[img title=(.*?) rel=(.*?)\](.*?)\[/img\]'i", - "'\[img title=(.*?)\](.*?)\[/img\]'i", - ); - - $replace = array( - "<strong>\\1</strong>", - "<u>\\1</u>", - "<i>\\1</i>", - "<del>\\1</del>", - "<pre>\\1</pre>", - "<q>\\1</q>", - "<q><span class=\"cite\">\\1 a écrit</span><br />\\2</q>", - "<span class=\"\\1\">\\2</span>", - "<div class=\"\\1\">\\2</div>", - "<b>\\1</b><br />", - "<a href=\"\\1\">\\1</a>", - "<a href=\"\\1\">\\2</a>", - "<object width=\"480\" height=\"387\" class=\"center\"><param name=\"movie\" value=\"\\1\"></param><embed src=\"\\1\" type=\"application/x-shockwave-flash\" width=\"480\" height=\"387\"></embed></object>", - "<object width=\"\\1\" height=\"\\2\" class=\"center\"><param name=\"movie\" value=\"\\3\"></param><embed src=\"\\3\" type=\"application/x-shockwave-flash\" width=\"\\1\" height=\"\\2\"></embed></object>", - "<a href=\"\\1\" rel=\"prettyPhoto\"><img src=\"\\1\" alt=\"\" /></a>", - "<img class=\"illustration\" src=\"\\3\" alt=\"\\1\" />", - "<img src=\"\\2\" alt=\"\\1\" />", - ); - - $string = makeLinks(preg_replace ($find, $replace, $string)); - $string = nl2brPlus ($string); - - return $string; -} - -// do nl2br except when in a <pre> tag -function nl2brPlus($string) { - $string = str_replace("\n", "<br />", $string); - if(preg_match_all('/\<pre\>(.*?)\<\/pre\>/', $string, $match)){ - foreach($match as $a){ - foreach($a as $b){ - $string = str_replace('<pre>'.$b.'</pre>', "<pre>".str_replace("<br />", "", $b)."</pre>", $string); - } - } - } - return $string; -} - -# Transform URL and e-mails into links -function makeLinks($string) { - $string = preg_replace_callback('/\s(http|https|ftp):(\/\/){0,1}([^\"\s]*)/i','splitUri',$string); - return $string; -} - -# Split links, require for makeLinks -function splitUri($matches) { - $uri = $matches[1].':'.$matches[2].$matches[3]; - $t = parse_url($uri); - $link = $matches[3]; - - if (!empty($t['scheme'])) { - return ' <a href="'.$uri.'">'.$link.'</a>'; - } else { - return $uri; - } -} - -// parse la description pour ajouter les liens sur les tags -function parse_tags ($desc) { - $desc_parse = preg_replace ('/#([\w\dÀÇÈÉÊËÎÏÔÙÚÛÜàáâçèéêëîïóùúûü]+)/i', '<a class="linktag" href="?addtag=\\1">\\1</a>', $desc); - - return $desc_parse; -} - -function lazyimg($content) { - return preg_replace( - '/<img([^<]+)src=([\'"])([^"\']*)([\'"])([^<]*)>/i', - '<img$1src="' . Url::display('/data/grey.gif') . '" data-original="$3"$5>', - $content - ); -}
\ No newline at end of file diff --git a/lib/minz/Cache.php b/lib/minz/Cache.php deleted file mode 100644 index 6848e3350..000000000 --- a/lib/minz/Cache.php +++ /dev/null @@ -1,116 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Cache permet de gérer facilement les pages en cache - */ -class Minz_Cache { - /** - * $expire timestamp auquel expire le cache de $url - */ - private $expire = 0; - - /** - * $file est le nom du fichier de cache - */ - private $file = ''; - - /** - * $enabled permet de déterminer si le cache est activé - */ - private static $enabled = true; - - /** - * Constructeur - */ - public function __construct () { - $this->_fileName (); - $this->_expire (); - } - - /** - * Setteurs - */ - public function _fileName () { - $file = md5 (Request::getURI ()); - - $this->file = CACHE_PATH . '/'.$file; - } - - public function _expire () { - if ($this->exist ()) { - $this->expire = filemtime ($this->file) - + Configuration::delayCache (); - } - } - - /** - * Permet de savoir si le cache est activé - * @return true si activé, false sinon - */ - public static function isEnabled () { - return Configuration::cacheEnabled () && self::$enabled; - } - - /** - * Active / désactive le cache - */ - public static function switchOn () { - self::$enabled = true; - } - public static function switchOff () { - self::$enabled = false; - } - - /** - * Détermine si le cache de $url a expiré ou non - * @return true si il a expiré, false sinon - */ - public function expired () { - return time () > $this->expire; - } - - /** - * Affiche le contenu du cache - * @print le code html du cache - */ - public function render () { - if ($this->exist ()) { - include ($this->file); - } - } - - /** - * Enregistre $html en cache - * @param $html le html à mettre en cache - */ - public function cache ($html) { - file_put_contents ($this->file, $html); - } - - /** - * Permet de savoir si le cache existe - * @return true si il existe, false sinon - */ - public function exist () { - return file_exists ($this->file); - } - - /** - * Nettoie le cache en supprimant tous les fichiers - */ - public static function clean () { - $files = opendir (CACHE_PATH); - - while ($fic = readdir ($files)) { - if ($fic != '.' && $fic != '..') { - unlink (CACHE_PATH.'/'.$fic); - } - } - - closedir ($files); - } -} diff --git a/lib/minz/Configuration.php b/lib/minz/Configuration.php deleted file mode 100755 index ad6df1bcf..000000000 --- a/lib/minz/Configuration.php +++ /dev/null @@ -1,242 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Configuration permet de gérer la configuration de l'application - */ -class Configuration { - const CONF_PATH_NAME = '/configuration/application.ini'; - - /** - * VERSION est la version actuelle de MINZ - */ - const VERSION = '1.3.1'; - - /** - * valeurs possibles pour l'"environment" - * SILENT rend l'application muette (pas de log) - * PRODUCTION est recommandée pour une appli en production - * (log les erreurs critiques) - * DEVELOPMENT log toutes les erreurs - */ - const SILENT = 0; - const PRODUCTION = 1; - const DEVELOPMENT = 2; - - /** - * définition des variables de configuration - * $sel_application une chaîne de caractères aléatoires (obligatoire) - * $environment gère le niveau d'affichage pour log et erreurs - * $use_url_rewriting indique si on utilise l'url_rewriting - * $base_url le chemin de base pour accéder à l'application - * $title le nom de l'application - * $language la langue par défaut de l'application - * $cacheEnabled permet de savoir si le cache doit être activé - * $delayCache la limite de cache - * $db paramètres pour la base de données (tableau) - * - host le serveur de la base - * - user nom d'utilisateur - * - password mot de passe de l'utilisateur - * - base le nom de la base de données - */ - private static $sel_application = ''; - private static $environment = Configuration::PRODUCTION; - private static $base_url = ''; - private static $use_url_rewriting = false; - private static $title = ''; - private static $language = 'en'; - private static $cache_enabled = false; - private static $delay_cache = 3600; - - private static $db = array ( - 'host' => false, - 'user' => false, - 'password' => false, - 'base' => false - ); - - /* - * Getteurs - */ - public static function selApplication () { - return self::$sel_application; - } - public static function environment () { - return self::$environment; - } - public static function baseUrl () { - return self::$base_url; - } - public static function useUrlRewriting () { - return self::$use_url_rewriting; - } - public static function title () { - return self::$title; - } - public static function language () { - return self::$language; - } - public static function cacheEnabled () { - return self::$cache_enabled; - } - public static function delayCache () { - return self::$delay_cache; - } - public static function dataBase () { - return self::$db; - } - - /** - * Initialise les variables de configuration - * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas - * @exception BadConfigurationException si CONF_PATH_NAME mal formaté - */ - public static function init () { - try { - self::parseFile (); - self::setReporting (); - } catch (BadConfigurationException $e) { - throw $e; - } catch (FileNotExistException $e) { - throw $e; - } - } - - /** - * Parse un fichier de configuration de type ".ini" - * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas - * @exception BadConfigurationException si CONF_PATH_NAME mal formaté - */ - private static function parseFile () { - if (!file_exists (APP_PATH . self::CONF_PATH_NAME)) { - throw new FileNotExistException ( - APP_PATH . self::CONF_PATH_NAME, - MinzException::ERROR - ); - } - $ini_array = parse_ini_file ( - APP_PATH . self::CONF_PATH_NAME, - true - ); - - // [general] est obligatoire - if (!isset ($ini_array['general'])) { - throw new BadConfigurationException ( - '[general]', - MinzException::ERROR - ); - } - $general = $ini_array['general']; - - - // sel_application est obligatoire - if (!isset ($general['sel_application'])) { - throw new BadConfigurationException ( - 'sel_application', - MinzException::ERROR - ); - } - self::$sel_application = $general['sel_application']; - - if (isset ($general['environment'])) { - switch ($general['environment']) { - case 'silent': - self::$environment = Configuration::SILENT; - break; - case 'development': - self::$environment = Configuration::DEVELOPMENT; - break; - case 'production': - self::$environment = Configuration::PRODUCTION; - break; - default: - throw new BadConfigurationException ( - 'environment', - MinzException::ERROR - ); - } - - } - if (isset ($general['base_url'])) { - self::$base_url = $general['base_url']; - } - if (isset ($general['use_url_rewriting'])) { - self::$use_url_rewriting = $general['use_url_rewriting']; - } - - if (isset ($general['title'])) { - self::$title = $general['title']; - } - if (isset ($general['language'])) { - self::$language = $general['language']; - } - if (isset ($general['cache_enabled'])) { - self::$cache_enabled = $general['cache_enabled']; - if (CACHE_PATH === false && self::$cache_enabled) { - throw new FileNotExistException ( - 'CACHE_PATH', - MinzException::ERROR - ); - } - } - if (isset ($general['delay_cache'])) { - self::$delay_cache = $general['delay_cache']; - } - - // Base de données - $db = false; - if (isset ($ini_array['db'])) { - $db = $ini_array['db']; - } - if ($db) { - if (!isset ($db['host'])) { - throw new BadConfigurationException ( - 'host', - MinzException::ERROR - ); - } - if (!isset ($db['user'])) { - throw new BadConfigurationException ( - 'user', - MinzException::ERROR - ); - } - if (!isset ($db['password'])) { - throw new BadConfigurationException ( - 'password', - MinzException::ERROR - ); - } - if (!isset ($db['base'])) { - throw new BadConfigurationException ( - 'base', - MinzException::ERROR - ); - } - - self::$db['type'] = isset ($db['type']) ? $db['type'] : 'mysql'; - self::$db['host'] = $db['host']; - self::$db['user'] = $db['user']; - self::$db['password'] = $db['password']; - self::$db['base'] = $db['base']; - self::$db['prefix'] = isset ($db['prefix']) ? $db['prefix'] : ''; - } - } - - private static function setReporting () { - if (self::environment () == self::DEVELOPMENT) { - error_reporting (E_ALL); - ini_set ('display_errors','On'); - ini_set('log_errors', 'On'); - } elseif (self::environment () == self::PRODUCTION) { - error_reporting(E_ALL); - ini_set('display_errors','Off'); - ini_set('log_errors', 'On'); - } else { - error_reporting(0); - } - } -} diff --git a/lib/minz/Dispatcher.php b/lib/minz/Dispatcher.php deleted file mode 100644 index 0cfdd8e75..000000000 --- a/lib/minz/Dispatcher.php +++ /dev/null @@ -1,152 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Le Dispatcher s'occupe d'initialiser le Controller et d'executer l'action - * déterminée dans la Request - * C'est un singleton - */ -class Dispatcher { - const CONTROLLERS_PATH_NAME = '/controllers'; - - /* singleton */ - private static $instance = null; - - private $router; - private $controller; - - /** - * Récupère l'instance du Dispatcher - */ - public static function getInstance ($router) { - if (is_null (self::$instance)) { - self::$instance = new Dispatcher ($router); - } - return self::$instance; - } - - /** - * Constructeur - */ - private function __construct ($router) { - $this->router = $router; - } - - /** - * Lance le controller indiqué dans Request - * Remplit le body de Response à partir de la Vue - * @exception MinzException - */ - public function run () { - $cache = new Minz_Cache(); - // Le ob_start est dupliqué : sans ça il y a un bug sous Firefox - // ici on l'appelle avec 'ob_gzhandler', après sans. - // Vraisemblablement la compression fonctionne mais c'est sale - // J'ignore les effets de bord :( - ob_start ('ob_gzhandler'); - - if (Minz_Cache::isEnabled () && !$cache->expired ()) { - ob_start (); - $cache->render (); - $text = ob_get_clean(); - } else { - while (Request::$reseted) { - Request::$reseted = false; - - try { - $this->createController ( - Request::controllerName () - . 'Controller' - ); - - $this->controller->init (); - $this->controller->firstAction (); - $this->launchAction ( - Request::actionName () - . 'Action' - ); - $this->controller->lastAction (); - - if (!Request::$reseted) { - ob_start (); - $this->controller->view ()->build (); - $text = ob_get_clean(); - } - } catch (MinzException $e) { - throw $e; - } - } - - if (Minz_Cache::isEnabled ()) { - $cache->cache ($text); - } - } - - Response::setBody ($text); - } - - /** - * Instancie le Controller - * @param $controller_name le nom du controller à instancier - * @exception FileNotExistException le fichier correspondant au - * > controller n'existe pas - * @exception ControllerNotExistException le controller n'existe pas - * @exception ControllerNotActionControllerException controller n'est - * > pas une instance de ActionController - */ - private function createController ($controller_name) { - $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' - . $controller_name . '.php'; - - if (!file_exists ($filename)) { - throw new FileNotExistException ( - $filename, - MinzException::ERROR - ); - } - require_once ($filename); - - if (!class_exists ($controller_name)) { - throw new ControllerNotExistException ( - $controller_name, - MinzException::ERROR - ); - } - $this->controller = new $controller_name ($this->router); - - if (! ($this->controller instanceof ActionController)) { - throw new ControllerNotActionControllerException ( - $controller_name, - MinzException::ERROR - ); - } - } - - /** - * Lance l'action sur le controller du dispatcher - * @param $action_name le nom de l'action - * @exception ActionException si on ne peut pas exécuter l'action sur - * > le controller - */ - private function launchAction ($action_name) { - if (!Request::$reseted) { - if (!is_callable (array ( - $this->controller, - $action_name - ))) { - throw new ActionException ( - get_class ($this->controller), - $action_name, - MinzException::ERROR - ); - } - call_user_func (array ( - $this->controller, - $action_name - )); - } - } -} diff --git a/lib/minz/FrontController.php b/lib/minz/FrontController.php deleted file mode 100755 index 84e7a07e1..000000000 --- a/lib/minz/FrontController.php +++ /dev/null @@ -1,123 +0,0 @@ -<?php -# ***** BEGIN LICENSE BLOCK ***** -# MINZ - a free PHP Framework like Zend Framework -# Copyright (C) 2011 Marien Fressinaud -# -# 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/>. -# -# ***** END LICENSE BLOCK ***** - -/** - * La classe FrontController est le noyau du framework, elle lance l'application - * Elle est appelée en général dans le fichier index.php à la racine du serveur - */ -class FrontController { - protected $dispatcher; - protected $router; - - /** - * Constructeur - * Initialise le router et le dispatcher - */ - public function __construct () { - $this->loadLib (); - - if (LOG_PATH === false) { - $this->killApp ('Path doesn\'t exist : LOG_PATH'); - } - - try { - Configuration::init (); - - Request::init (); - - $this->router = new Router (); - $this->router->init (); - } catch (RouteNotFoundException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - Error::error ( - 404, - array ('error' => array ($e->getMessage ())) - ); - } catch (MinzException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $this->killApp (); - } - - $this->dispatcher = Dispatcher::getInstance ($this->router); - } - - /** - * Inclue les fichiers de la librairie - */ - private function loadLib () { - require ('ActionController.php'); - require ('Cache.php'); - require ('Configuration.php'); - require ('Dispatcher.php'); - require ('Error.php'); - require ('Helper.php'); - require ('Log.php'); - require ('Model.php'); - require ('Paginator.php'); - require ('Request.php'); - require ('Response.php'); - require ('Router.php'); - require ('Session.php'); - require ('Translate.php'); - require ('Url.php'); - require ('View.php'); - - require ('dao/Model_pdo.php'); - require ('dao/Model_txt.php'); - require ('dao/Model_array.php'); - - require ('exceptions/MinzException.php'); - } - - /** - * Démarre l'application (lance le dispatcher et renvoie la réponse - */ - public function run () { - try { - $this->dispatcher->run (); - Response::send (); - } catch (MinzException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - - if ($e instanceof FileNotExistException || - $e instanceof ControllerNotExistException || - $e instanceof ControllerNotActionControllerException || - $e instanceof ActionException) { - Error::error ( - 404, - array ('error' => array ($e->getMessage ())), - true - ); - } else { - $this->killApp (); - } - } - } - - /** - * Permet d'arrêter le programme en urgence - */ - private function killApp ($txt = '') { - if ($txt == '') { - $txt = 'See logs files'; - } - exit ('### Application problem ###'."\n".$txt); - } -} diff --git a/lib/minz/Helper.php b/lib/minz/Helper.php deleted file mode 100755 index 4f64ba218..000000000 --- a/lib/minz/Helper.php +++ /dev/null @@ -1,22 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Helper représente une aide pour des tâches récurrentes - */ -class Helper { - /** - * Annule les effets des magic_quotes pour une variable donnée - * @param $var variable à traiter (tableau ou simple variable) - */ - public static function stripslashes_r ($var) { - if (is_array ($var)){ - return array_map (array ('Helper', 'stripslashes_r'), $var); - } else { - return stripslashes($var); - } - } -} diff --git a/lib/minz/Request.php b/lib/minz/Request.php deleted file mode 100644 index bde17f988..000000000 --- a/lib/minz/Request.php +++ /dev/null @@ -1,196 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Request représente la requête http - */ -class Request { - private static $controller_name = ''; - private static $action_name = ''; - private static $params = array (); - - private static $default_controller_name = 'index'; - private static $default_action_name = 'index'; - - public static $reseted = true; - - /** - * Getteurs - */ - public static function controllerName () { - return self::$controller_name; - } - public static function actionName () { - return self::$action_name; - } - public static function params () { - return self::$params; - } - public static function param ($key, $default = false, $specialchars = false) { - if (isset (self::$params[$key])) { - $p = self::$params[$key]; - if(is_object($p) || $specialchars) { - return $p; - } elseif(is_array($p)) { - return array_map('htmlspecialchars', $p); //TODO: Should use explicit UTF-8 - } else { - return htmlspecialchars($p, ENT_NOQUOTES, 'UTF-8'); - } - } else { - return $default; - } - } - public static function defaultControllerName () { - return self::$default_controller_name; - } - public static function defaultActionName () { - return self::$default_action_name; - } - - /** - * Setteurs - */ - public static function _controllerName ($controller_name) { - self::$controller_name = $controller_name; - } - public static function _actionName ($action_name) { - self::$action_name = $action_name; - } - public static function _params ($params) { - if (!is_array($params)) { - $params = array ($params); - } - - self::$params = $params; - } - public static function _param ($key, $value = false) { - if ($value === false) { - unset (self::$params[$key]); - } else { - self::$params[$key] = $value; - } - } - - /** - * Initialise la Request - */ - public static function init () { - self::magicQuotesOff (); - } - - /** - * Retourn le nom de domaine du site - */ - public static function getDomainName () { - return $_SERVER['HTTP_HOST']; - } - - /** - * Détermine la base de l'url - * @return la base de l'url - */ - public static function getBaseUrl () { - return Configuration::baseUrl (); - } - - /** - * Récupère l'URI de la requête - * @return l'URI - */ - public static function getURI () { - if (isset ($_SERVER['REQUEST_URI'])) { - $base_url = self::getBaseUrl (); - $uri = $_SERVER['REQUEST_URI']; - - $len_base_url = strlen ($base_url); - $real_uri = substr ($uri, $len_base_url); - } else { - $real_uri = ''; - } - - return $real_uri; - } - - /** - * Relance une requête - * @param $url l'url vers laquelle est relancée la requête - * @param $redirect si vrai, force la redirection http - * > sinon, le dispatcher recharge en interne - */ - public static function forward ($url = array (), $redirect = false) { - $url = Url::checkUrl ($url); - - if ($redirect) { - header ('Location: ' . Url::display ($url, 'php')); - exit (); - } else { - self::$reseted = true; - - self::_controllerName ($url['c']); - self::_actionName ($url['a']); - self::_params (array_merge ( - self::$params, - $url['params'] - )); - } - } - - /** - * Permet de récupérer une variable de type $_GET - * @param $param nom de la variable - * @param $default valeur par défaut à attribuer à la variable - * @return $_GET[$param] - * $_GET si $param = false - * $default si $_GET[$param] n'existe pas - */ - public static function fetchGET ($param = false, $default = false) { - if ($param === false) { - return $_GET; - } elseif (isset ($_GET[$param])) { - return $_GET[$param]; - } else { - return $default; - } - } - - /** - * Permet de récupérer une variable de type $_POST - * @param $param nom de la variable - * @param $default valeur par défaut à attribuer à la variable - * @return $_POST[$param] - * $_POST si $param = false - * $default si $_POST[$param] n'existe pas - */ - public static function fetchPOST ($param = false, $default = false) { - if ($param === false) { - return $_POST; - } elseif (isset ($_POST[$param])) { - return $_POST[$param]; - } else { - return $default; - } - } - - /** - * Méthode désactivant les magic_quotes pour les variables - * $_GET - * $_POST - * $_COOKIE - */ - private static function magicQuotesOff () { - if (get_magic_quotes_gpc ()) { - $_GET = Helper::stripslashes_r ($_GET); - $_POST = Helper::stripslashes_r ($_POST); - $_COOKIE = Helper::stripslashes_r ($_COOKIE); - } - } - - public static function isPost () { - return !empty ($_POST) || !empty ($_FILES); - } -} - - diff --git a/lib/minz/Response.php b/lib/minz/Response.php deleted file mode 100644 index fcf53c5b1..000000000 --- a/lib/minz/Response.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * Response représente la requête http renvoyée à l'utilisateur - */ -class Response { - private static $header = 'HTTP/1.0 200 OK'; - private static $body = ''; - - /** - * Mets à jour le body de la Response - * @param $text le texte à incorporer dans le body - */ - public static function setBody ($text) { - self::$body = $text; - } - - /** - * Mets à jour le header de la Response - * @param $code le code HTTP, valeurs possibles - * - 200 (OK) - * - 403 (Forbidden) - * - 404 (Forbidden) - * - 500 (Forbidden) -> par défaut si $code erroné - * - 503 (Forbidden) - */ - public static function setHeader ($code) { - switch ($code) { - case 200 : - self::$header = 'HTTP/1.0 200 OK'; - break; - case 403 : - self::$header = 'HTTP/1.0 403 Forbidden'; - break; - case 404 : - self::$header = 'HTTP/1.0 404 Not Found'; - break; - case 500 : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - break; - case 503 : - self::$header = 'HTTP/1.0 503 Service Unavailable'; - break; - default : - self::$header = 'HTTP/1.0 500 Internal Server Error'; - } - } - - /** - * Envoie la Response à l'utilisateur - */ - public static function send () { - header (self::$header); - echo self::$body; - } -} diff --git a/lib/minz/Router.php b/lib/minz/Router.php deleted file mode 100755 index c5d6f5baa..000000000 --- a/lib/minz/Router.php +++ /dev/null @@ -1,209 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Router gère le routage de l'application - * Les routes sont définies dans APP_PATH.'/configuration/routes.php' - */ -class Router { - const ROUTES_PATH_NAME = '/configuration/routes.php'; - - private $routes = array (); - - /** - * Constructeur - * @exception FileNotExistException si ROUTES_PATH_NAME n'existe pas - * et que l'on utilise l'url rewriting - */ - public function __construct () { - if (Configuration::useUrlRewriting ()) { - if (file_exists (APP_PATH . self::ROUTES_PATH_NAME)) { - $routes = include ( - APP_PATH . self::ROUTES_PATH_NAME - ); - - if (!is_array ($routes)) { - $routes = array (); - } - - $this->routes = array_map ( - array ('Url', 'checkUrl'), - $routes - ); - } else { - throw new FileNotExistException ( - self::ROUTES_PATH_NAME, - MinzException::ERROR - ); - } - } - } - - /** - * Initialise le Router en déterminant le couple Controller / Action - * Mets à jour la Request - * @exception RouteNotFoundException si l'uri n'est pas présente dans - * > la table de routage - */ - public function init () { - $url = array (); - - if (Configuration::useUrlRewriting ()) { - try { - $url = $this->buildWithRewriting (); - } catch (RouteNotFoundException $e) { - throw $e; - } - } else { - $url = $this->buildWithoutRewriting (); - } - - $url['params'] = array_merge ( - $url['params'], - Request::fetchPOST () - ); - - Request::forward ($url); - } - - /** - * Retourne un tableau représentant l'url passée par la barre d'adresses - * Ne se base PAS sur la table de routage - * @return tableau représentant l'url - */ - public function buildWithoutRewriting () { - $url = array (); - - $url['c'] = Request::fetchGET ( - 'c', - Request::defaultControllerName () - ); - $url['a'] = Request::fetchGET ( - 'a', - Request::defaultActionName () - ); - $url['params'] = Request::fetchGET (); - - // post-traitement - unset ($url['params']['c']); - unset ($url['params']['a']); - - return $url; - } - - /** - * Retourne un tableau représentant l'url passée par la barre d'adresses - * Se base sur la table de routage - * @return tableau représentant l'url - * @exception RouteNotFoundException si l'uri n'est pas présente dans - * > la table de routage - */ - public function buildWithRewriting () { - $url = array (); - $uri = Request::getURI (); - $find = false; - - foreach ($this->routes as $route) { - $regex = '*^' . $route['route'] . '$*'; - if (preg_match ($regex, $uri, $matches)) { - $url['c'] = $route['controller']; - $url['a'] = $route['action']; - $url['params'] = $this->getParams ( - $route['params'], - $matches - ); - $find = true; - break; - } - } - - if (!$find && $uri != '/') { - throw new RouteNotFoundException ( - $uri, - MinzException::ERROR - ); - } - - // post-traitement - $url = Url::checkUrl ($url); - - return $url; - } - - /** - * Retourne l'uri d'une url en se basant sur la table de routage - * @param l'url sous forme de tableau - * @return l'uri formatée (string) selon une route trouvée - */ - public function printUriRewrited ($url) { - $route = $this->searchRoute ($url); - - if ($route !== false) { - return $this->replaceParams ($route, $url['params']); - } - - return ''; - } - - /** - * Recherche la route correspondante à une url - * @param l'url sous forme de tableau - * @return la route telle que spécifiée dans la table de routage, - * false si pas trouvée - */ - public function searchRoute ($url) { - foreach ($this->routes as $route) { - if ($route['controller'] == $url['c'] - && $route['action'] == $url['a']) { - // calcule la différence des tableaux de params - $params = array_flip ($route['params']); - $difference_params = array_diff_key ( - $params, - $url['params'] - ); - - // vérifie que pas de différence - // et le cas où $params est vide et pas $url['params'] - if (empty ($difference_params) - && (!empty ($params) || empty ($url['params']))) { - return $route; - } - } - } - - return false; - } - - /** - * Récupère un tableau dont - * - les clés sont définies dans $params_route - * - les valeurs sont situées dans $matches - * Le tableau $matches est décalé de +1 par rapport à $params_route - */ - private function getParams($params_route, $matches) { - $params = array (); - - for ($i = 0; $i < count ($params_route); $i++) { - $param = $params_route[$i]; - $params[$param] = $matches[$i + 1]; - } - - return $params; - } - - /** - * Remplace les éléments de la route par les valeurs contenues dans $params - */ - private function replaceParams ($route, $params_replace) { - $uri = $route['route']; - $params = array(); - foreach($route['params'] as $param) { - $uri = preg_replace('#\((.+)\)#U', $params_replace[$param], $uri, 1); - } - - return stripslashes($uri); - } -} diff --git a/lib/minz/Session.php b/lib/minz/Session.php deleted file mode 100755 index f9c9c6754..000000000 --- a/lib/minz/Session.php +++ /dev/null @@ -1,78 +0,0 @@ -<?php - -/** - * La classe Session gère la session utilisateur - * C'est un singleton - */ -class Session { - /** - * $session stocke les variables de session - */ - private static $session = array (); - - /** - * Initialise la session - */ - public static function init () { - // démarre la session - session_name (md5 (Configuration::selApplication ())); - session_start (); - - if (isset ($_SESSION)) { - self::$session = $_SESSION; - } - } - - - /** - * Permet de récupérer une variable de session - * @param $p le paramètre à récupérer - * @return la valeur de la variable de session, false si n'existe pas - */ - public static function param ($p, $default = false) { - if (isset (self::$session[$p])) { - $return = self::$session[$p]; - } else { - $return = $default; - } - - return $return; - } - - - /** - * Permet de créer ou mettre à jour une variable de session - * @param $p le paramètre à créer ou modifier - * @param $v la valeur à attribuer, false pour supprimer - */ - public static function _param ($p, $v = false) { - if ($v === false) { - unset ($_SESSION[$p]); - unset (self::$session[$p]); - } else { - $_SESSION[$p] = $v; - self::$session[$p] = $v; - - if($p == 'language') { - // reset pour remettre à jour le fichier de langue à utiliser - Translate::reset (); - } - } - } - - - /** - * Permet d'effacer une session - * @param $force si à false, n'efface pas le paramètre de langue - */ - public static function unset_session ($force = false) { - $language = self::param ('language'); - - session_unset (); - self::$session = array (); - - if (!$force) { - self::_param ('language', $language); - } - } -} diff --git a/lib/minz/dao/Model_array.php b/lib/minz/dao/Model_array.php deleted file mode 100755 index 0b9ccf071..000000000 --- a/lib/minz/dao/Model_array.php +++ /dev/null @@ -1,122 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php - */ -class Model_array extends Model_txt { - /** - * $array Le tableau php contenu dans le fichier $nameFile - */ - protected $array = array (); - - /** - * Ouvre le fichier indiqué, charge le tableau dans $array et le $nameFile - * @param $nameFile le nom du fichier à ouvrir contenant un tableau - * Remarque : $array sera obligatoirement un tableau - */ - public function __construct ($nameFile) { - parent::__construct ($nameFile); - - if (!$this->getLock ('read')) { - throw new PermissionDeniedException ($this->filename); - } else { - $this->array = include ($this->filename); - $this->releaseLock (); - - if (!is_array ($this->array)) { - $this->array = array (); - } - - $this->array = $this->decodeArray ($this->array); - } - } - - /** - * Écrit un tableau dans le fichier $nameFile - * @param $array le tableau php à enregistrer - **/ - public function writeFile ($array) { - if (!$this->getLock ('write')) { - throw new PermissionDeniedException ($this->namefile); - } else { - $this->erase (); - - $this->writeLine ('<?php'); - $this->writeLine ('return ', false); - $this->writeArray ($array); - $this->writeLine (';'); - - $this->releaseLock (); - } - } - - private function writeArray ($array, $profondeur = 0) { - $tab = ''; - for ($i = 0; $i < $profondeur; $i++) { - $tab .= "\t"; - } - $this->writeLine ('array ('); - - foreach ($array as $key => $value) { - if (is_int ($key)) { - $this->writeLine ($tab . "\t" . $key . ' => ', false); - } else { - $this->writeLine ($tab . "\t" . '\'' . $key . '\'' . ' => ', false); - } - - if (is_array ($value)) { - $this->writeArray ($value, $profondeur + 1); - $this->writeLine (','); - } else { - if (is_numeric ($value)) { - $this->writeLine ($value . ','); - } else { - $this->writeLine ('\'' . addslashes ($value) . '\','); - } - } - } - - $this->writeLine ($tab . ')', false); - } - - private function decodeArray ($array) { - $new_array = array (); - - foreach ($array as $key => $value) { - if (is_array ($value)) { - $new_array[$key] = $this->decodeArray ($value); - } else { - $new_array[$key] = stripslashes ($value); - } - } - - return $new_array; - } - - private function getLock ($type) { - if ($type == 'write') { - $lock = LOCK_EX; - } else { - $lock = LOCK_SH; - } - - $count = 1; - while (!flock ($this->file, $lock) && $count <= 50) { - $count++; - } - - if ($count >= 50) { - return false; - } else { - return true; - } - } - - private function releaseLock () { - flock ($this->file, LOCK_UN); - } -} diff --git a/lib/minz/dao/Model_pdo.php b/lib/minz/dao/Model_pdo.php deleted file mode 100755 index fa44038db..000000000 --- a/lib/minz/dao/Model_pdo.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_sql représente le modèle interragissant avec les bases de données - * Seul la connexion MySQL est prise en charge pour le moment - */ -class Model_pdo { - /** - * $bd variable représentant la base de données - */ - protected $bd; - - protected $prefix; - - /** - * Créé la connexion à la base de données à l'aide des variables - * HOST, BASE, USER et PASS définies dans le fichier de configuration - */ - public function __construct () { - $db = Configuration::dataBase (); - $driver_options = null; - - try { - $type = $db['type']; - if($type == 'mysql') { - $string = $type - . ':host=' . $db['host'] - . ';dbname=' . $db['base'] - . ';charset=utf8'; - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' - ); - } elseif($type == 'sqlite') { - $string = $type - . ':/' . PUBLIC_PATH - . '/data/' . $db['base'] . '.sqlite'; //TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797 - } - - $this->bd = new PDO ( - $string, - $db['user'], - $db['password'], - $driver_options - ); - - $this->prefix = $db['prefix']; - } catch (Exception $e) { - throw new PDOConnectionException ( - $string, - $db['user'], MinzException::WARNING - ); - } - } -} diff --git a/lib/minz/dao/Model_txt.php b/lib/minz/dao/Model_txt.php deleted file mode 100755 index c9d5cfe77..000000000 --- a/lib/minz/dao/Model_txt.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -/** - * MINZ - Copyright 2011 Marien Fressinaud - * Sous licence AGPL3 <http://www.gnu.org/licenses/> -*/ - -/** - * La classe Model_txt représente le modèle interragissant avec les fichiers de type texte - */ -class Model_txt { - /** - * $file représente le fichier à ouvrir - */ - protected $file; - - /** - * $filename est le nom du fichier - */ - protected $filename; - - /** - * Ouvre un fichier dans $file - * @param $nameFile nom du fichier à ouvrir - * @param $mode mode d'ouverture du fichier ('a+' par défaut) - * @exception FileNotExistException si le fichier n'existe pas - * > ou ne peux pas être ouvert - */ - public function __construct ($nameFile, $mode = 'a+') { - $this->filename = $nameFile; - $this->file = @fopen ($this->filename, $mode); - - if (!$this->file) { - throw new FileNotExistException ( - $this->filename, - MinzException::WARNING - ); - } - } - - /** - * Lit une ligne de $file - * @return une ligne du fichier - */ - public function readLine () { - return fgets ($this->file); - } - - /** - * Écrit une ligne dans $file - * @param $line la ligne à écrire - */ - public function writeLine ($line, $newLine = true) { - $char = ''; - if ($newLine) { - $char = "\n"; - } - - fwrite ($this->file, $line . $char); - } - - /** - * Efface le fichier $file - * @return true en cas de succès, false sinon - */ - public function erase () { - return ftruncate ($this->file, 0); - } - - /** - * Ferme $file - */ - public function __destruct () { - if (isset ($this->file)) { - fclose ($this->file); - } - } -} diff --git a/lib/minz/exceptions/MinzException.php b/lib/minz/exceptions/MinzException.php deleted file mode 100644 index 8fca5ec16..000000000 --- a/lib/minz/exceptions/MinzException.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php - -class MinzException extends Exception { - const ERROR = 0; - const WARNING = 10; - const NOTICE = 20; - - public function __construct ($message, $code = self::ERROR) { - if ($code != MinzException::ERROR - && $code != MinzException::WARNING - && $code != MinzException::NOTICE) { - $code = MinzException::ERROR; - } - - parent::__construct ($message, $code); - } -} - -class PermissionDeniedException extends MinzException { - public function __construct ($file_name, $code = self::ERROR) { - $message = 'Permission is denied for `' . $file_name.'`'; - - parent::__construct ($message, $code); - } -} -class FileNotExistException extends MinzException { - public function __construct ($file_name, $code = self::ERROR) { - $message = 'File doesn\'t exist : `' . $file_name.'`'; - - parent::__construct ($message, $code); - } -} -class BadConfigurationException extends MinzException { - public function __construct ($part_missing, $code = self::ERROR) { - $message = '`' . $part_missing - . '` in the configuration file is missing'; - - parent::__construct ($message, $code); - } -} -class ControllerNotExistException extends MinzException { - public function __construct ($controller_name, $code = self::ERROR) { - $message = 'Controller `' . $controller_name - . '` doesn\'t exist'; - - parent::__construct ($message, $code); - } -} -class ControllerNotActionControllerException extends MinzException { - public function __construct ($controller_name, $code = self::ERROR) { - $message = 'Controller `' . $controller_name - . '` isn\'t instance of ActionController'; - - parent::__construct ($message, $code); - } -} -class ActionException extends MinzException { - public function __construct ($controller_name, $action_name, $code = self::ERROR) { - $message = '`' . $action_name . '` cannot be invoked on `' - . $controller_name . '`'; - - parent::__construct ($message, $code); - } -} -class RouteNotFoundException extends MinzException { - private $route; - - public function __construct ($route, $code = self::ERROR) { - $this->route = $route; - - $message = 'Route `' . $route . '` not found'; - - parent::__construct ($message, $code); - } - - public function route () { - return $this->route; - } -} -class PDOConnectionException extends MinzException { - public function __construct ($string_connection, $user, $code = self::ERROR) { - $message = 'Access to database is denied for `' . $user . '`' - . ' (`' . $string_connection . '`)'; - - parent::__construct ($message, $code); - } -} -class CurrentPagePaginationException extends MinzException { - public function __construct ($page) { - $message = 'Page number `' . $page . '` doesn\'t exist'; - - parent::__construct ($message, self::ERROR); - } -} diff --git a/lib/password_compat.php b/lib/password_compat.php new file mode 100644 index 000000000..e8ec02885 --- /dev/null +++ b/lib/password_compat.php @@ -0,0 +1,279 @@ +<?php +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara <ircmaxell@php.net> + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + +if (!defined('PASSWORD_DEFAULT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_requires_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_requires_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = PasswordCompat\binary\_strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $bl = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_requires_encoding = true; + } + if ($salt_requires_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} + +} + +namespace PasswordCompat\binary { + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + +} diff --git a/log/.gitignore b/log/.gitignore deleted file mode 100644 index 72e8ffc0d..000000000 --- a/log/.gitignore +++ /dev/null @@ -1 +0,0 @@ -* diff --git a/p/.htaccess b/p/.htaccess new file mode 100644 index 000000000..2b1e27a88 --- /dev/null +++ b/p/.htaccess @@ -0,0 +1,44 @@ +<IfModule mod_dir.c> + DirectoryIndex index.php index.html +</IfModule> + +FileETag None +AddDefaultCharset UTF-8 + +<IfModule mod_mime.c> + AddType application/json .map + AddType application/font-woff .woff + + AddCharset UTF-8 .css + AddCharset UTF-8 .html + AddCharset UTF-8 .js + AddCharset UTF-8 .svg +</IfModule> + +<IfModule mod_deflate.c> + AddOutputFilterByType DEFLATE application/javascript application/json application/xhtml+xml image/svg+xml text/css text/html text/javascript +</IfModule> + +<IfModule mod_expires.c> + ExpiresActive on + ExpiresByType application/font-woff "access plus 1 month" + ExpiresByType application/javascript "access plus 1 month" + ExpiresByType application/json "access plus 1 month" + ExpiresByType application/xhtml+xml "access plus 1 month" + ExpiresByType image/gif "access plus 1 month" + ExpiresByType image/png "access plus 1 month" + ExpiresByType image/svg+xml "access plus 1 month" + ExpiresByType image/x-icon "access plus 1 month" + ExpiresByType text/css "access plus 1 month" + ExpiresByType text/html "access plus 1 month" + ExpiresByType text/javascript "access plus 1 month" + <FilesMatch "\.php$"> + ExpiresActive Off + </FilesMatch> +</IfModule> + +<IfModule mod_headers.c> + <FilesMatch "\.(css|html|js|ico|gif|png|woff)$"> + Header merge Cache-Control "public" + </FilesMatch> +</IfModule> diff --git a/p/Web.config b/p/Web.config new file mode 100644 index 000000000..2321c7613 --- /dev/null +++ b/p/Web.config @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration> + <system.web> + <httpRuntime executionTimeout="300" maxRequestLength="8192"/> + </system.web> + <system.webServer> + <defaultDocument> + <files> + <clear /> + <add value="index.php" /> + <add value="index.html" /> + </files> + </defaultDocument> + <urlCompression doStaticCompression="true" doDynamicCompression="true"/> + <staticContent> + <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="31.00:00:00" /> + </staticContent> + <caching> + <profiles> + <add extension=".css" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".gif" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".html" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".jpg" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".js" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".png" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".svg" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + <add extension=".woff" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" /> + </profiles> + </caching> + <!--<httpProtocol> + <customHeaders> + <add name="Cache-Control" value="public" /> + </customHeaders> + </httpProtocol>--> + </system.webServer> +</configuration> diff --git a/p/api/.htaccess b/p/api/.htaccess new file mode 100644 index 000000000..41b653d96 --- /dev/null +++ b/p/api/.htaccess @@ -0,0 +1,4 @@ +<IfModule mod_rewrite.c> + RewriteEngine on + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +</IfModule> diff --git a/p/api/greader.php b/p/api/greader.php new file mode 100644 index 000000000..5a6fdad7d --- /dev/null +++ b/p/api/greader.php @@ -0,0 +1,649 @@ +<?php +/** +== Description == +Server-side API compatible with Google Reader API layer 2 + for the FreshRSS project http://freshrss.org + +== Credits == +* 2014-03: Released by Alexandre Alapetite http://alexandre.alapetite.fr + under GNU AGPL 3 license http://www.gnu.org/licenses/agpl-3.0.html + +== Documentation == +* http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +* http://web.archive.org/web/20130718025427/http://undoc.in/ +* http://ranchero.com/downloads/GoogleReaderAPI-2009.pdf +* http://code.google.com/p/google-reader-api/w/list +* http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/ +* https://github.com/noinnion/newsplus/blob/master/extensions/GoogleReaderCloneExtension/src/com/noinnion/android/newsplus/extension/google_reader/GoogleReaderClient.java +* https://github.com/ericmann/gReader-Library/blob/master/greader.class.php +* https://github.com/devongovett/reader +* https://github.com/theoldreader/api +*/ + +require('../../constants.php'); +require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader + +$ORIGINAL_INPUT = file_get_contents('php://input'); + +if (PHP_INT_SIZE < 8) { //32-bit + function dec2hex($dec) { + return str_pad(gmp_strval(gmp_init($dec, 10), 16), 16, '0', STR_PAD_LEFT); + } + function hex2dec($hex) { + return gmp_strval(gmp_init($hex, 16), 10); + } +} else { //64-bit + function dec2hex($dec) { //http://code.google.com/p/google-reader-api/wiki/ItemId + return str_pad(dechex($dec), 16, '0', STR_PAD_LEFT); + } + function hex2dec($hex) { + return hexdec($hex); + } +} + +function headerVariable($headerName, $varName) { + $header = ''; + $upName = 'HTTP_' . strtoupper($headerName); + if (isset($_SERVER[$upName])) { + $header = $_SERVER[$upName]; + } elseif (function_exists('getallheaders')) { + $ALL_HEADERS = getallheaders(); + if (isset($ALL_HEADERS[$headerName])) { + $header = $ALL_HEADERS[$headerName]; + } + } + parse_str($header, $pairs); + return isset($pairs[$varName]) ? $pairs[$varName] : null; +} + +function multiplePosts($name) { //https://bugs.php.net/bug.php?id=51633 + global $ORIGINAL_INPUT; + $inputs = explode('&', $ORIGINAL_INPUT); + $result = array(); + $prefix = $name . '='; + $prefixLength = strlen($prefix); + foreach ($inputs as $input) { + if (strpos($input, $prefix) === 0) { + $result[] = urldecode(substr($input, $prefixLength)); + } + } + return $result; +} + +class MyPDO extends Minz_ModelPdo { + function prepare($sql) { + return $this->bd->prepare(str_replace('%_', $this->prefix, $sql)); + } +} + +function logMe($text) { + file_put_contents(LOG_PATH . '/api.log', $text, FILE_APPEND); +} + +function debugInfo() { + if (function_exists('getallheaders')) { + $ALL_HEADERS = getallheaders(); + } else { //nginx http://php.net/getallheaders#84262 + $ALL_HEADERS = ''; + foreach ($_SERVER as $name => $value) { + if (substr($name, 0, 5) === 'HTTP_') { + $ALL_HEADERS[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value; + } + } + } + global $ORIGINAL_INPUT; + return print_r(array('date' => date('c'), 'headers' => $ALL_HEADERS, '_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, '_COOKIE' => $_COOKIE, 'INPUT' => $ORIGINAL_INPUT), true); +} + +function badRequest() { + logMe("badRequest()\n"); + logMe(debugInfo()); + header('HTTP/1.1 400 Bad Request'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Bad Request!'); +} + +function unauthorized() { + logMe("unauthorized()\n"); + logMe(debugInfo()); + header('HTTP/1.1 401 Unauthorized'); + header('Content-Type: text/plain; charset=UTF-8'); + header('Google-Bad-Token: true'); + die('Unauthorized!'); +} + +function notImplemented() { + logMe("notImplemented()\n"); + logMe(debugInfo()); + header('HTTP/1.1 501 Not Implemented'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Not Implemented!'); +} + +function serviceUnavailable() { + logMe("serviceUnavailable()\n"); + header('HTTP/1.1 503 Service Unavailable'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Service Unavailable!'); +} + +function checkCompatibility() { + logMe("checkCompatibility()\n"); + header('Content-Type: text/plain; charset=UTF-8'); + if (PHP_INT_SIZE < 8 && !function_exists('gmp_init')) { + die('FAIL 64-bit or GMP extension!'); + } + if ((!array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) && //Apache mod_rewrite trick should be fine + (empty($_SERVER['SERVER_SOFTWARE']) || (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) && //nginx should be fine + (empty($_SERVER['SERVER_SOFTWARE']) || (stripos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') === false)) && //lighttpd should be fine + ((!function_exists('getallheaders')) || (stripos(php_sapi_name(), 'cgi') !== false))) { //Main problem is Apache/CGI mode + die('FAIL getallheaders! (probably)'); + } + echo 'PASS'; + exit(); +} + +function authorizationToUserConf() { + $headerAuth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external + if ($headerAuth != '') { + $headerAuthX = explode('/', $headerAuth, 2); + if (count($headerAuthX) === 2) { + $user = $headerAuthX[0]; + if (ctype_alnum($user)) { + try { + $conf = new FreshRSS_Configuration($user); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + unauthorized(); + } + if ($headerAuthX[1] === sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash)) { + return $conf; + } else { + logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n"); + Minz_Log::record('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1], Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); + } + } + } + return null; +} + +function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html + logMe('clientLogin(' . $email . ")\n"); + if (ctype_alnum($email)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($email); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + Minz_Log::record('Invalid API user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) { + header('Content-Type: text/plain; charset=UTF-8'); + $auth = $email . '/' . sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash); + echo 'SID=', $auth, "\n", + 'Auth=', $auth, "\n"; + exit(); + } else { + Minz_Log::record('Password API mismatch for user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); + } + die(); +} + +function token($conf) { +//http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ +//https://github.com/ericmann/gReader-Library/blob/master/greader.class.php + logMe('token('. $conf->user . ")\n"); //TODO: Implement real token that expires + $token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z'); //Must have 57 characters + echo $token, "\n"; + exit(); +} + +function checkToken($conf, $token) { +//http://code.google.com/p/google-reader-api/wiki/ActionToken + logMe('checkToken(' . $token . ")\n"); + if ($token === str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z')) { + return true; + } + unauthorized(); +} + +function tagList() { + logMe("tagList()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $pdo = new MyPDO(); + $stm = $pdo->prepare('SELECT c.name FROM `%_category` c'); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + + $tags = array( + array('id' => 'user/-/state/com.google/starred'), + //array('id' => 'user/-/state/com.google/broadcast', 'sortid' => '2'), + ); + + foreach ($res as $cName) { + $tags[] = array( + 'id' => 'user/-/label/' . $cName, + //'sortid' => $cName, + ); + } + + echo json_encode(array('tags' => $tags)), "\n"; + exit(); +} + +function subscriptionList() { + logMe("subscriptionList()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $pdo = new MyPDO(); + $stm = $pdo->prepare('SELECT f.id, f.name, f.url, f.website, c.id as c_id, c.name as c_name FROM `%_feed` f + INNER JOIN `%_category` c ON c.id = f.category'); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + $subscriptions = array(); + + foreach ($res as $line) { + $subscriptions[] = array( + 'id' => 'feed/' . $line['id'], + 'title' => $line['name'], + 'categories' => array( + array( + 'id' => 'user/-/label/' . $line['c_name'], + 'label' => $line['c_name'], + ), + ), + //'sortid' => $line['name'], + //'firstitemmsec' => 0, + 'url' => $line['url'], + 'htmlUrl' => $line['website'], + //'iconUrl' => '', + ); + } + + echo json_encode(array('subscriptions' => $subscriptions)), "\n"; + exit(); +} + +function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#unread-count + logMe("unreadCount()\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $totalUnreads = 0; + $totalLastUpdate = 0; + + $categoryDAO = new FreshRSS_CategoryDAO(); + foreach ($categoryDAO->listCategories(true, true) as $cat) { + $catLastUpdate = 0; + foreach ($cat->feeds() as $feed) { + $lastUpdate = $feed->lastUpdate(); + $unreadcounts[] = array( + 'id' => 'feed/' . $feed->id(), + 'count' => $feed->nbNotRead(), + 'newestItemTimestampUsec' => $lastUpdate . '000000', + ); + if ($catLastUpdate < $lastUpdate) { + $catLastUpdate = $lastUpdate; + } + } + $unreadcounts[] = array( + 'id' => 'user/-/label/' . $cat->name(), + 'count' => $cat->nbNotRead(), + 'newestItemTimestampUsec' => $catLastUpdate . '000000', + ); + $totalUnreads += $cat->nbNotRead(); + if ($totalLastUpdate < $catLastUpdate) { + $totalLastUpdate = $catLastUpdate; + } + } + + $unreadcounts[] = array( + 'id' => 'user/-/state/com.google/reading-list', + 'count' => $totalUnreads, + 'newestItemTimestampUsec' => $totalLastUpdate . '000000', + ); + + echo json_encode(array( + 'max' => $totalUnreads, + 'unreadcounts' => $unreadcounts, + )), "\n"; + exit(); +} + +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + logMe("streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation)\n"); + header('Content-Type: application/json; charset=UTF-8'); + + $feedDAO = FreshRSS_Factory::createFeedDao(); + $arrayFeedCategoryNames = $feedDAO->arrayFeedCategoryNames(); + + switch ($path) { + case 'reading-list': + $type = 'A'; + break; + case 'starred': + $type = 's'; + break; + case 'feed': + $type = 'f'; + break; + case 'label': + $type = 'c'; + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($include_target); + $include_target = $cat == null ? -1 : $cat->id(); + break; + default: + $type = 'A'; + break; + } + + switch ($exclude_target) { + case 'user/-/state/com.google/read': + $state = FreshRSS_Entry::STATE_NOT_READ; + break; + default: + $state = FreshRSS_Entry::STATE_ALL; + break; + } + + if (!empty($continuation)) { + $count++; //Shift by one element + } + + $entryDAO = FreshRSS_Factory::createEntryDao(); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, '', $start_time); + + $items = array(); + foreach ($entries as $entry) { + $f_id = $entry->feed(); + if (isset($arrayFeedCategoryNames[$f_id])) { + $c_name = $arrayFeedCategoryNames[$f_id]['c_name']; + $f_name = $arrayFeedCategoryNames[$f_id]['name']; + } else { + $c_name = '_'; + $f_name = '_'; + } + $item = array( + 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId + 'crawlTimeMsec' => substr($entry->id(), 0, -3), + 'timestampUsec' => $entry->id(), //EasyRSS + 'published' => $entry->date(true), + 'title' => $entry->title(), + 'summary' => array('content' => $entry->content()), + 'alternate' => array( + array('href' => $entry->link()), + ), + 'categories' => array( + 'user/-/state/com.google/reading-list', + 'user/-/label/' . $c_name, + ), + 'origin' => array( + 'streamId' => 'feed/' . $f_id, + 'title' => $f_name, //EasyRSS + //'htmlUrl' => $line['f_website'], + ), + ); + if ($entry->author() != '') { + $item['author'] = $entry->author(); + } + if ($entry->isRead()) { + $item['categories'][] = 'user/-/state/com.google/read'; + } + if ($entry->isFavorite()) { + $item['categories'][] = 'user/-/state/com.google/starred'; + } + $items[] = $item; + } + + if (!empty($continuation)) { + array_shift($items); //Discard first element that was already sent in the previous response + } + + $response = array( + 'id' => 'user/-/state/com.google/reading-list', + 'updated' => time(), + 'items' => $items, + ); + if ((count($entries) >= $count) && (!empty($entry))) { + $response['continuation'] = $entry->id(); + } + + echo json_encode($response), "\n"; + exit(); +} + +function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) { +//http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + logMe("streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target)\n"); + + $type = 'A'; + $id = ''; + if ($streamId === 'user/-/state/com.google/reading-list') { + $type = 'A'; + } elseif ('user/-/state/com.google/starred') { + $type = 's'; + } elseif (strpos($streamId, 'feed/') === 0) { + $type = 'f'; + $id = basename($streamId); + } elseif (strpos($streamId, 'user/-/label/') === 0) { + $type = 'c'; + $c_name = basename($streamId); + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($c_name); + $id = $cat == null ? -1 : $cat->id(); + } + + switch ($exclude_target) { + case 'user/-/state/com.google/read': + $state = FreshRSS_Entry::STATE_NOT_READ; + break; + default: + $state = FreshRSS_Entry::STATE_ALL; + break; + } + + $entryDAO = FreshRSS_Factory::createEntryDao(); + $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + + $itemRefs = array(); + foreach ($ids as $id) { + $itemRefs[] = array( + 'id' => $id, //64-bit decimal + ); + } + + echo json_encode(array( + 'itemRefs' => $itemRefs, + )), "\n"; + exit(); +} + +function editTag($e_ids, $a, $r) { + logMe("editTag()\n"); + + foreach ($e_ids as $i => $e_id) { + $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + + $entryDAO = FreshRSS_Factory::createEntryDao(); + + switch ($a) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_ids, true); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_ids, true); + break; + /*case 'user/-/state/com.google/tracking-kept-unread': + break; + case 'user/-/state/com.google/like': + break; + case 'user/-/state/com.google/broadcast': + break;*/ + } + switch ($r) { + case 'user/-/state/com.google/read': + $entryDAO->markRead($e_ids, false); + break; + case 'user/-/state/com.google/starred': + $entryDAO->markFavorite($e_ids, false); + break; + } + + echo 'OK'; + exit(); +} + +function markAllAsRead($streamId, $olderThanId) { + logMe("markAllAsRead($streamId, $olderThanId)\n"); + $entryDAO = FreshRSS_Factory::createEntryDao(); + if (strpos($streamId, 'feed/') === 0) { + $f_id = basename($streamId); + $entryDAO->markReadFeed($f_id, $olderThanId); + } elseif (strpos($streamId, 'user/-/label/') === 0) { + $c_name = basename($streamId); + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($c_name); + $entryDAO->markReadCat($cat === null ? -1 : $cat->id(), $olderThanId); + } elseif ($streamId === 'user/-/state/com.google/reading-list') { + $entryDAO->markReadEntries($olderThanId, false, -1); + } + + echo 'OK'; + exit(); +} + +logMe('----------------------------------------------------------------'."\n"); +//logMe(debugInfo()); + +$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']); +$pathInfos = explode('/', $pathInfo); + +Minz_Configuration::init(); + +if (!Minz_Configuration::apiEnabled()) { + serviceUnavailable(); +} + +Minz_Session::init('FreshRSS'); + +$conf = authorizationToUserConf(); +$user = $conf == null ? '' : $conf->user; + +logMe('User => ' . $user . "\n"); + +Minz_Session::_param('currentUser', $user); + +if (count($pathInfos) < 3) { + badRequest(); +} +elseif ($pathInfos[1] === 'accounts') { + if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) { + clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); + } +} +elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfos[3]) && $pathInfos[3] === '0' && isset($pathInfos[4])) { + if ($user == '') { + unauthorized(); + } + $timestamp = isset($_GET['ck']) ? intval($_GET['ck']) : 0; //ck=[unix timestamp] : Use the current Unix time here, helps Google with caching. + switch ($pathInfos[4]) { + case 'stream': + $exclude_target = isset($_GET['xt']) ? $_GET['xt'] : ''; //xt=[exclude target] : Used to exclude certain items from the feed. For example, using xt=user/-/state/com.google/read will exclude items that the current user has marked as read, or xt=feed/[feedurl] will exclude items from a particular feed (obviously not useful in this request, but xt appears in other listing requests). + $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. + $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. + $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; //ot=[unix timestamp] : The time from which you want to retrieve items. Only items that have been crawled by Google Reader after this time will be returned. + $continuation = isset($_GET['c']) ? $_GET['c'] : ''; //Continuation token. If a StreamContents response does not represent all items in a timestamp range, it will have a continuation attribute. The same request can be re-issued with the value of that attribute put in this parameter to get more items + if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6])) { + if (isset($pathInfos[7])) { + if ($pathInfos[6] === 'feed') { + $include_target = $pathInfos[7]; + StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { + if ($pathInfos[8] === 'state') { + if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { + if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { + $include_target = ''; + streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + } + } + } elseif ($pathInfos[8] === 'label') { + $include_target = $pathInfos[9]; + streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + } + } + } else { //EasyRSS + $include_target = ''; + streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target, $continuation); + } + } elseif ($pathInfos[5] === 'items') { + if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { + $streamId = $_GET['s']; //StreamId for which to fetch the item IDs. The parameter may be repeated to fetch the item IDs from multiple streams at once (more efficient from a backend perspective than multiple requests). + streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target); + } + } + break; + case 'tag': + if (isset($pathInfos[5]) && $pathInfos[5] === 'list') { + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + tagList($_GET['output']); + } + break; + case 'subscription': + if (isset($pathInfos[5]) && $pathInfos[5] === 'list') { + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + subscriptionList($_GET['output']); + } + break; + case 'unread-count': + $output = isset($_GET['output']) ? $_GET['output'] : ''; + if ($output !== 'json') notImplemented(); + $all = isset($_GET['all']) ? $_GET['all'] : ''; + unreadCount($all); + break; + case 'edit-tag': //http://blog.martindoms.com/2010/01/20/using-the-google-reader-api-part-3/ + $token = isset($_POST['T']) ? trim($_POST['T']) : ''; + checkToken($conf, $token); + $a = isset($_POST['a']) ? $_POST['a'] : ''; //Add: user/-/state/com.google/read user/-/state/com.google/starred + $r = isset($_POST['r']) ? $_POST['r'] : ''; //Remove: user/-/state/com.google/read user/-/state/com.google/starred + $e_ids = multiplePosts('i'); //item IDs + editTag($e_ids, $a, $r); + break; + case 'mark-all-as-read': + $token = isset($_POST['T']) ? trim($_POST['T']) : ''; + checkToken($conf, $token); + $streamId = $_POST['s']; //StreamId + $ts = isset($_POST['ts']) ? $_POST['ts'] : '0'; //Older than timestamp in nanoseconds + if (!ctype_digit($ts)) { + $ts = '0'; + } + markAllAsRead($streamId, $ts); + break; + case 'token': + Token($conf); + break; + } +} elseif ($pathInfos[1] === 'check' && $pathInfos[2] === 'compatibility') { + checkCompatibility(); +} + +badRequest(); diff --git a/p/api/index.html b/p/api/index.html new file mode 100644 index 000000000..8da0bcb58 --- /dev/null +++ b/p/api/index.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB"> +<head> +<meta charset="UTF-8" /> +<title>FreshRSS API</title> +<meta name="robots" content="noindex" /> +<link rel="start" href="../i/" /> +</head> + +<body> +<h1>FreshRSS API</h1> + +<h2>Google Reader compatible API</h2> +<ul> +<li><a href="greader.php/check%2Fcompatibility" rel="nofollow">Check full server configuration (with <code>%2F</code> support)</a></li> +<li><a href="greader.php/check/compatibility" rel="nofollow">Check partial server configuration (without <code>%2F</code> support)</a></li> +</ul> + +</body> +</html> diff --git a/p/f.php b/p/f.php new file mode 100644 index 000000000..5db9ab213 --- /dev/null +++ b/p/f.php @@ -0,0 +1,64 @@ +<?php +require('../constants.php'); +$favicons_dir = DATA_PATH . '/favicons/'; + +/* Télécharge le favicon d'un site et le place sur le serveur */ +function download_favicon ($website, $dest) { + $ok = false; + $url = 'http://g.etfv.co/' . $website; + + $c = curl_init ($url); + curl_setopt ($c, CURLOPT_HEADER, false); + curl_setopt ($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt ($c, CURLOPT_BINARYTRANSFER, true); + $imgRaw = curl_exec ($c); + + if (curl_getinfo ($c, CURLINFO_HTTP_CODE) == 200) { + $file = fopen ($dest, 'w'); + if ($file !== false) { + fwrite ($file, $imgRaw); + fclose ($file); + $ok = true; + } + } + curl_close ($c); + if (!$ok) { + header('Location: ' . $url); + return false; + } + return true; +} + +$id = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '0'; + +if (!ctype_xdigit($id)) { + $id = '0'; +} + +$txt = $favicons_dir . $id . '.txt'; +$ico = $favicons_dir . $id . '.ico'; + +$icoMTime = @filemtime($ico); +$txtMTime = @filemtime($txt); + +if (($icoMTime == false) || ($txtMTime > $icoMTime)) { + if ($txtMTime == false) { + header('HTTP/1.1 404 Not Found'); + header('Content-Type: image/gif'); + readfile(PUBLIC_PATH . '/themes/icons/grey.gif'); //TODO: Better 404 favicon + die(); + } + $url = file_get_contents($txt); + if (!download_favicon($url, $ico)) { + die(); + } +} + +require(LIB_PATH . '/http-conditional.php'); + +header('Content-Type: image/x-icon'); +header('Content-Disposition: inline; filename="' . $id . '.ico"'); + +if (!httpConditional($icoMTime, 2592000, 2)) { + readfile($ico); +} diff --git a/p/favicon.ico b/p/favicon.ico Binary files differnew file mode 100644 index 000000000..5d571cc82 --- /dev/null +++ b/p/favicon.ico diff --git a/p/i/.gitignore b/p/i/.gitignore new file mode 100644 index 000000000..03c88fd7a --- /dev/null +++ b/p/i/.gitignore @@ -0,0 +1 @@ +.htaccess diff --git a/p/i/index.php b/p/i/index.php new file mode 100755 index 000000000..7b34eefd1 --- /dev/null +++ b/p/i/index.php @@ -0,0 +1,52 @@ +<?php +# ***** BEGIN LICENSE BLOCK ***** +# MINZ - A free PHP framework +# Copyright (C) 2011 Marien Fressinaud +# +# 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/>. +# +# ***** END LICENSE BLOCK ***** + +require('../../constants.php'); +require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader + +if (file_exists(DATA_PATH . '/do-install.txt')) { + require(APP_PATH . '/install.php'); +} else { + session_cache_limiter(''); + Minz_Session::init('FreshRSS'); + Minz_Session::_param('keepAlive', 1); //For Persona + + if (!file_exists(DATA_PATH . '/no-cache.txt')) { + require(LIB_PATH . '/http-conditional.php'); + $currentUser = Minz_Session::param('currentUser', ''); + $dateLastModification = $currentUser === '' ? time() : max( + @filemtime(LOG_PATH . '/' . $currentUser . '.log'), + @filemtime(DATA_PATH . '/config.php') + ); + if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) { + exit(); //No need to send anything + } + } + + try { + $front_controller = new FreshRSS(); + $front_controller->init(); + $front_controller->run(); + } catch (Exception $e) { + echo '### Fatal error! ###<br />', "\n"; + Minz_Log::record($e->getMessage(), Minz_Log::ERROR); + echo 'See logs files.'; + } +} diff --git a/p/index.html b/p/index.html new file mode 100644 index 000000000..260f437bd --- /dev/null +++ b/p/index.html @@ -0,0 +1,35 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB"> +<head> +<meta charset="UTF-8" /> +<meta name="viewport" content="initial-scale=1.0" /> +<meta http-equiv="Refresh" content="0; url=i/" /> +<title>FreshRSS</title> +<link rel="shortcut icon" type="image/x-icon" sizes="16x16 64x64" href="favicon.ico" /> +<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="themes/icons/favicon-256.png" /> +<meta name="msapplication-TileColor" content="#FFF" /> +<meta name="robots" content="noindex,nofollow" /> +<style> +body { + font-family: sans-serif; + text-align: center; +} +h1 { + font-size: xx-large; + text-shadow: 1px -1px 0 #CCCCCC; +} +h1 a { + color: #0062BE; + text-decoration: none; +} +img { + border: 0; +} +</style> +</head> + +<body> +<h1><a href="i/">FreshRSS</a></h1> +<p><a href="i/"><img class="logo" width="25%" src="themes/icons/icon.svg" alt="⊚" /></a></p> +</body> +</html> diff --git a/p/robots.txt b/p/robots.txt new file mode 100644 index 000000000..1f53798bb --- /dev/null +++ b/p/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/p/scripts/bcrypt.min.js b/p/scripts/bcrypt.min.js new file mode 100644 index 000000000..6892caddc --- /dev/null +++ b/p/scripts/bcrypt.min.js @@ -0,0 +1,41 @@ +/* + bcrypt.js (c) 2013 Daniel Wirtz <dcode@dcode.io> + Released under the Apache License, Version 2.0 + see: https://github.com/dcodeIO/bcrypt.js for details +*/ +function p(n){throw n;}var q=null; +(function(n){function u(c,a,b,f){for(var d,e=c[a],l=c[a+1],e=e^b[0],h=0;14>=h;)d=f[e>>24&255],d+=f[256|e>>16&255],d^=f[512|e>>8&255],d+=f[768|e&255],l^=d^b[++h],d=f[l>>24&255],d+=f[256|l>>16&255],d^=f[512|l>>8&255],d+=f[768|l&255],e^=d^b[++h];c[a]=l^b[17];c[a+1]=e;return c}function s(c,a){var b,f=0;for(b=0;4>b;b++)f=f<<8|c[a]&255,a=(a+1)%c.length;return{key:f,a:a}}function y(c,a,b){for(var f=0,d=[0,0],e=a.length,l=b.length,h=0;h<e;h++){var g=s(c,f),f=g.a;a[h]^=g.key}for(h=0;h<e;h+=2)d=u(d,0,a,b), +a[h]=d[0],a[h+1]=d[1];for(h=0;h<l;h+=2)d=u(d,0,a,b),b[h]=d[0],b[h+1]=d[1]}function C(c,a,b,f){for(var d=0,e=[0,0],l=b.length,h=f.length,g,k=0;k<l;k++)g=s(a,d),d=g.a,b[k]^=g.key;for(k=d=0;k<l;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),b[k]=e[0],b[k+1]=e[1];for(k=0;k<h;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),f[k]=e[0],f[k+1]=e[1]}function z(c){"undefined"!==typeof process&&"function"===typeof process.nextTick?process.nextTick(c):setTimeout(c, +0)}function A(c,a,b,f){function d(){if(k<b)for(var n=new Date;k<b&&!(k+=1,y(c,h,g),y(a,h,g),100<Date.now()-n););else{for(k=0;64>k;k++)for(m=0;m<l>>1;m++)u(e,m<<1,h,g);n=[];for(k=0;k<l;k++)n.push((e[k]>>24&255)>>>0),n.push((e[k]>>16&255)>>>0),n.push((e[k]>>8&255)>>>0),n.push((e[k]&255)>>>0);return f?(f(q,n),q):n}f&&z(d);return q}var e=B.slice(),l=e.length;(4>b||31<b)&&p(Error("Illegal number of rounds: "+b));16!=a.length&&p(Error("Illegal salt length: "+a.length+" != 16"));b=1<<b;var h=D.slice(),g= +E.slice();C(a,c,h,g);var k=0,m;if("undefined"!==typeof f)return d(),q;for(var n;;)if((n=d())!==q)return n}function F(c){for(var a,b,f=[],d=0;d<c.length;d++){a=c.charCodeAt(d);b=[];do b.push(a&255),a>>=8;while(a);f=f.concat(b.reverse())}return f}function w(c,a,b){function f(a){var b=[];b.push("$2");"a"<=d&&b.push(d);b.push("$");10>l&&b.push("0");b.push(l.toString());b.push("$");b.push(v.b(h,h.length));b.push(v.b(a,4*B.length-1));return b.join("")}var d,e;("$"!=a.charAt(0)||"2"!=a.charAt(1))&&p(Error("Invalid salt version: "+ +a.substring(0,2)));"$"==a.charAt(2)?(d=String.fromCharCode(0),e=3):(d=a.charAt(2),("a"!=d||"$"!=a.charAt(3))&&p(Error("Invalid salt revision: "+a.substring(2,4))),e=4);"$"<a.charAt(e+2)&&p(Error("Missing salt rounds"));var l=10*parseInt(a.substring(e,e+1),10)+parseInt(a.substring(e+1,e+2),10);a=a.substring(e+3,e+25);c=F(c+("a"<=d?"\x00":""));var h=[],h=v.c(a,16);if("undefined"==typeof b)return f(A(c,h,l));A(c,h,l,function(a,c){a?b(a,q):b(q,f(c))});return q}function G(){if("undefined"!==typeof module&& +module.exports)return require("crypto").randomBytes(16);var c=new Uint32Array(16);n.crypto&&"function"===typeof n.crypto.getRandomValues?n.crypto.getRandomValues(c):"function"===typeof x?x(c):p(Error("Failed to generate random values: Web Crypto API not available / no polyfill set"));return Array.prototype.slice.call(c)}var t="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),r=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],v={b:function(c,a){var b=0,f=[],d,e;for((0>=a||a>c.length)&&p(Error("Invalid 'len': "+a));b<a;){d=c[b++]&255;f.push(t[d>>2&63]);d=(d&3)<<4;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>4&15;f.push(t[d&63]);d=(e&15)<< +2;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>6&3;f.push(t[d&63]);f.push(t[e&63])}return f.join("")},c:function(c,a){var b=0,f=c.length,d=0,e=[],l,h,g;for(0>=a&&p(Error("Illegal 'len': "+a));b<f-1&&d<a;){g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;if(-1==l||-1==h)break;g=l<<2>>>0;g|=(h&48)>>4;e.push(String.fromCharCode(g));if(++d>=a||b>=f)break;g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;if(-1==l)break;g=(h&15)<<4>>>0;g|=(l&60)>>2;e.push(String.fromCharCode(g)); +if(++d>=a||b>=f)break;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;g=(l&3)<<6>>>0;g|=h;e.push(String.fromCharCode(g));++d}f=[];for(b=0;b<d;b++)f.push(e[b].charCodeAt(0));return f}},m={},D=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],E=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374, +1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131, +1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968, +3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899, +3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193, +3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195, +705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060, +2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842, +3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102, +3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641, +2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569, +3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037, +2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547, +1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079, +4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015, +3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264, +448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278, +3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729, +2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361, +1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417, +2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597, +3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138, +3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],B=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],x=q;m.setRandomPolyfill=function(c){x=c};m.genSaltSync=function(c){c||(c=10);var a;c=c||10;(4>c||31<c)&&p(Error("Illegal number of rounds: "+c));var b=[]; +b.push("$2a$");10>c&&b.push("0");b.push(c.toString());b.push("$");try{b.push(v.b(G(),16)),a=b.join("")}catch(f){p(f)}return a};m.genSalt=function(c,a,b){"function"==typeof a&&(b=a,a=-1);var f;"function"==typeof c?(b=c,f=10):f=parseInt(c,10);"function"!=typeof b&&p(Error("Illegal or missing 'callback': "+b));z(function(){try{var a=m.genSaltSync(f);b(q,a)}catch(c){b(c,q)}})};m.hashSync=function(c,a){a||(a=10);"number"==typeof a&&(a=m.genSaltSync(a));return w(c,a)};m.hash=function(c,a,b){"function"!= +typeof b&&p(Error("Illegal 'callback': "+b));"number"==typeof a?m.genSalt(a,function(a,d){w(c,d,b)}):w(c,a,b)};m.compareSync=function(c,a){("string"!=typeof c||"string"!=typeof a)&&p(Error("Illegal argument types: "+typeof c+", "+typeof a));60!=a.length&&p(Error("Illegal hash length: "+a.length+" != 60"));for(var b=m.hashSync(c,a.substr(0,a.length-31)),f=b.length==a.length,d=b.length<a.length?b.length:a.length,e=0;e<d;++e)b.length>=e&&(a.length>=e&&b[e]!=a[e])&&(f=!1);return f};m.compare=function(c, +a,b){"function"!=typeof b&&p(Error("Illegal 'callback': "+b));m.hash(c,a.substr(0,29),function(c,d){b(c,a===d)})};m.getRounds=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));return parseInt(c.split("$")[2],10)};m.getSalt=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));60!=c.length&&p(Error("Illegal hash length: "+c.length+" != 60"));return c.substring(0,29)};"undefined"!=typeof module&&module.exports?module.exports=m:"undefined"!=typeof define&& +define.amd?define("bcrypt",function(){return m}):(n.dcodeIO||(n.dcodeIO={}),n.dcodeIO.bcrypt=m)})(this); diff --git a/p/scripts/flotr2.min.js b/p/scripts/flotr2.min.js new file mode 100644 index 000000000..c3601e827 --- /dev/null +++ b/p/scripts/flotr2.min.js @@ -0,0 +1,27 @@ +/*! + * bean.js - copyright Jacob Thornton 2011 + * https://github.com/fat/bean + * MIT License + * special thanks to: + * dean edwards: http://dean.edwards.name/ + * dperini: https://github.com/dperini/nwevents + * the entire mootools team: github.com/mootools/mootools-core + *//*global module:true, define:true*/ +!function(e,t,n){typeof module!="undefined"?module.exports=n(e,t):typeof define=="function"&&typeof define.amd=="object"?define(n):t[e]=n(e,t)}("bean",this,function(e,t){var n=window,r=t[e],i=/over|out/,s=/[^\.]*(?=\..*)\.|.*/,o=/\..*/,u="addEventListener",a="attachEvent",f="removeEventListener",l="detachEvent",c=document||{},h=c.documentElement||{},p=h[u],d=p?u:a,v=Array.prototype.slice,m=/click|mouse|menu|drag|drop/i,g=/^touch|^gesture/i,y={one:1},b=function(e,t,n){for(n=0;n<t.length;n++)e[t[n]]=1;return e}({},("click dblclick mouseup mousedown contextmenu mousewheel DOMMouseScroll mouseover mouseout mousemove selectstart selectend keydown keypress keyup orientationchange focus blur change reset select submit load unload beforeunload resize move DOMContentLoaded readystatechange error abort scroll "+(p?"show input invalid touchstart touchmove touchend touchcancel gesturestart gesturechange gestureend message readystatechange pageshow pagehide popstate hashchange offline online afterprint beforeprint dragstart dragenter dragover dragleave drag drop dragend loadstart progress suspend emptied stalled loadmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate play pause ratechange volumechange cuechange checking noupdate downloading cached updateready obsolete ":"")).split(" ")),w=function(){function e(e,t){while((t=t.parentNode)!==null)if(t===e)return!0;return!1}function t(t){var n=t.relatedTarget;return n?n!==this&&n.prefix!=="xul"&&!/document/.test(this.toString())&&!e(this,n):n===null}return{mouseenter:{base:"mouseover",condition:t},mouseleave:{base:"mouseout",condition:t},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}}}(),E=function(){var e="altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which".split(" "),t=e.concat("button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" ")),n=e.concat("char charCode key keyCode".split(" ")),r=e.concat("touches targetTouches changedTouches scale rotation".split(" ")),s="preventDefault",o=function(e){return function(){e[s]?e[s]():e.returnValue=!1}},u="stopPropagation",a=function(e){return function(){e[u]?e[u]():e.cancelBubble=!0}},f=function(e){return function(){e[s](),e[u](),e.stopped=!0}},l=function(e,t,n){var r,i;for(r=n.length;r--;)i=n[r],!(i in t)&&i in e&&(t[i]=e[i])};return function(p,d){var v={originalEvent:p,isNative:d};if(!p)return v;var y,b=p.type,w=p.target||p.srcElement;v[s]=o(p),v[u]=a(p),v.stop=f(v),v.target=w&&w.nodeType===3?w.parentNode:w;if(d){if(b.indexOf("key")!==-1)y=n,v.keyCode=p.which||p.keyCode;else if(m.test(b)){y=t,v.rightClick=p.which===3||p.button===2,v.pos={x:0,y:0};if(p.pageX||p.pageY)v.clientX=p.pageX,v.clientY=p.pageY;else if(p.clientX||p.clientY)v.clientX=p.clientX+c.body.scrollLeft+h.scrollLeft,v.clientY=p.clientY+c.body.scrollTop+h.scrollTop;i.test(b)&&(v.relatedTarget=p.relatedTarget||p[(b==="mouseover"?"from":"to")+"Element"])}else g.test(b)&&(y=r);l(p,v,y||e)}return v}}(),S=function(e,t){return!p&&!t&&(e===c||e===n)?h:e},x=function(){function e(e,t,n,r,i){this.element=e,this.type=t,this.handler=n,this.original=r,this.namespaces=i,this.custom=w[t],this.isNative=b[t]&&e[d],this.eventType=p||this.isNative?t:"propertychange",this.customType=!p&&!this.isNative&&t,this.target=S(e,this.isNative),this.eventSupport=this.target[d]}return e.prototype={inNamespaces:function(e){var t,n;if(!e)return!0;if(!this.namespaces)return!1;for(t=e.length;t--;)for(n=this.namespaces.length;n--;)if(e[t]===this.namespaces[n])return!0;return!1},matches:function(e,t,n){return this.element===e&&(!t||this.original===t)&&(!n||this.handler===n)}},e}(),T=function(){var e={},t=function(n,r,i,s,o){if(!r||r==="*")for(var u in e)u.charAt(0)==="$"&&t(n,u.substr(1),i,s,o);else{var a=0,f,l=e["$"+r],c=n==="*";if(!l)return;for(f=l.length;a<f;a++)if(c||l[a].matches(n,i,s))if(!o(l[a],l,a,r))return}},n=function(t,n,r){var i,s=e["$"+n];if(s)for(i=s.length;i--;)if(s[i].matches(t,r,null))return!0;return!1},r=function(e,n,r){var i=[];return t(e,n,r,null,function(e){return i.push(e)}),i},i=function(t){return(e["$"+t.type]||(e["$"+t.type]=[])).push(t),t},s=function(n){t(n.element,n.type,null,n.handler,function(t,n,r){return n.splice(r,1),n.length===0&&delete e["$"+t.type],!1})},o=function(){var t,n=[];for(t in e)t.charAt(0)==="$"&&(n=n.concat(e[t]));return n};return{has:n,get:r,put:i,del:s,entries:o}}(),N=p?function(e,t,n,r){e[r?u:f](t,n,!1)}:function(e,t,n,r,i){i&&r&&e["_on"+i]===null&&(e["_on"+i]=0),e[r?a:l]("on"+t,n)},C=function(e,t,r){return function(i){return i=E(i||((this.ownerDocument||this.document||this).parentWindow||n).event,!0),t.apply(e,[i].concat(r))}},k=function(e,t,r,i,s,o){return function(u){if(i?i.apply(this,arguments):p?!0:u&&u.propertyName==="_on"+r||!u)u&&(u=E(u||((this.ownerDocument||this.document||this).parentWindow||n).event,o)),t.apply(e,u&&(!s||s.length===0)?arguments:v.call(arguments,u?0:1).concat(s))}},L=function(e,t,n,r,i){return function(){e(t,n,i),r.apply(this,arguments)}},A=function(e,t,n,r){var i,s,u,a=t&&t.replace(o,""),f=T.get(e,a,n);for(i=0,s=f.length;i<s;i++)f[i].inNamespaces(r)&&((u=f[i]).eventSupport&&N(u.target,u.eventType,u.handler,!1,u.type),T.del(u))},O=function(e,t,n,r,i){var u,a=t.replace(o,""),f=t.replace(s,"").split(".");if(T.has(e,a,n))return e;a==="unload"&&(n=L(A,e,a,n,r)),w[a]&&(w[a].condition&&(n=k(e,n,a,w[a].condition,!0)),a=w[a].base||a),u=T.put(new x(e,a,n,r,f[0]&&f)),u.handler=u.isNative?C(e,u.handler,i):k(e,u.handler,a,!1,i,!1),u.eventSupport&&N(u.target,u.eventType,u.handler,!0,u.customType)},M=function(e,t,n){return function(r){var i,s,o=typeof e=="string"?n(e,this):e;for(i=r.target;i&&i!==this;i=i.parentNode)for(s=o.length;s--;)if(o[s]===i)return t.apply(i,arguments)}},_=function(e,t,n){var r,i,u,a,f,l=A,c=t&&typeof t=="string";if(c&&t.indexOf(" ")>0){t=t.split(" ");for(f=t.length;f--;)_(e,t[f],n);return e}u=c&&t.replace(o,""),u&&w[u]&&(u=w[u].type);if(!t||c){if(a=c&&t.replace(s,""))a=a.split(".");l(e,u,n,a)}else if(typeof t=="function")l(e,null,t);else for(r in t)t.hasOwnProperty(r)&&_(e,r,t[r]);return e},D=function(e,t,n,r,i){var s,o,u,a,f=n,l=n&&typeof n=="string";if(t&&!n&&typeof t=="object")for(s in t)t.hasOwnProperty(s)&&D.apply(this,[e,s,t[s]]);else{a=arguments.length>3?v.call(arguments,3):[],o=(l?n:t).split(" "),l&&(n=M(t,f=r,i))&&(a=v.call(a,1)),this===y&&(n=L(_,e,t,n,f));for(u=o.length;u--;)O(e,o[u],n,f,a)}return e},P=function(){return D.apply(y,arguments)},H=p?function(e,t,r){var i=c.createEvent(e?"HTMLEvents":"UIEvents");i[e?"initEvent":"initUIEvent"](t,!0,!0,n,1),r.dispatchEvent(i)}:function(e,t,n){n=S(n,e),e?n.fireEvent("on"+t,c.createEventObject()):n["_on"+t]++},B=function(e,t,n){var r,i,u,a,f,l=t.split(" ");for(r=l.length;r--;){t=l[r].replace(o,"");if(a=l[r].replace(s,""))a=a.split(".");if(!a&&!n&&e[d])H(b[t],t,e);else{f=T.get(e,t),n=[!1].concat(n);for(i=0,u=f.length;i<u;i++)f[i].inNamespaces(a)&&f[i].handler.apply(e,n)}}return e},j=function(e,t,n){var r=0,i=T.get(t,n),s=i.length;for(;r<s;r++)i[r].original&&D(e,i[r].type,i[r].original);return e},F={add:D,one:P,remove:_,clone:j,fire:B,noConflict:function(){return t[e]=r,this}};if(n[a]){var I=function(){var e,t=T.entries();for(e in t)t[e].type&&t[e].type!=="unload"&&_(t[e].element,t[e].type);n[l]("onunload",I),n.CollectGarbage&&n.CollectGarbage()};n[a]("onunload",I)}return F}); +// Underscore.js 1.1.7 +// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore is freely distributable under the MIT license. +// Portions of Underscore are inspired or borrowed from Prototype, +// Oliver Steele's Functional, and John Resig's Micro-Templating. +// For all details and documentation: +// http://documentcloud.github.com/underscore + +(function(){var e=this,t=e._,n={},r=Array.prototype,i=Object.prototype,s=Function.prototype,o=r.slice,u=r.unshift,a=i.toString,f=i.hasOwnProperty,l=r.forEach,c=r.map,h=r.reduce,p=r.reduceRight,d=r.filter,v=r.every,m=r.some,g=r.indexOf,y=r.lastIndexOf,b=Array.isArray,w=Object.keys,E=s.bind,S=function(e){return new k(e)};typeof module!="undefined"&&module.exports?(module.exports=S,S._=S):e._=S,S.VERSION="1.1.7";var x=S.each=S.forEach=function(e,t,r){if(e==null)return;if(l&&e.forEach===l)e.forEach(t,r);else if(e.length===+e.length){for(var i=0,s=e.length;i<s;i++)if(i in e&&t.call(r,e[i],i,e)===n)return}else for(var o in e)if(f.call(e,o)&&t.call(r,e[o],o,e)===n)return};S.map=function(e,t,n){var r=[];return e==null?r:c&&e.map===c?e.map(t,n):(x(e,function(e,i,s){r[r.length]=t.call(n,e,i,s)}),r)},S.reduce=S.foldl=S.inject=function(e,t,n,r){var i=n!==void 0;e==null&&(e=[]);if(h&&e.reduce===h)return r&&(t=S.bind(t,r)),i?e.reduce(t,n):e.reduce(t);x(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError("Reduce of empty array with no initial value");return n},S.reduceRight=S.foldr=function(e,t,n,r){e==null&&(e=[]);if(p&&e.reduceRight===p)return r&&(t=S.bind(t,r)),n!==void 0?e.reduceRight(t,n):e.reduceRight(t);var i=(S.isArray(e)?e.slice():S.toArray(e)).reverse();return S.reduce(i,t,n,r)},S.find=S.detect=function(e,t,n){var r;return T(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},S.filter=S.select=function(e,t,n){var r=[];return e==null?r:d&&e.filter===d?e.filter(t,n):(x(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},S.reject=function(e,t,n){var r=[];return e==null?r:(x(e,function(e,i,s){t.call(n,e,i,s)||(r[r.length]=e)}),r)},S.every=S.all=function(e,t,r){var i=!0;return e==null?i:v&&e.every===v?e.every(t,r):(x(e,function(e,s,o){if(!(i=i&&t.call(r,e,s,o)))return n}),i)};var T=S.some=S.any=function(e,t,r){t=t||S.identity;var i=!1;return e==null?i:m&&e.some===m?e.some(t,r):(x(e,function(e,s,o){if(i|=t.call(r,e,s,o))return n}),!!i)};S.include=S.contains=function(e,t){var n=!1;return e==null?n:g&&e.indexOf===g?e.indexOf(t)!=-1:(T(e,function(e){if(n=e===t)return!0}),n)},S.invoke=function(e,t){var n=o.call(arguments,2);return S.map(e,function(e){return(t.call?t||e:e[t]).apply(e,n)})},S.pluck=function(e,t){return S.map(e,function(e){return e[t]})},S.max=function(e,t,n){if(!t&&S.isArray(e))return Math.max.apply(Math,e);var r={computed:-Infinity};return x(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},S.min=function(e,t,n){if(!t&&S.isArray(e))return Math.min.apply(Math,e);var r={computed:Infinity};return x(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o<r.computed&&(r={value:e,computed:o})}),r.value},S.sortBy=function(e,t,n){return S.pluck(S.map(e,function(e,r,i){return{value:e,criteria:t.call(n,e,r,i)}}).sort(function(e,t){var n=e.criteria,r=t.criteria;return n<r?-1:n>r?1:0}),"value")},S.groupBy=function(e,t){var n={};return x(e,function(e,r){var i=t(e,r);(n[i]||(n[i]=[])).push(e)}),n},S.sortedIndex=function(e,t,n){n||(n=S.identity);var r=0,i=e.length;while(r<i){var s=r+i>>1;n(e[s])<n(t)?r=s+1:i=s}return r},S.toArray=function(e){return e?e.toArray?e.toArray():S.isArray(e)?o.call(e):S.isArguments(e)?o.call(e):S.values(e):[]},S.size=function(e){return S.toArray(e).length},S.first=S.head=function(e,t,n){return t!=null&&!n?o.call(e,0,t):e[0]},S.rest=S.tail=function(e,t,n){return o.call(e,t==null||n?1:t)},S.last=function(e){return e[e.length-1]},S.compact=function(e){return S.filter(e,function(e){return!!e})},S.flatten=function(e){return S.reduce(e,function(e,t){return S.isArray(t)?e.concat(S.flatten(t)):(e[e.length]=t,e)},[])},S.without=function(e){return S.difference(e,o.call(arguments,1))},S.uniq=S.unique=function(e,t){return S.reduce(e,function(e,n,r){if(0==r||(t===!0?S.last(e)!=n:!S.include(e,n)))e[e.length]=n;return e},[])},S.union=function(){return S.uniq(S.flatten(arguments))},S.intersection=S.intersect=function(e){var t=o.call(arguments,1);return S.filter(S.uniq(e),function(e){return S.every(t,function(t){return S.indexOf(t,e)>=0})})},S.difference=function(e,t){return S.filter(e,function(e){return!S.include(t,e)})},S.zip=function(){var e=o.call(arguments),t=S.max(S.pluck(e,"length")),n=new Array(t);for(var r=0;r<t;r++)n[r]=S.pluck(e,""+r);return n},S.indexOf=function(e,t,n){if(e==null)return-1;var r,i;if(n)return r=S.sortedIndex(e,t),e[r]===t?r:-1;if(g&&e.indexOf===g)return e.indexOf(t);for(r=0,i=e.length;r<i;r++)if(e[r]===t)return r;return-1},S.lastIndexOf=function(e,t){if(e==null)return-1;if(y&&e.lastIndexOf===y)return e.lastIndexOf(t);var n=e.length;while(n--)if(e[n]===t)return n;return-1},S.range=function(e,t,n){arguments.length<=1&&(t=e||0,e=0),n=arguments[2]||1;var r=Math.max(Math.ceil((t-e)/n),0),i=0,s=new Array(r);while(i<r)s[i++]=e,e+=n;return s},S.bind=function(e,t){if(e.bind===E&&E)return E.apply(e,o.call(arguments,1));var n=o.call(arguments,2);return function(){return e.apply(t,n.concat(o.call(arguments)))}},S.bindAll=function(e){var t=o.call(arguments,1);return t.length==0&&(t=S.functions(e)),x(t,function(t){e[t]=S.bind(e[t],e)}),e},S.memoize=function(e,t){var n={};return t||(t=S.identity),function(){var r=t.apply(this,arguments);return f.call(n,r)?n[r]:n[r]=e.apply(this,arguments)}},S.delay=function(e,t){var n=o.call(arguments,2);return setTimeout(function(){return e.apply(e,n)},t)},S.defer=function(e){return S.delay.apply(S,[e,1].concat(o.call(arguments,1)))};var N=function(e,t,n){var r;return function(){var i=this,s=arguments,o=function(){r=null,e.apply(i,s)};n&&clearTimeout(r);if(n||!r)r=setTimeout(o,t)}};S.throttle=function(e,t){return N(e,t,!1)},S.debounce=function(e,t){return N(e,t,!0)},S.once=function(e){var t=!1,n;return function(){return t?n:(t=!0,n=e.apply(this,arguments))}},S.wrap=function(e,t){return function(){var n=[e].concat(o.call(arguments));return t.apply(this,n)}},S.compose=function(){var e=o.call(arguments);return function(){var t=o.call(arguments);for(var n=e.length-1;n>=0;n--)t=[e[n].apply(this,t)];return t[0]}},S.after=function(e,t){return function(){if(--e<1)return t.apply(this,arguments)}},S.keys=w||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)f.call(e,n)&&(t[t.length]=n);return t},S.values=function(e){return S.map(e,S.identity)},S.functions=S.methods=function(e){var t=[];for(var n in e)S.isFunction(e[n])&&t.push(n);return t.sort()},S.extend=function(e){return x(o.call(arguments,1),function(t){for(var n in t)t[n]!==void 0&&(e[n]=t[n])}),e},S.defaults=function(e){return x(o.call(arguments,1),function(t){for(var n in t)e[n]==null&&(e[n]=t[n])}),e},S.clone=function(e){return S.isArray(e)?e.slice():S.extend({},e)},S.tap=function(e,t){return t(e),e},S.isEqual=function(e,t){if(e===t)return!0;var n=typeof e,r=typeof t;if(n!=r)return!1;if(e==t)return!0;if(!e&&t||e&&!t)return!1;e._chain&&(e=e._wrapped),t._chain&&(t=t._wrapped);if(e.isEqual)return e.isEqual(t);if(t.isEqual)return t.isEqual(e);if(S.isDate(e)&&S.isDate(t))return e.getTime()===t.getTime();if(S.isNaN(e)&&S.isNaN(t))return!1;if(S.isRegExp(e)&&S.isRegExp(t))return e.source===t.source&&e.global===t.global&&e.ignoreCase===t.ignoreCase&&e.multiline===t.multiline;if(n!=="object")return!1;if(e.length&&e.length!==t.length)return!1;var i=S.keys(e),s=S.keys(t);if(i.length!=s.length)return!1;for(var o in e)if(!(o in t)||!S.isEqual(e[o],t[o]))return!1;return!0},S.isEmpty=function(e){if(S.isArray(e)||S.isString(e))return e.length===0;for(var t in e)if(f.call(e,t))return!1;return!0},S.isElement=function(e){return!!e&&e.nodeType==1},S.isArray=b||function(e){return a.call(e)==="[object Array]"},S.isObject=function(e){return e===Object(e)},S.isArguments=function(e){return!!e&&!!f.call(e,"callee")},S.isFunction=function(e){return!!(e&&e.constructor&&e.call&&e.apply)},S.isString=function(e){return!!(e===""||e&&e.charCodeAt&&e.substr)},S.isNumber=function(e){return!!(e===0||e&&e.toExponential&&e.toFixed)},S.isNaN=function(e){return e!==e},S.isBoolean=function(e){return e===!0||e===!1},S.isDate=function(e){return!!(e&&e.getTimezoneOffset&&e.setUTCFullYear)},S.isRegExp=function(e){return!(!(e&&e.test&&e.exec)||!e.ignoreCase&&e.ignoreCase!==!1)},S.isNull=function(e){return e===null},S.isUndefined=function(e){return e===void 0},S.noConflict=function(){return e._=t,this},S.identity=function(e){return e},S.times=function(e,t,n){for(var r=0;r<e;r++)t.call(n,r)},S.mixin=function(e){x(S.functions(e),function(t){A(t,S[t]=e[t])})};var C=0;S.uniqueId=function(e){var t=C++;return e?e+t:t},S.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g},S.template=function(e,t){var n=S.templateSettings,r="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+e.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(n.interpolate,function(e,t){return"',"+t.replace(/\\'/g,"'")+",'"}).replace(n.evaluate||null,function(e,t){return"');"+t.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",i=new Function("obj",r);return t?i(t):i};var k=function(e){this._wrapped=e};S.prototype=k.prototype;var L=function(e,t){return t?S(e).chain():e},A=function(e,t){k.prototype[e]=function(){var e=o.call(arguments);return u.call(e,this._wrapped),L(t.apply(S,e),this._chain)}};S.mixin(S),x(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=r[e];k.prototype[e]=function(){return t.apply(this._wrapped,arguments),L(this._wrapped,this._chain)}}),x(["concat","join","slice"],function(e){var t=r[e];k.prototype[e]=function(){return L(t.apply(this._wrapped,arguments),this._chain)}}),k.prototype.chain=function(){return this._chain=!0,this},k.prototype.value=function(){return this._wrapped}})(); +/** + * Flotr2 (c) 2012 Carl Sutherland + * MIT License + * Special thanks to: + * Flotr: http://code.google.com/p/flotr/ (fork) + * Flot: https://github.com/flot/flot (original fork) + */ +(function(){var e=this,t=this.Flotr,n;n={_:_,bean:bean,isIphone:/iphone/i.test(navigator.userAgent),isIE:navigator.appVersion.indexOf("MSIE")!=-1?parseFloat(navigator.appVersion.split("MSIE")[1]):!1,graphTypes:{},plugins:{},addType:function(e,t){n.graphTypes[e]=t,n.defaultOptions[e]=t.options||{},n.defaultOptions.defaultType=n.defaultOptions.defaultType||e},addPlugin:function(e,t){n.plugins[e]=t,n.defaultOptions[e]=t.options||{}},draw:function(e,t,r,i){return i=i||n.Graph,new i(e,t,r)},merge:function(e,t){var r,i,s=t||{};for(r in e)i=e[r],i&&typeof i=="object"?i.constructor===Array?s[r]=this._.clone(i):i.constructor!==RegExp&&!this._.isElement(i)&&!i.jquery?s[r]=n.merge(i,t?t[r]:undefined):s[r]=i:s[r]=i;return s},clone:function(e){return n.merge(e,{})},getTickSize:function(e,t,r,i){var s=(r-t)/e,o=n.getMagnitude(s),u=10,a=s/o;return a<1.5?u=1:a<2.25?u=2:a<3?u=i===0?2:2.5:a<7.5&&(u=5),u*o},defaultTickFormatter:function(e,t){return e+""},defaultTrackFormatter:function(e){return"("+e.x+", "+e.y+")"},engineeringNotation:function(e,t,n){var r=["Y","Z","E","P","T","G","M","k",""],i=["y","z","a","f","p","n","µ","m",""],s=r.length;n=n||1e3,t=Math.pow(10,t||2);if(e===0)return 0;if(e>1)while(s--&&e>=n)e/=n;else{r=i,s=r.length;while(s--&&e<1)e*=n}return Math.round(e*t)/t+r[s]},getMagnitude:function(e){return Math.pow(10,Math.floor(Math.log(e)/Math.LN10))},toPixel:function(e){return Math.floor(e)+.5},toRad:function(e){return-e*(Math.PI/180)},floorInBase:function(e,t){return t*Math.floor(e/t)},drawText:function(e,t,r,i,s){if(!e.fillText){e.drawText(t,r,i,s);return}s=this._.extend({size:n.defaultOptions.fontSize,color:"#000000",textAlign:"left",textBaseline:"bottom",weight:1,angle:0},s),e.save(),e.translate(r,i),e.rotate(s.angle),e.fillStyle=s.color,e.font=(s.weight>1?"bold ":"")+s.size*1.3+"px sans-serif",e.textAlign=s.textAlign,e.textBaseline=s.textBaseline,e.fillText(t,0,0),e.restore()},getBestTextAlign:function(e,t){return t=t||{textAlign:"center",textBaseline:"middle"},e+=n.getTextAngleFromAlign(t),Math.abs(Math.cos(e))>.01&&(t.textAlign=Math.cos(e)>0?"right":"left"),Math.abs(Math.sin(e))>.01&&(t.textBaseline=Math.sin(e)>0?"top":"bottom"),t},alignTable:{"right middle":0,"right top":Math.PI/4,"center top":Math.PI/2,"left top":3*(Math.PI/4),"left middle":Math.PI,"left bottom":-3*(Math.PI/4),"center bottom":-Math.PI/2,"right bottom":-Math.PI/4,"center middle":0},getTextAngleFromAlign:function(e){return n.alignTable[e.textAlign+" "+e.textBaseline]||0},noConflict:function(){return e.Flotr=t,this}},e.Flotr=n})(),Flotr.defaultOptions={colors:["#00A8F0","#C0D800","#CB4B4B","#4DA74D","#9440ED"],ieBackgroundColor:"#FFFFFF",title:null,subtitle:null,shadowSize:4,defaultType:null,HtmlText:!0,fontColor:"#545454",fontSize:7.5,resolution:1,parseFloat:!0,preventDefault:!0,xaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:0,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,mode:"normal",timeFormat:null,timeMode:"UTC",timeUnit:"millisecond",scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},x2axis:{},yaxis:{ticks:null,minorTicks:null,showLabels:!0,showMinorLabels:!1,labelsAngle:0,title:null,titleAngle:90,noTicks:5,minorTickFreq:null,tickFormatter:Flotr.defaultTickFormatter,tickDecimals:null,min:null,max:null,autoscale:!1,autoscaleMargin:0,color:null,scaling:"linear",base:Math.E,titleAlign:"center",margin:!0},y2axis:{titleAngle:270},grid:{color:"#545454",backgroundColor:null,backgroundImage:null,watermarkAlpha:.4,tickColor:"#DDDDDD",labelMargin:3,verticalLines:!0,minorVerticalLines:null,horizontalLines:!0,minorHorizontalLines:null,outlineWidth:1,outline:"nsew",circular:!1},mouse:{track:!1,trackAll:!1,position:"se",relative:!1,trackFormatter:Flotr.defaultTrackFormatter,margin:5,lineColor:"#FF3F19",trackDecimals:1,sensibility:2,trackY:!0,radius:3,fillColor:null,fillOpacity:.4}},function(){function t(e,t,n,r){this.rgba=["r","g","b","a"];var i=4;while(-1<--i)this[this.rgba[i]]=arguments[i]||(i==3?1:0);this.normalize()}var e=Flotr._,n={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]};t.prototype={scale:function(t,n,r,i){var s=4;while(-1<--s)e.isUndefined(arguments[s])||(this[this.rgba[s]]*=arguments[s]);return this.normalize()},alpha:function(t){return!e.isUndefined(t)&&!e.isNull(t)&&(this.a=t),this.normalize()},clone:function(){return new t(this.r,this.b,this.g,this.a)},limit:function(e,t,n){return Math.max(Math.min(e,n),t)},normalize:function(){var e=this.limit;return this.r=e(parseInt(this.r,10),0,255),this.g=e(parseInt(this.g,10),0,255),this.b=e(parseInt(this.b,10),0,255),this.a=e(this.a,0,1),this},distance:function(e){if(!e)return;e=new t.parse(e);var n=0,r=3;while(-1<--r)n+=Math.abs(this[this.rgba[r]]-e[this.rgba[r]]);return n},toString:function(){return this.a>=1?"rgb("+[this.r,this.g,this.b].join(",")+")":"rgba("+[this.r,this.g,this.b,this.a].join(",")+")"},contrast:function(){var e=1-(.299*this.r+.587*this.g+.114*this.b)/255;return e<.5?"#000000":"#ffffff"}},e.extend(t,{parse:function(e){if(e instanceof t)return e;var r;if(r=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(e))return new t(parseInt(r[1],16),parseInt(r[2],16),parseInt(r[3],16));if(r=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(e))return new t(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10));if(r=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(e))return new t(parseInt(r[1]+r[1],16),parseInt(r[2]+r[2],16),parseInt(r[3]+r[3],16));if(r=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(e))return new t(parseInt(r[1],10),parseInt(r[2],10),parseInt(r[3],10),parseFloat(r[4]));if(r=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(e))return new t(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55);if(r=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(e))return new t(parseFloat(r[1])*2.55,parseFloat(r[2])*2.55,parseFloat(r[3])*2.55,parseFloat(r[4]));var i=(e+"").replace(/^\s*([\S\s]*?)\s*$/,"$1").toLowerCase();return i=="transparent"?new t(255,255,255,0):(r=n[i])?new t(r[0],r[1],r[2]):new t(0,0,0,0)},processColor:function(n,r){var i=r.opacity;if(!n)return"rgba(0, 0, 0, 0)";if(n instanceof t)return n.alpha(i).toString();if(e.isString(n))return t.parse(n).alpha(i).toString();var s=n.colors?n:{colors:n};if(!r.ctx)return e.isArray(s.colors)?t.parse(e.isArray(s.colors[0])?s.colors[0][1]:s.colors[0]).alpha(i).toString():"rgba(0, 0, 0, 0)";s=e.extend({start:"top",end:"bottom"},s),/top/i.test(s.start)&&(r.x1=0),/left/i.test(s.start)&&(r.y1=0),/bottom/i.test(s.end)&&(r.x2=0),/right/i.test(s.end)&&(r.y2=0);var o,u,a,f=r.ctx.createLinearGradient(r.x1,r.y1,r.x2,r.y2);for(o=0;o<s.colors.length;o++)u=s.colors[o],e.isArray(u)?(a=u[0],u=u[1]):a=o/(s.colors.length-1),f.addColorStop(a,t.parse(u).alpha(i));return f}}),Flotr.Color=t}(),Flotr.Date={set:function(e,t,n,r){n=n||"UTC",t="set"+(n==="UTC"?"UTC":"")+t,e[t](r)},get:function(e,t,n){return n=n||"UTC",t="get"+(n==="UTC"?"UTC":"")+t,e[t]()},format:function(e,t,n){function s(e){return e+="",e.length==1?"0"+e:e}if(!e)return;var r=this.get,i={h:r(e,"Hours",n).toString(),H:s(r(e,"Hours",n)),M:s(r(e,"Minutes",n)),S:s(r(e,"Seconds",n)),s:r(e,"Milliseconds",n),d:r(e,"Date",n).toString(),m:(r(e,"Month",n)+1).toString(),y:r(e,"FullYear",n).toString(),b:Flotr.Date.monthNames[r(e,"Month",n)]},o=[],u,a=!1;for(var f=0;f<t.length;++f)u=t.charAt(f),a?(o.push(i[u]||u),a=!1):u=="%"?a=!0:o.push(u);return o.join("")},getFormat:function(e,t){var n=Flotr.Date.timeUnits;return e<n.second?"%h:%M:%S.%s":e<n.minute?"%h:%M:%S":e<n.day?t<2*n.day?"%h:%M":"%b %d %h:%M":e<n.month?"%b %d":e<n.year?t<n.year?"%b":"%b %y":"%y"},formatter:function(e,t){var n=t.options,r=Flotr.Date.timeUnits[n.timeUnit],i=new Date(e*r);if(t.options.timeFormat)return Flotr.Date.format(i,n.timeFormat,n.timeMode);var s=(t.max-t.min)*r,o=t.tickSize*Flotr.Date.timeUnits[t.tickUnit];return Flotr.Date.format(i,Flotr.Date.getFormat(o,s),n.timeMode)},generator:function(e){function y(e){t(m,e,o,Flotr.floorInBase(n(m,e,o),h))}var t=this.set,n=this.get,r=this.timeUnits,i=this.spec,s=e.options,o=s.timeMode,u=r[s.timeUnit],a=e.min*u,f=e.max*u,l=(f-a)/s.noTicks,c=[],h=e.tickSize,p,d,v;d=s.tickFormatter===Flotr.defaultTickFormatter?this.formatter:s.tickFormatter;for(v=0;v<i.length-1;++v){var m=i[v][0]*r[i[v][1]];if(l<(m+i[v+1][0]*r[i[v+1][1]])/2&&m>=h)break}h=i[v][0],p=i[v][1],p=="year"&&(h=Flotr.getTickSize(s.noTicks*r.year,a,f,0),h==.5&&(p="month",h=6)),e.tickUnit=p,e.tickSize=h;var g=h*r[p];m=new Date(a);switch(p){case"millisecond":y("Milliseconds");break;case"second":y("Seconds");break;case"minute":y("Minutes");break;case"hour":y("Hours");break;case"month":y("Month");break;case"year":y("FullYear")}g>=r.second&&t(m,"Milliseconds",o,0),g>=r.minute&&t(m,"Seconds",o,0),g>=r.hour&&t(m,"Minutes",o,0),g>=r.day&&t(m,"Hours",o,0),g>=r.day*4&&t(m,"Date",o,1),g>=r.year&&t(m,"Month",o,0);var b=0,w=NaN,E;do{E=w,w=m.getTime(),c.push({v:w/u,label:d(w/u,e)});if(p=="month")if(h<1){t(m,"Date",o,1);var S=m.getTime();t(m,"Month",o,n(m,"Month",o)+1);var x=m.getTime();m.setTime(w+b*r.hour+(x-S)*h),b=n(m,"Hours",o),t(m,"Hours",o,0)}else t(m,"Month",o,n(m,"Month",o)+h);else p=="year"?t(m,"FullYear",o,n(m,"FullYear",o)+h):m.setTime(w+g)}while(w<f&&w!=E);return c},timeUnits:{millisecond:1,second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,year:31556952e3},spec:[[1,"millisecond"],[20,"millisecond"],[50,"millisecond"],[100,"millisecond"],[200,"millisecond"],[500,"millisecond"],[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"],[3,"month"],[6,"month"],[1,"year"]],monthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]},function(){function t(e){return e&&e.jquery?e[0]:e}var e=Flotr._;Flotr.DOM={addClass:function(n,r){n=t(n);var i=n.className?n.className:"";if(e.include(i.split(/\s+/g),r))return;n.className=(i?i+" ":"")+r},create:function(e){return document.createElement(e)},node:function(e){var t=Flotr.DOM.create("div"),n;return t.innerHTML=e,n=t.children[0],t.innerHTML="",n},empty:function(e){e=t(e),e.innerHTML=""},remove:function(e){e=t(e),e.parentNode.removeChild(e)},hide:function(e){e=t(e),Flotr.DOM.setStyles(e,{display:"none"})},insert:function(n,r){n=t(n),e.isString(r)?n.innerHTML+=r:e.isElement(r)&&n.appendChild(r)},opacity:function(e,n){e=t(e),e.style.opacity=n},position:function(e,n){return e=t(e),e.offsetParent?(n=this.position(e.offsetParent),n.left+=e.offsetLeft,n.top+=e.offsetTop,n):{left:e.offsetLeft||0,top:e.offsetTop||0}},removeClass:function(n,r){var i=n.className?n.className:"";n=t(n),n.className=e.filter(i.split(/\s+/g),function(e){if(e!=r)return!0}).join(" ")},setStyles:function(n,r){n=t(n),e.each(r,function(e,t){n.style[t]=e})},show:function(e){e=t(e),Flotr.DOM.setStyles(e,{display:""})},size:function(e){return e=t(e),{height:e.offsetHeight,width:e.offsetWidth}}}}(),function(){var e=Flotr,t=e.bean;e.EventAdapter={observe:function(e,n,r){return t.add(e,n,r),this},fire:function(e,n,r){return t.fire(e,n,r),typeof Prototype!="undefined"&&Event.fire(e,n,r),this},stopObserving:function(e,n,r){return t.remove(e,n,r),this},eventPointer:function(t){if(!e._.isUndefined(t.touches)&&t.touches.length>0)return{x:t.touches[0].pageX,y:t.touches[0].pageY};if(!e._.isUndefined(t.changedTouches)&&t.changedTouches.length>0)return{x:t.changedTouches[0].pageX,y:t.changedTouches[0].pageY};if(t.pageX||t.pageY)return{x:t.pageX,y:t.pageY};if(t.clientX||t.clientY){var n=document,r=n.body,i=n.documentElement;return{x:t.clientX+r.scrollLeft+i.scrollLeft,y:t.clientY+r.scrollTop+i.scrollTop}}}}}(),function(){var e=Flotr,t=e.DOM,n=e._,r=function(e){this.o=e};r.prototype={dimensions:function(e,t,n,r){return e?this.o.html?this.html(e,this.o.element,n,r):this.canvas(e,t):{width:0,height:0}},canvas:function(t,n){if(!this.o.textEnabled)return;n=n||{};var r=this.measureText(t,n),i=r.width,s=n.size||e.defaultOptions.fontSize,o=n.angle||0,u=Math.cos(o),a=Math.sin(o),f=2,l=6,c;return c={width:Math.abs(u*i)+Math.abs(a*s)+f,height:Math.abs(a*i)+Math.abs(u*s)+l},c},html:function(e,n,r,i){var s=t.create("div");return t.setStyles(s,{position:"absolute",top:"-10000px"}),t.insert(s,'<div style="'+r+'" class="'+i+' flotr-dummy-div">'+e+"</div>"),t.insert(this.o.element,s),t.size(s)},measureText:function(t,r){var i=this.o.ctx,s;return!i.fillText||e.isIphone&&i.measure?{width:i.measure(t,r)}:(r=n.extend({size:e.defaultOptions.fontSize,weight:1,angle:0},r),i.save(),i.font=(r.weight>1?"bold ":"")+r.size*1.3+"px sans-serif",s=i.measureText(t),i.restore(),s)}},Flotr.Text=r}(),function(){function i(e,n,r){return t.observe.apply(this,arguments),this._handles.push(arguments),this}var e=Flotr.DOM,t=Flotr.EventAdapter,n=Flotr._,r=Flotr;Graph=function(e,i,s){this._setEl(e),this._initMembers(),this._initPlugins(),t.fire(this.el,"flotr:beforeinit",[this]),this.data=i,this.series=r.Series.getSeries(i),this._initOptions(s),this._initGraphTypes(),this._initCanvas(),this._text=new r.Text({element:this.el,ctx:this.ctx,html:this.options.HtmlText,textEnabled:this.textEnabled}),t.fire(this.el,"flotr:afterconstruct",[this]),this._initEvents(),this.findDataRanges(),this.calculateSpacing(),this.draw(n.bind(function(){t.fire(this.el,"flotr:afterinit",[this])},this))},Graph.prototype={destroy:function(){t.fire(this.el,"flotr:destroy"),n.each(this._handles,function(e){t.stopObserving.apply(this,e)}),this._handles=[],this.el.graph=null},observe:i,_observe:i,processColor:function(e,t){var i={x1:0,y1:0,x2:this.plotWidth,y2:this.plotHeight,opacity:1,ctx:this.ctx};return n.extend(i,t),r.Color.processColor(e,i)},findDataRanges:function(){var e=this.axes,t,i,s;n.each(this.series,function(e){s=e.getRange(),s&&(t=e.xaxis,i=e.yaxis,t.datamin=Math.min(s.xmin,t.datamin),t.datamax=Math.max(s.xmax,t.datamax),i.datamin=Math.min(s.ymin,i.datamin),i.datamax=Math.max(s.ymax,i.datamax),t.used=t.used||s.xused,i.used=i.used||s.yused)},this),!e.x.used&&!e.x2.used&&(e.x.used=!0),!e.y.used&&!e.y2.used&&(e.y.used=!0),n.each(e,function(e){e.calculateRange()});var o=n.keys(r.graphTypes),u=!1;n.each(this.series,function(e){if(e.hide)return;n.each(o,function(t){e[t]&&e[t].show&&(this.extendRange(t,e),u=!0)},this),u||this.extendRange(this.options.defaultType,e)},this)},extendRange:function(e,t){this[e].extendRange&&this[e].extendRange(t,t.data,t[e],this[e]),this[e].extendYRange&&this[e].extendYRange(t.yaxis,t.data,t[e],this[e]),this[e].extendXRange&&this[e].extendXRange(t.xaxis,t.data,t[e],this[e])},calculateSpacing:function(){var e=this.axes,t=this.options,r=this.series,i=t.grid.labelMargin,s=this._text,o=e.x,u=e.x2,a=e.y,f=e.y2,l=t.grid.outlineWidth,c,h,p,d;n.each(e,function(e){e.calculateTicks(),e.calculateTextDimensions(s,t)}),d=s.dimensions(t.title,{size:t.fontSize*1.5},"font-size:1em;font-weight:bold;","flotr-title"),this.titleHeight=d.height,d=s.dimensions(t.subtitle,{size:t.fontSize},"font-size:smaller;","flotr-subtitle"),this.subtitleHeight=d.height;for(h=0;h<t.length;++h)r[h].points.show&&(l=Math.max(l,r[h].points.radius+r[h].points.lineWidth/2));var v=this.plotOffset;o.options.margin===!1?(v.bottom=0,v.top=0):o.options.margin===!0?(v.bottom+=(t.grid.circular?0:o.used&&o.options.showLabels?o.maxLabel.height+i:0)+(o.used&&o.options.title?o.titleSize.height+i:0)+l,v.top+=(t.grid.circular?0:u.used&&u.options.showLabels?u.maxLabel.height+i:0)+(u.used&&u.options.title?u.titleSize.height+i:0)+this.subtitleHeight+this.titleHeight+l):(v.bottom=o.options.margin,v.top=o.options.margin),a.options.margin===!1?(v.left=0,v.right=0):a.options.margin===!0?(v.left+=(t.grid.circular?0:a.used&&a.options.showLabels?a.maxLabel.width+i:0)+(a.used&&a.options.title?a.titleSize.width+i:0)+l,v.right+=(t.grid.circular?0:f.used&&f.options.showLabels?f.maxLabel.width+i:0)+(f.used&&f.options.title?f.titleSize.width+i:0)+l):(v.left=a.options.margin,v.right=a.options.margin),v.top=Math.floor(v.top),this.plotWidth=this.canvasWidth-v.left-v.right,this.plotHeight=this.canvasHeight-v.bottom-v.top,o.length=u.length=this.plotWidth,a.length=f.length=this.plotHeight,a.offset=f.offset=this.plotHeight,o.setScale(),u.setScale(),a.setScale(),f.setScale()},draw:function(e){var n=this.ctx,r;t.fire(this.el,"flotr:beforedraw",[this.series,this]);if(this.series.length){n.save(),n.translate(this.plotOffset.left,this.plotOffset.top);for(r=0;r<this.series.length;r++)this.series[r].hide||this.drawSeries(this.series[r]);n.restore(),this.clip()}t.fire(this.el,"flotr:afterdraw",[this.series,this]),e&&e()},drawSeries:function(e){function t(e,t){var n=this.getOptions(e,t);this[t].draw(n)}var i=!1;e=e||this.series,n.each(r.graphTypes,function(n,r){e[r]&&e[r].show&&this[r]&&(i=!0,t.call(this,e,r))},this),i||t.call(this,e,this.options.defaultType)},getOptions:function(e,t){var n=e[t],i=this[t],s=e.xaxis,o=e.yaxis,u={context:this.ctx,width:this.plotWidth,height:this.plotHeight,fontSize:this.options.fontSize,fontColor:this.options.fontColor,textEnabled:this.textEnabled,htmlText:this.options.HtmlText,text:this._text,element:this.el,data:e.data,color:e.color,shadowSize:e.shadowSize,xScale:s.d2p,yScale:o.d2p,xInverse:s.p2d,yInverse:o.p2d};return u=r.merge(n,u),u.fillStyle=this.processColor(n.fillColor||e.color,{opacity:n.fillOpacity}),u},getEventPosition:function(n){var r=document,i=r.body,s=r.documentElement,o=this.axes,u=this.plotOffset,a=this.lastMousePos,f=t.eventPointer(n),l=f.x-a.pageX,c=f.y-a.pageY,h,p,d;return"ontouchstart"in this.el?(h=e.position(this.overlay),p=f.x-h.left-u.left,d=f.y-h.top-u.top):(h=this.overlay.getBoundingClientRect(),p=n.clientX-h.left-u.left-i.scrollLeft-s.scrollLeft,d=n.clientY-h.top-u.top-i.scrollTop-s.scrollTop),{x:o.x.p2d(p),x2:o.x2.p2d(p),y:o.y.p2d(d),y2:o.y2.p2d(d),relX:p,relY:d,dX:l,dY:c,absX:f.x,absY:f.y,pageX:f.x,pageY:f.y}},clickHandler:function(e){if(this.ignoreClick)return this.ignoreClick=!1,this.ignoreClick;t.fire(this.el,"flotr:click",[this.getEventPosition(e),this])},mouseMoveHandler:function(e){if(this.mouseDownMoveHandler)return;var n=this.getEventPosition(e);t.fire(this.el,"flotr:mousemove",[e,n,this]),this.lastMousePos=n},mouseDownHandler:function(e){if(this.mouseUpHandler)return;this.mouseUpHandler=n.bind(function(e){t.stopObserving(document,"mouseup",this.mouseUpHandler),t.stopObserving(document,"mousemove",this.mouseDownMoveHandler),this.mouseDownMoveHandler=null,this.mouseUpHandler=null,t.fire(this.el,"flotr:mouseup",[e,this])},this),this.mouseDownMoveHandler=n.bind(function(n){var r=this.getEventPosition(n);t.fire(this.el,"flotr:mousemove",[e,r,this]),this.lastMousePos=r},this),t.observe(document,"mouseup",this.mouseUpHandler),t.observe(document,"mousemove",this.mouseDownMoveHandler),t.fire(this.el,"flotr:mousedown",[e,this]),this.ignoreClick=!1},drawTooltip:function(t,n,r,i){var s=this.getMouseTrack(),o="opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;",u=i.position,a=i.margin,f=this.plotOffset;n!==null&&r!==null?(i.relative?(u.charAt(0)=="n"?o+="bottom:"+(a-f.top-r+this.canvasHeight)+"px;top:auto;":u.charAt(0)=="s"&&(o+="top:"+(a+f.top+r)+"px;bottom:auto;"),u.charAt(1)=="e"?o+="left:"+(a+f.left+n)+"px;right:auto;":u.charAt(1)=="w"&&(o+="right:"+(a-f.left-n+this.canvasWidth)+"px;left:auto;")):(u.charAt(0)=="n"?o+="top:"+(a+f.top)+"px;bottom:auto;":u.charAt(0)=="s"&&(o+="bottom:"+(a+f.bottom)+"px;top:auto;"),u.charAt(1)=="e"?o+="right:"+(a+f.right)+"px;left:auto;":u.charAt(1)=="w"&&(o+="left:"+(a+f.left)+"px;right:auto;")),s.style.cssText=o,e.empty(s),e.insert(s,t),e.show(s)):e.hide(s)},clip:function(e){var t=this.plotOffset,n=this.canvasWidth,i=this.canvasHeight;e=e||this.ctx;if(r.isIE&&r.isIE<9&&!r.isFlashCanvas){if(e===this.octx)return;e.save(),e.fillStyle=this.processColor(this.options.ieBackgroundColor),e.fillRect(0,0,n,t.top),e.fillRect(0,0,t.left,i),e.fillRect(0,i-t.bottom,n,t.bottom),e.fillRect(n-t.right,0,t.right,i),e.restore()}else e.clearRect(0,0,n,t.top),e.clearRect(0,0,t.left,i),e.clearRect(0,i-t.bottom,n,t.bottom),e.clearRect(n-t.right,0,t.right,i)},_initMembers:function(){this._handles=[],this.lastMousePos={pageX:null,pageY:null},this.plotOffset={left:0,right:0,top:0,bottom:0},this.ignoreClick=!0,this.prevHit=null},_initGraphTypes:function(){n.each(r.graphTypes,function(e,t){this[t]=r.clone(e)},this)},_initEvents:function(){var e=this.el,r,i,s;"ontouchstart"in e?(r=n.bind(function(n){s=!0,t.stopObserving(document,"touchend",r),t.fire(e,"flotr:mouseup",[event,this]),this.multitouches=null,i||this.clickHandler(n)},this),this.observe(this.overlay,"touchstart",n.bind(function(n){i=!1,s=!1,this.ignoreClick=!1,n.touches&&n.touches.length>1&&(this.multitouches=n.touches),t.fire(e,"flotr:mousedown",[event,this]),this.observe(document,"touchend",r)},this)),this.observe(this.overlay,"touchmove",n.bind(function(n){var r=this.getEventPosition(n);this.options.preventDefault&&n.preventDefault(),i=!0,this.multitouches||n.touches&&n.touches.length>1?this.multitouches=n.touches:s||t.fire(e,"flotr:mousemove",[event,r,this]),this.lastMousePos=r},this))):this.observe(this.overlay,"mousedown",n.bind(this.mouseDownHandler,this)).observe(e,"mousemove",n.bind(this.mouseMoveHandler,this)).observe(this.overlay,"click",n.bind(this.clickHandler,this)).observe(e,"mouseout",function(n){t.fire(e,"flotr:mouseout",n)})},_initCanvas:function(){function l(i,s){return i||(i=e.create("canvas"),typeof FlashCanvas!="undefined"&&typeof i.getContext=="function"&&(FlashCanvas.initElement(i),this.isFlashCanvas=!0),i.className="flotr-"+s,i.style.cssText="position:absolute;left:0px;top:0px;",e.insert(t,i)),n.each(a,function(t,n){e.show(i);if(s=="canvas"&&i.getAttribute(n)===t)return;i.setAttribute(n,t*r.resolution),i.style[n]=t+"px"}),i.context_=null,i}function c(e){window.G_vmlCanvasManager&&window.G_vmlCanvasManager.initElement(e);var t=e.getContext("2d");return window.G_vmlCanvasManager||t.scale(r.resolution,r.resolution),t}var t=this.el,r=this.options,i=t.children,s=[],o,u,a,f;for(u=i.length;u--;)o=i[u],!this.canvas&&o.className==="flotr-canvas"?this.canvas=o:!this.overlay&&o.className==="flotr-overlay"?this.overlay=o:s.push(o);for(u=s.length;u--;)t.removeChild(s[u]);e.setStyles(t,{position:"relative"}),a={},a.width=t.clientWidth,a.height=t.clientHeight;if(a.width<=0||a.height<=0||r.resolution<=0)throw"Invalid dimensions for plot, width = "+a.width+", height = "+a.height+", resolution = "+r.resolution;this.canvas=l(this.canvas,"canvas"),this.overlay=l(this.overlay,"overlay"),this.ctx=c(this.canvas),this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.octx=c(this.overlay),this.octx.clearRect(0,0,this.overlay.width,this.overlay.height),this.canvasHeight=a.height,this.canvasWidth=a.width,this.textEnabled=!!this.ctx.drawText||!!this.ctx.fillText},_initPlugins:function(){n.each(r.plugins,function(e,t){n.each(e.callbacks,function(e,t){this.observe(this.el,t,n.bind(e,this))},this),this[t]=r.clone(e),n.each(this[t],function(e,r){n.isFunction(e)&&(this[t][r]=n.bind(e,this))},this)},this)},_initOptions:function(e){var i=r.clone(r.defaultOptions);i.x2axis=n.extend(n.clone(i.xaxis),i.x2axis),i.y2axis=n.extend(n.clone(i.yaxis),i.y2axis),this.options=r.merge(e||{},i),this.options.grid.minorVerticalLines===null&&this.options.xaxis.scaling==="logarithmic"&&(this.options.grid.minorVerticalLines=!0),this.options.grid.minorHorizontalLines===null&&this.options.yaxis.scaling==="logarithmic"&&(this.options.grid.minorHorizontalLines=!0),t.fire(this.el,"flotr:afterinitoptions",[this]),this.axes=r.Axis.getAxes(this.options);var s=[],o=[],u=this.series.length,a=this.series.length,f=this.options.colors,l=[],c=0,h,p,d,v;for(p=a-1;p>-1;--p)h=this.series[p].color,h&&(--a,n.isNumber(h)?s.push(h):l.push(r.Color.parse(h)));for(p=s.length-1;p>-1;--p)a=Math.max(a,s[p]+1);for(p=0;o.length<a;){h=f.length==p?new r.Color(100,100,100):r.Color.parse(f[p]);var m=c%2==1?-1:1,g=1+m*Math.ceil(c/2)*.2;h.scale(g,g,g),o.push(h),++p>=f.length&&(p=0,++c)}for(p=0,d=0;p<u;++p){v=this.series[p],v.color?n.isNumber(v.color)&&(v.color=o[v.color].toString()):v.color=o[d++].toString(),v.xaxis||(v.xaxis=this.axes.x),v.xaxis==1?v.xaxis=this.axes.x:v.xaxis==2&&(v.xaxis=this.axes.x2),v.yaxis||(v.yaxis=this.axes.y),v.yaxis==1?v.yaxis=this.axes.y:v.yaxis==2&&(v.yaxis=this.axes.y2);for(var y in r.graphTypes)v[y]=n.extend(n.clone(this.options[y]),v[y]);v.mouse=n.extend(n.clone(this.options.mouse),v.mouse),n.isUndefined(v.shadowSize)&&(v.shadowSize=this.options.shadowSize)}},_setEl:function(e){if(!e)throw"The target container doesn't exist";if(e.graph instanceof Graph)e.graph.destroy();else if(!e.clientWidth)throw"The target container must be visible";e.graph=this,this.el=e}},Flotr.Graph=Graph}(),function(){function n(t){this.orientation=1,this.offset=0,this.datamin=Number.MAX_VALUE,this.datamax=-Number.MAX_VALUE,e.extend(this,t)}function r(e,t){return e=Math.log(Math.max(e,Number.MIN_VALUE)),t!==Math.E&&(e/=Math.log(t)),e}function s(e,t){return t===Math.E?Math.exp(e):Math.pow(t,e)}var e=Flotr._,t="logarithmic";n.prototype={setScale:function(){var e=this.length,n=this.max,i=this.min,o=this.offset,u=this.orientation,a=this.options,f=a.scaling===t,l;f?l=e/(r(n,a.base)-r(i,a.base)):l=e/(n-i),this.scale=l,f?(this.d2p=function(e){return o+u*(r(e,a.base)-r(i,a.base))*l},this.p2d=function(e){return s((o+u*e)/l+r(i,a.base),a.base)}):(this.d2p=function(e){return o+u*(e-i)*l},this.p2d=function(e){return(o+u*e)/l+i})},calculateTicks:function(){var t=this.options;this.ticks=[],this.minorTicks=[],t.ticks?(this._cleanUserTicks(t.ticks,this.ticks),this._cleanUserTicks(t.minorTicks||[],this.minorTicks)):t.mode=="time"?this._calculateTimeTicks():t.scaling==="logarithmic"?this._calculateLogTicks():this._calculateTicks(),e.each(this.ticks,function(e){e.label+=""}),e.each(this.minorTicks,function(e){e.label+=""})},calculateRange:function(){if(!this.used)return;var e=this,t=e.options,n=t.min!==null?t.min:e.datamin,r=t.max!==null?t.max:e.datamax,i=t.autoscaleMargin;t.scaling=="logarithmic"&&(n<=0&&(n=e.datamin),r<=0&&(r=n));if(r==n){var s=r?.01:1;t.min===null&&(n-=s),t.max===null&&(r+=s)}if(t.scaling==="logarithmic"){n<0&&(n=r/t.base);var o=Math.log(r);t.base!=Math.E&&(o/=Math.log(t.base)),o=Math.ceil(o);var u=Math.log(n);t.base!=Math.E&&(u/=Math.log(t.base)),u=Math.ceil(u),e.tickSize=Flotr.getTickSize(t.noTicks,u,o,t.tickDecimals===null?0:t.tickDecimals),t.minorTickFreq===null&&(o-u>10?t.minorTickFreq=0:o-u>5?t.minorTickFreq=2:t.minorTickFreq=5)}else e.tickSize=Flotr.getTickSize(t.noTicks,n,r,t.tickDecimals);e.min=n,e.max=r,t.min===null&&t.autoscale&&(e.min-=e.tickSize*i,e.min<0&&e.datamin>=0&&(e.min=0),e.min=e.tickSize*Math.floor(e.min/e.tickSize)),t.max===null&&t.autoscale&&(e.max+=e.tickSize*i,e.max>0&&e.datamax<=0&&e.datamax!=e.datamin&&(e.max=0),e.max=e.tickSize*Math.ceil(e.max/e.tickSize)),e.min==e.max&&(e.max=e.min+1)},calculateTextDimensions:function(e,t){var n="",r,i;if(this.options.showLabels)for(i=0;i<this.ticks.length;++i)r=this.ticks[i].label.length,r>n.length&&(n=this.ticks[i].label);this.maxLabel=e.dimensions(n,{size:t.fontSize,angle:Flotr.toRad(this.options.labelsAngle)},"font-size:smaller;","flotr-grid-label"),this.titleSize=e.dimensions(this.options.title,{size:t.fontSize*1.2,angle:Flotr.toRad(this.options.titleAngle)},"font-weight:bold;","flotr-axis-title")},_cleanUserTicks:function(t,n){var r=this,i=this.options,s,o,u,a;e.isFunction(t)&&(t=t({min:r.min,max:r.max}));for(o=0;o<t.length;++o)a=t[o],typeof a=="object"?(s=a[0],u=a.length>1?a[1]:i.tickFormatter(s,{min:r.min,max:r.max})):(s=a,u=i.tickFormatter(s,{min:this.min,max:this.max})),n[o]={v:s,label:u}},_calculateTimeTicks:function(){this.ticks=Flotr.Date.generator(this)},_calculateLogTicks:function(){var e=this,t=e.options,n,r,s=Math.log(e.max);t.base!=Math.E&&(s/=Math.log(t.base)),s=Math.ceil(s);var o=Math.log(e.min);t.base!=Math.E&&(o/=Math.log(t.base)),o=Math.ceil(o);for(i=o;i<s;i+=e.tickSize){r=t.base==Math.E?Math.exp(i):Math.pow(t.base,i);var u=r*(t.base==Math.E?Math.exp(e.tickSize):Math.pow(t.base,e.tickSize)),a=(u-r)/t.minorTickFreq;e.ticks.push({v:r,label:t.tickFormatter(r,{min:e.min,max:e.max})});for(n=r+a;n<u;n+=a)e.minorTicks.push({v:n,label:t.tickFormatter(n,{min:e.min,max:e.max})})}r=t.base==Math.E?Math.exp(i):Math.pow(t.base,i),e.ticks.push({v:r,label:t.tickFormatter(r,{min:e.min,max:e.max})})},_calculateTicks:function(){var e=this,t=e.options,n=e.tickSize,r=e.min,i=e.max,s=n*Math.ceil(r/n),o,u,a,f,l,c;t.minorTickFreq&&(u=n/t.minorTickFreq);for(l=0;(a=f=s+l*n)<=i;++l){o=t.tickDecimals,o===null&&(o=1-Math.floor(Math.log(n)/Math.LN10)),o<0&&(o=0),a=a.toFixed(o),e.ticks.push({v:a,label:t.tickFormatter(a,{min:e.min,max:e.max})});if(t.minorTickFreq)for(c=0;c<t.minorTickFreq&&l*n+c*u<i;++c)a=f+c*u,e.minorTicks.push({v:a,label:t.tickFormatter(a,{min:e.min,max:e.max})})}}},e.extend(n,{getAxes:function(e){return{x:new n({options:e.xaxis,n:1,length:this.plotWidth}),x2:new n({options:e.x2axis,n:2,length:this.plotWidth}),y:new n({options:e.yaxis,n:1,length:this.plotHeight,offset:this.plotHeight,orientation:-1}),y2:new n({options:e.y2axis,n:2,length:this.plotHeight,offset:this.plotHeight,orientation:-1})}}}),Flotr.Axis=n}(),function(){function t(t){e.extend(this,t)}var e=Flotr._;t.prototype={getRange:function(){var e=this.data,t=e.length,n=Number.MAX_VALUE,r=Number.MAX_VALUE,i=-Number.MAX_VALUE,s=-Number.MAX_VALUE,o=!1,u=!1,a,f,l;if(t<0||this.hide)return!1;for(l=0;l<t;l++)a=e[l][0],f=e[l][1],a!==null&&(a<n&&(n=a,o=!0),a>i&&(i=a,o=!0)),f!==null&&(f<r&&(r=f,u=!0),f>s&&(s=f,u=!0));return{xmin:n,xmax:i,ymin:r,ymax:s,xused:o,yused:u}}},e.extend(t,{getSeries:function(n){return e.map(n,function(n){var r;return n.data?(r=new t,e.extend(r,n)):r=new t({data:n}),r})}}),Flotr.Series=t}(),Flotr.addType("lines",{options:{show:!1,lineWidth:2,fill:!1,fillBorder:!1,fillColor:null,fillOpacity:.4,steps:!1,stacked:!1},stack:{values:[]},draw:function(e){var t=e.context,n=e.lineWidth,r=e.shadowSize,i;t.save(),t.lineJoin="round",r&&(t.lineWidth=r/2,i=n/2+t.lineWidth/2,t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,i+r/2,!1),t.strokeStyle="rgba(0,0,0,0.2)",this.plot(e,i,!1)),t.lineWidth=n,t.strokeStyle=e.color,this.plot(e,0,!0),t.restore()},plot:function(e,t,n){function S(){!t&&e.fill&&d&&(v=o(d[0]),r.fillStyle=e.fillStyle,r.lineTo(m,p),r.lineTo(v,p),r.lineTo(v,u(d[1])),r.fill(),e.fillBorder&&r.stroke())}var r=e.context,i=e.width,s=e.height,o=e.xScale,u=e.yScale,a=e.data,f=e.stacked?this.stack:!1,l=a.length-1,c=null,h=null,p=u(0),d=null,v,m,g,y,b,w,E;if(l<1)return;r.beginPath();for(E=0;E<l;++E){if(a[E][1]===null||a[E+1][1]===null){e.fill&&E>0&&a[E][1]!==null&&(r.stroke(),S(),d=null,r.closePath(),r.beginPath());continue}v=o(a[E][0]),m=o(a[E+1][0]),d===null&&(d=a[E]),f?(b=f.values[a[E][0]]||0,w=f.values[a[E+1][0]]||f.values[a[E][0]]||0,g=u(a[E][1]+b),y=u(a[E+1][1]+w),n&&(a[E].y0=b,f.values[a[E][0]]=a[E][1]+b,E==l-1&&(a[E+1].y0=w,f.values[a[E+1][0]]=a[E+1][1]+w))):(g=u(a[E][1]),y=u(a[E+1][1]));if(g>s&&y>s||g<0&&y<0||v<0&&m<0||v>i&&m>i)continue;(c!=v||h!=g+t)&&r.moveTo(v,g+t),c=m,h=y+t,e.steps?(r.lineTo(c+t/2,g+t),r.lineTo(c+t/2,h)):r.lineTo(c,h)}(!e.fill||e.fill&&!e.fillBorder)&&r.stroke(),S(),r.closePath()},extendYRange:function(e,t,n,r){var i=e.options;if(n.stacked&&(!i.max&&i.max!==0||!i.min&&i.min!==0)){var s=e.max,o=e.min,u=r.positiveSums||{},a=r.negativeSums||{},f,l;for(l=0;l<t.length;l++)f=t[l][0]+"",t[l][1]>0?(u[f]=(u[f]||0)+t[l][1],s=Math.max(s,u[f])):(a[f]=(a[f]||0)+t[l][1],o=Math.min(o,a[f]));r.negativeSums=a,r.positiveSums=u,e.max=s,e.min=o}n.steps&&(this.hit=function(e){var t=e.data,n=e.args,r=e.yScale,i=n[0],s=t.length,o=n[1],u=e.xInverse(i.relX),a=i.relY,f;for(f=0;f<s-1;f++)if(u>=t[f][0]&&u<=t[f+1][0]){Math.abs(r(t[f][1])-a)<8&&(o.x=t[f][0],o.y=t[f][1],o.index=f,o.seriesIndex=e.index);break}},this.drawHit=function(e){var t=e.context,n=e.args,r=e.data,i=e.xScale,s=n.index,o=i(n.x),u=e.yScale(n.y),a;r.length-1>s&&(a=e.xScale(r[s+1][0]),t.save(),t.strokeStyle=e.color,t.lineWidth=e.lineWidth,t.beginPath(),t.moveTo(o,u),t.lineTo(a,u),t.stroke(),t.closePath(),t.restore())},this.clearHit=function(e){var t=e.context,n=e.args,r=e.data,i=e.xScale,s=e.lineWidth,o=n.index,u=i(n.x),a=e.yScale(n.y),f;r.length-1>o&&(f=e.xScale(r[o+1][0]),t.clearRect(u-s,a-s,f-u+2*s,2*s))})}}),Flotr.addType("bars",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,horizontal:!1,stacked:!1,centered:!0,topPadding:.1,grouped:!1},stack:{positive:[],negative:[],_positive:[],_negative:[]},draw:function(e){var t=e.context;this.current+=1,t.save(),t.lineJoin="miter",t.lineWidth=e.lineWidth,t.strokeStyle=e.color,e.fill&&(t.fillStyle=e.fillStyle),this.plot(e),t.restore()},plot:function(e){var t=e.data,n=e.context,r=e.shadowSize,i,s,o,u,a,f;if(t.length<1)return;this.translate(n,e.horizontal);for(i=0;i<t.length;i++){s=this.getBarGeometry(t[i][0],t[i][1],e);if(s===null)continue;o=s.left,u=s.top,a=s.width,f=s.height,e.fill&&n.fillRect(o,u,a,f),r&&(n.save(),n.fillStyle="rgba(0,0,0,0.05)",n.fillRect(o+r,u+r,a,f),n.restore()),e.lineWidth&&n.strokeRect(o,u,a,f)}},translate:function(e,t){t&&(e.rotate(-Math.PI/2),e.scale(-1,1))},getBarGeometry:function(e,t,n){var r=n.horizontal,i=n.barWidth,s=n.centered,o=n.stacked?this.stack:!1,u=n.lineWidth,a=s?i/2:0,f=r?n.yScale:n.xScale,l=r?n.xScale:n.yScale,c=r?t:e,h=r?e:t,p=0,d,v,m,g,y;return n.grouped&&(this.current/this.groups,c-=a,i/=this.groups,a=i/2,c=c+i*this.current-a),o&&(d=h>0?o.positive:o.negative,p=d[c]||p,d[c]=p+h),v=f(c-a),m=f(c+i-a),g=l(h+p),y=l(p),y<0&&(y=0),e===null||t===null?null:{x:c,y:h,xScale:f,yScale:l,top:g,left:Math.min(v,m)-u/2,width:Math.abs(m-v)-u,height:y-g}},hit:function(e){var t=e.data,n=e.args,r=n[0],i=n[1],s=e.xInverse(r.relX),o=e.yInverse(r.relY),u=this.getBarGeometry(s,o,e),a=u.width/2,f=u.left,l=u.y,c,h;for(h=t.length;h--;)c=this.getBarGeometry(t[h][0],t[h][1],e),(l>0&&l<c.y||l<0&&l>c.y)&&Math.abs(f-c.left)<a&&(i.x=t[h][0],i.y=t[h][1],i.index=h,i.seriesIndex=e.index)},drawHit:function(e){var t=e.context,n=e.args,r=this.getBarGeometry(n.x,n.y,e),i=r.left,s=r.top,o=r.width,u=r.height;t.save(),t.strokeStyle=e.color,t.lineWidth=e.lineWidth,this.translate(t,e.horizontal),t.beginPath(),t.moveTo(i,s+u),t.lineTo(i,s),t.lineTo(i+o,s),t.lineTo(i+o,s+u),e.fill&&(t.fillStyle=e.fillStyle,t.fill()),t.stroke(),t.closePath(),t.restore()},clearHit:function(e){var t=e.context,n=e.args,r=this.getBarGeometry(n.x,n.y,e),i=r.left,s=r.width,o=r.top,u=r.height,a=2*e.lineWidth;t.save(),this.translate(t,e.horizontal),t.clearRect(i-a,Math.min(o,o+u)-a,s+2*a,Math.abs(u)+2*a),t.restore()},extendXRange:function(e,t,n,r){this._extendRange(e,t,n,r),this.groups=this.groups+1||1,this.current=0},extendYRange:function(e,t,n,r){this._extendRange(e,t,n,r)},_extendRange:function(e,t,n,r){var i=e.options.max;if(_.isNumber(i)||_.isString(i))return;var s=e.min,o=e.max,u=n.horizontal,a=e.orientation,f=this.positiveSums||{},l=this.negativeSums||{},c,h,p,d;(a==1&&!u||a==-1&&u)&&n.centered&&(o=Math.max(e.datamax+n.barWidth,o),s=Math.min(e.datamin-n.barWidth,s));if(n.stacked&&(a==1&&u||a==-1&&!u))for(d=t.length;d--;)c=t[d][a==1?1:0]+"",h=t[d][a==1?0:1],h>0?(f[c]=(f[c]||0)+h,o=Math.max(o,f[c])):(l[c]=(l[c]||0)+h,s=Math.min(s,l[c]));(a==1&&u||a==-1&&!u)&&n.topPadding&&(e.max===e.datamax||n.stacked&&this.stackMax!==o)&&(o+=n.topPadding*(o-s)),this.stackMin=s,this.stackMax=o,this.negativeSums=l,this.positiveSums=f,e.max=o,e.min=s}}),Flotr.addType("bubbles",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,baseRadius:2},draw:function(e){var t=e.context,n=e.shadowSize;t.save(),t.lineWidth=e.lineWidth,t.fillStyle="rgba(0,0,0,0.05)",t.strokeStyle="rgba(0,0,0,0.05)",this.plot(e,n/2),t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,n/4),t.strokeStyle=e.color,t.fillStyle=e.fillStyle,this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i,s,o,u,a;t=t||0;for(s=0;s<n.length;++s)i=this.getGeometry(n[s],e),r.beginPath(),r.arc(i.x+t,i.y+t,i.z,0,2*Math.PI,!0),r.stroke(),e.fill&&r.fill(),r.closePath()},getGeometry:function(e,t){return{x:t.xScale(e[0]),y:t.yScale(e[1]),z:e[2]*t.baseRadius}},hit:function(e){var t=e.data,n=e.args,r=n[0],s=n[1],o=r.relX,u=r.relY,a,f,l,c;s.best=s.best||Number.MAX_VALUE;for(i=t.length;i--;)f=this.getGeometry(t[i],e),l=f.x-o,c=f.y-u,a=Math.sqrt(l*l+c*c),a<f.z&&f.z<s.best&&(s.x=t[i][0],s.y=t[i][1],s.index=i,s.seriesIndex=e.index,s.best=f.z)},drawHit:function(e){var t=e.context,n=this.getGeometry(e.data[e.args.index],e);t.save(),t.lineWidth=e.lineWidth,t.fillStyle=e.fillStyle,t.strokeStyle=e.color,t.beginPath(),t.arc(n.x,n.y,n.z,0,2*Math.PI,!0),t.fill(),t.stroke(),t.closePath(),t.restore()},clearHit:function(e){var t=e.context,n=this.getGeometry(e.data[e.args.index],e),r=n.z+e.lineWidth;t.save(),t.clearRect(n.x-r,n.y-r,2*r,2*r),t.restore()}}),Flotr.addType("candles",{options:{show:!1,lineWidth:1,wickLineWidth:1,candleWidth:.6,fill:!0,upFillColor:"#00A8F0",downFillColor:"#CB4B4B",fillOpacity:.5,barcharts:!1},draw:function(e){var t=e.context;t.save(),t.lineJoin="miter",t.lineCap="butt",t.lineWidth=e.wickLineWidth||e.lineWidth,this.plot(e),t.restore()},plot:function(e){var t=e.data,n=e.context,r=e.xScale,i=e.yScale,s=e.candleWidth/2,o=e.shadowSize,u=e.lineWidth,a=e.wickLineWidth,f=a%2/2,l,c,h,p,d,v,m,g,y,b,w,E,S,x,T,N;if(t.length<1)return;for(N=0;N<t.length;N++){c=t[N],h=c[0],d=c[1],v=c[2],m=c[3],g=c[4],y=r(h-s),b=r(h+s),w=i(m),E=i(v),S=i(Math.min(d,g)),x=i(Math.max(d,g)),l=e[d>g?"downFillColor":"upFillColor"],e.fill&&!e.barcharts&&(n.fillStyle="rgba(0,0,0,0.05)",n.fillRect(y+o,x+o,b-y,S-x),n.save(),n.globalAlpha=e.fillOpacity,n.fillStyle=l,n.fillRect(y,x+u,b-y,S-x),n.restore());if(u||a)h=Math.floor((y+b)/2)+f,n.strokeStyle=l,n.beginPath(),e.barcharts?(n.moveTo(h,Math.floor(E+u)),n.lineTo(h,Math.floor(w+u)),T=d<g,n.moveTo(T?b:y,Math.floor(x+u)),n.lineTo(h,Math.floor(x+u)),n.moveTo(h,Math.floor(S+u)),n.lineTo(T?y:b,Math.floor(S+u))):(n.strokeRect(y,x+u,b-y,S-x),n.moveTo(h,Math.floor(x+u)),n.lineTo(h,Math.floor(E+u)),n.moveTo(h,Math.floor(S+u)),n.lineTo(h,Math.floor(w+u))),n.closePath(),n.stroke()}},hit:function(e){var t=e.xScale,n=e.yScale,r=e.data,i=e.args,s=i[0],o=e.candleWidth/2,u=i[1],a=s.relX,f=s.relY,l=r.length,c,h,p,d,v,m,g,y;for(c=0;c<l;c++){h=r[c],p=h[2],d=h[3],v=t(h[0]-o),m=t(h[0]+o),y=n(d),g=n(p);if(a>v&&a<m&&f>g&&f<y){u.x=h[0],u.index=c,u.seriesIndex=e.index;return}}},drawHit:function(e){var t=e.context;t.save(),this.plot(_.defaults({fill:!!e.fillColor,upFillColor:e.color,downFillColor:e.color,data:[e.data[e.args.index]]},e)),t.restore()},clearHit:function(e){var t=e.args,n=e.context,r=e.xScale,i=e.yScale,s=e.lineWidth,o=e.candleWidth/2,u=e.data[t.index],a=r(u[0]-o)-s,f=r(u[0]+o)+s,l=i(u[2]),c=i(u[3])+s;n.clearRect(a,l,f-a,c-l)},extendXRange:function(e,t,n){e.options.max===null&&(e.max=Math.max(e.datamax+.5,e.max),e.min=Math.min(e.datamin-.5,e.min))}}),Flotr.addType("gantt",{options:{show:!1,lineWidth:2,barWidth:1,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(e){var t=this.ctx,n=e.gantt.barWidth,r=Math.min(e.gantt.lineWidth,n);t.save(),t.translate(this.plotOffset.left,this.plotOffset.top),t.lineJoin="miter",t.lineWidth=r,t.strokeStyle=e.color,t.save(),this.gantt.plotShadows(e,n,0,e.gantt.fill),t.restore();if(e.gantt.fill){var i=e.gantt.fillColor||e.color;t.fillStyle=this.processColor(i,{opacity:e.gantt.fillOpacity})}this.gantt.plot(e,n,0,e.gantt.fill),t.restore()},plot:function(e,t,n,r){var i=e.data;if(i.length<1)return;var s=e.xaxis,o=e.yaxis,u=this.ctx,a;for(a=0;a<i.length;a++){var f=i[a][0],l=i[a][1],c=i[a][2],h=!0,p=!0,d=!0;if(l===null||c===null)continue;var v=l,m=l+c,g=f-(e.gantt.centered?t/2:0),y=f+t-(e.gantt.centered?t/2:0);if(m<s.min||v>s.max||y<o.min||g>o.max)continue;v<s.min&&(v=s.min,h=!1),m>s.max&&(m=s.max,s.lastSerie!=e&&(p=!1)),g<o.min&&(g=o.min),y>o.max&&(y=o.max,o.lastSerie!=e&&(p=!1)),r&&(u.beginPath(),u.moveTo(s.d2p(v),o.d2p(g)+n),u.lineTo(s.d2p(v),o.d2p(y)+n),u.lineTo(s.d2p(m),o.d2p(y)+n),u.lineTo(s.d2p(m),o.d2p(g)+n),u.fill(),u.closePath()),e.gantt.lineWidth&&(h||d||p)&&(u.beginPath(),u.moveTo(s.d2p(v),o.d2p(g)+n),u[h?"lineTo":"moveTo"](s.d2p(v),o.d2p(y)+n),u[p?"lineTo":"moveTo"](s.d2p(m),o.d2p(y)+n),u[d?"lineTo":"moveTo"](s.d2p(m),o.d2p(g)+n),u.stroke(),u.closePath())}},plotShadows:function(e,t,n){var r=e.data;if(r.length<1)return;var i,s,o,u,a=e.xaxis,f=e.yaxis,l=this.ctx,c=this.options.shadowSize;for(i=0;i<r.length;i++){s=r[i][0],o=r[i][1],u=r[i][2];if(o===null||u===null)continue;var h=o,p=o+u,d=s-(e.gantt.centered?t/2:0),v=s+t-(e.gantt.centered?t/2:0);if(p<a.min||h>a.max||v<f.min||d>f.max)continue;h<a.min&&(h=a.min),p>a.max&&(p=a.max),d<f.min&&(d=f.min),v>f.max&&(v=f.max);var m=a.d2p(p)-a.d2p(h)-(a.d2p(p)+c<=this.plotWidth?0:c),g=f.d2p(d)-f.d2p(v)-(f.d2p(d)+c<=this.plotHeight?0:c);l.fillStyle="rgba(0,0,0,0.05)",l.fillRect(Math.min(a.d2p(h)+c,this.plotWidth),Math.min(f.d2p(v)+c,this.plotHeight),m,g)}},extendXRange:function(e){if(e.options.max===null){var t=e.min,n=e.max,r,i,s,o,u,a={},f={},l=null;for(r=0;r<this.series.length;++r){o=this.series[r],u=o.gantt;if(u.show&&o.xaxis==e){for(i=0;i<o.data.length;i++)u.show&&(y=o.data[i][0]+"",a[y]=Math.max(a[y]||0,o.data[i][1]+o.data[i][2]),l=o);for(i in a)n=Math.max(a[i],n)}}e.lastSerie=l,e.max=n,e.min=t}},extendYRange:function(e){if(e.options.max===null){var t=Number.MIN_VALUE,n=Number.MAX_VALUE,r,i,s,o,u={},a={},f=null;for(r=0;r<this.series.length;++r){s=this.series[r],o=s.gantt;if(o.show&&!s.hide&&s.yaxis==e){var l=Number.MIN_VALUE,c=Number.MAX_VALUE;for(i=0;i<s.data.length;i++)l=Math.max(l,s.data[i][0]),c=Math.min(c,s.data[i][0]);o.centered?(t=Math.max(l+.5,t),n=Math.min(c-.5,n)):(t=Math.max(l+1,t),n=Math.min(c,n)),o.barWidth+l>t&&(t=e.max+o.barWidth)}}e.lastSerie=f,e.max=t,e.min=n,e.tickSize=Flotr.getTickSize(e.options.noTicks,n,t,e.options.tickDecimals)}}}),function(){function e(e){return typeof e=="object"&&e.constructor&&(Image?!0:e.constructor===Image)}Flotr.defaultMarkerFormatter=function(e){return Math.round(e.y*100)/100+""},Flotr.addType("markers",{options:{show:!1,lineWidth:1,color:"#000000",fill:!1,fillColor:"#FFFFFF",fillOpacity:.4,stroke:!1,position:"ct",verticalMargin:0,labelFormatter:Flotr.defaultMarkerFormatter,fontSize:Flotr.defaultOptions.fontSize,stacked:!1,stackingType:"b",horizontal:!1},stack:{positive:[],negative:[],values:[]},draw:function(e){function h(e,t){return o=r.negative[e]||0,s=r.positive[e]||0,t>0?(r.positive[e]=o+t,o+t):(r.negative[e]=s+t,s+t)}var t=e.data,n=e.context,r=e.stacked?e.stack:!1,i=e.stackingType,s,o,u,a,f,l,c;n.save(),n.lineJoin="round",n.lineWidth=e.lineWidth,n.strokeStyle="rgba(0,0,0,0.5)",n.fillStyle=e.fillStyle;for(a=0;a<t.length;++a)f=t[a][0],l=t[a][1],r&&(i=="b"?e.horizontal?l=h(l,f):f=h(f,l):i=="a"&&(u=r.values[f]||0,r.values[f]=u+l,l=u+l)),c=e.labelFormatter({x:f,y:l,index:a,data:t}),this.plot(e.xScale(f),e.yScale(l),c,e);n.restore()},plot:function(t,n,r,i){var s=i.context;if(e(r)&&!r.complete)throw"Marker image not loaded.";this._plot(t,n,r,i)},_plot:function(t,n,r,i){var s=i.context,o=2,u=t,a=n,f;e(r)?f={height:r.height,width:r.width}:f=i.text.canvas(r),f.width=Math.floor(f.width+o*2),f.height=Math.floor(f.height+o*2),i.position.indexOf("c")!=-1?u-=f.width/2+o:i.position.indexOf("l")!=-1&&(u-=f.width),i.position.indexOf("m")!=-1?a-=f.height/2+o:i.position.indexOf("t")!=-1?a-=f.height+i.verticalMargin:a+=i.verticalMargin,u=Math.floor(u)+.5,a=Math.floor(a)+.5,i.fill&&s.fillRect(u,a,f.width,f.height),i.stroke&&s.strokeRect(u,a,f.width,f.height),e(r)?s.drawImage(r,parseInt(u+o,10),parseInt(a+o,10)):Flotr.drawText(s,r,u+o,a+o,{textBaseline:"top",textAlign:"left",size:i.fontSize,color:i.color})}})}(),function(){var e=Flotr._;Flotr.defaultPieLabelFormatter=function(e,t){return(100*t/e).toFixed(2)+"%"},Flotr.addType("pie",{options:{show:!1,lineWidth:1,fill:!0,fillColor:null,fillOpacity:.6,explode:6,sizeRatio:.6,startAngle:Math.PI/4,labelFormatter:Flotr.defaultPieLabelFormatter,pie3D:!1,pie3DviewAngle:Math.PI/2*.8,pie3DspliceThickness:20,epsilon:.1},draw:function(e){var t=e.data,n=e.context,r=e.lineWidth,i=e.shadowSize,s=e.sizeRatio,o=e.height,u=e.width,a=e.explode,f=e.color,l=e.fill,c=e.fillStyle,h=Math.min(u,o)*s/2,p=t[0][1],d=[],v=1,m=Math.PI*2*p/this.total,g=this.startAngle||2*Math.PI*e.startAngle,y=g+m,b=g+m/2,w=e.labelFormatter(this.total,p),E=a+h+4,S=Math.cos(b)*E,x=Math.sin(b)*E,T=S<0?"right":"left",N=x>0?"top":"bottom",C,k,L;n.save(),n.translate(u/2,o/2),n.scale(1,v),k=Math.cos(b)*a,L=Math.sin(b)*a,i>0&&(this.plotSlice(k+i,L+i,h,g,y,n),l&&(n.fillStyle="rgba(0,0,0,0.1)",n.fill())),this.plotSlice(k,L,h,g,y,n),l&&(n.fillStyle=c,n.fill()),n.lineWidth=r,n.strokeStyle=f,n.stroke(),C={size:e.fontSize*1.2,color:e.fontColor,weight:1.5},w&&(e.htmlText||!e.textEnabled?(divStyle="position:absolute;"+N+":"+(o/2+(N==="top"?x:-x))+"px;",divStyle+=T+":"+(u/2+(T==="right"?-S:S))+"px;",d.push('<div style="',divStyle,'" class="flotr-grid-label">',w,"</div>")):(C.textAlign=T,C.textBaseline=N,Flotr.drawText(n,w,S,x,C)));if(e.htmlText||!e.textEnabled){var A=Flotr.DOM.node('<div style="color:'+e.fontColor+'" class="flotr-labels"></div>');Flotr.DOM.insert(A,d.join("")),Flotr.DOM.insert(e.element,A)}n.restore(),this.startAngle=y,this.slices=this.slices||[],this.slices.push({radius:h,x:k,y:L,explode:a,start:g,end:y})},plotSlice:function(e,t,n,r,i,s){s.beginPath(),s.moveTo(e,t),s.arc(e,t,n,r,i,!1),s.lineTo(e,t),s.closePath()},hit:function(e){var t=e.data[0],n=e.args,r=e.index,i=n[0],s=n[1],o=this.slices[r],u=i.relX-e.width/2,a=i.relY-e.height/2,f=Math.sqrt(u*u+a*a),l=Math.atan(a/u),c=Math.PI*2,h=o.explode||e.explode,p=o.start%c,d=o.end%c,v=e.epsilon;u<0?l+=Math.PI:u>0&&a<0&&(l+=c),f<o.radius+h&&f>h&&(l>p&&l<d||p>d&&(l<d||l>p)||p===d&&(o.start===o.end&&Math.abs(l-p)<v||o.start!==o.end&&Math.abs(l-p)>v))&&(s.x=t[0],s.y=t[1],s.sAngle=p,s.eAngle=d,s.index=0,s.seriesIndex=r,s.fraction=t[1]/this.total)},drawHit:function(e){var t=e.context,n=this.slices[e.args.seriesIndex];t.save(),t.translate(e.width/2,e.height/2),this.plotSlice(n.x,n.y,n.radius,n.start,n.end,t),t.stroke(),t.restore()},clearHit:function(e){var t=e.context,n=this.slices[e.args.seriesIndex],r=2*e.lineWidth,i=n.radius+r;t.save(),t.translate(e.width/2,e.height/2),t.clearRect(n.x-i,n.y-i,2*i+r,2*i+r),t.restore()},extendYRange:function(e,t){this.total=(this.total||0)+t[0][1]}})}(),Flotr.addType("points",{options:{show:!1,radius:3,lineWidth:2,fill:!0,fillColor:"#FFFFFF",fillOpacity:1,hitRadius:null},draw:function(e){var t=e.context,n=e.lineWidth,r=e.shadowSize;t.save(),r>0&&(t.lineWidth=r/2,t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,r/2+t.lineWidth/2),t.strokeStyle="rgba(0,0,0,0.2)",this.plot(e,t.lineWidth/2)),t.lineWidth=e.lineWidth,t.strokeStyle=e.color,e.fill&&(t.fillStyle=e.fillStyle),this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i=e.xScale,s=e.yScale,o,u,a;for(o=n.length-1;o>-1;--o){a=n[o][1];if(a===null)continue;u=i(n[o][0]),a=s(a);if(u<0||u>e.width||a<0||a>e.height)continue;r.beginPath(),t?r.arc(u,a+t,e.radius,0,Math.PI,!1):(r.arc(u,a,e.radius,0,2*Math.PI,!0),e.fill&&r.fill()),r.stroke(),r.closePath()}}}),Flotr.addType("radar",{options:{show:!1,lineWidth:2,fill:!0,fillOpacity:.4,radiusRatio:.9,sensibility:2},draw:function(e){var t=e.context,n=e.shadowSize;t.save(),t.translate(e.width/2,e.height/2),t.lineWidth=e.lineWidth,t.fillStyle="rgba(0,0,0,0.05)",t.strokeStyle="rgba(0,0,0,0.05)",this.plot(e,n/2),t.strokeStyle="rgba(0,0,0,0.1)",this.plot(e,n/4),t.strokeStyle=e.color,t.fillStyle=e.fillStyle,this.plot(e),t.restore()},plot:function(e,t){var n=e.data,r=e.context,i=Math.min(e.height,e.width)*e.radiusRatio/2,s=2*Math.PI/n.length,o=-Math.PI/2,u,a;t=t||0,r.beginPath();for(u=0;u<n.length;++u)a=n[u][1]/this.max,r[u===0?"moveTo":"lineTo"](Math.cos(u*s+o)*i*a+t,Math.sin(u*s+o)*i*a+t);r.closePath(),e.fill&&r.fill(),r.stroke()},getGeometry:function(e,t){var n=Math.min(t.height,t.width)*t.radiusRatio/2,r=2*Math.PI/t.data.length,i=-Math.PI/2,s=e[1]/this.max;return{x:Math.cos(e[0]*r+i)*n*s+t.width/2,y:Math.sin(e[0]*r+i)*n*s+t.height/2}},hit:function(e){var t=e.args,n=t[0],r=t[1],i=n.relX,s=n.relY,o,u,a,f;for(var l=0;l<r.series.length;l++){var c=r.series[l],h=c.data;for(var p=h.length;p--;){u=this.getGeometry(h[p],e),a=u.x-i,f=u.y-s,o=Math.sqrt(a*a+f*f);if(o<e.sensibility*2)return r.x=h[p][0],r.y=h[p][1],r.index=p,r.seriesIndex=l,r}}},drawHit:function(e){var t=2*Math.PI/e.data.length,n=-Math.PI/2,r=Math.min(e.height,e.width)*e.radiusRatio/2,i=e.args.series,s=i.points.hitRadius||i.points.radius||i.mouse.radius,o=e.context;o.translate(e.width/2,e.height/2);var u=e.args.index,a=e.data[u][1]/this.max,f=Math.cos(u*t+n)*r*a,l=Math.sin(u*t+n)*r*a;o.beginPath(),o.arc(f,l,s,0,2*Math.PI,!0),o.closePath(),o.stroke()},clearHit:function(e){var t=2*Math.PI/e.data.length,n=-Math.PI/2,r=Math.min(e.height,e.width)*e.radiusRatio/2,i=e.context,s=e.args.series,o=s.points?s.points.lineWidth:1;offset=(s.points.hitRadius||s.points.radius||s.mouse.radius)+o,i.translate(e.width/2,e.height/2);var u=e.args.index,a=e.data[u][1]/this.max,f=Math.cos(u*t+n)*r*a,l=Math.sin(u*t+n)*r*a;i.clearRect(f-offset,l-offset,offset*2,offset*2)},extendYRange:function(e,t){this.max=Math.max(e.max,this.max||-Number.MAX_VALUE)}}),Flotr.addType("timeline",{options:{show:!1,lineWidth:1,barWidth:.2,fill:!0,fillColor:null,fillOpacity:.4,centered:!0},draw:function(e){var t=e.context;t.save(),t.lineJoin="miter",t.lineWidth=e.lineWidth,t.strokeStyle=e.color,t.fillStyle=e.fillStyle,this.plot(e),t.restore()},plot:function(e){var t=e.data,n=e.context,r=e.xScale,i=e.yScale,s=e.barWidth,o=e.lineWidth,u;Flotr._.each(t,function(e){var t=e[0],u=e[1],a=e[2],f=s,l=Math.ceil(r(t)),c=Math.ceil(r(t+a))-l,h=Math.round(i(u)),p=Math.round(i(u-f))-h,d=l-o/2,v=Math.round(h-p/2)-o/2;n.strokeRect(d,v,c,p),n.fillRect(d,v,c,p)})},extendRange:function(e){var t=e.data,n=e.xaxis,r=e.yaxis,i=e.timeline.barWidth;n.options.min===null&&(n.min=n.datamin-i/2);if(n.options.max===null){var s=n.max;Flotr._.each(t,function(e){s=Math.max(s,e[0]+e[2])},this),n.max=s+i/2}r.options.min===null&&(r.min=r.datamin-i),r.options.min===null&&(r.max=r.datamax+i)}}),function(){var e=Flotr.DOM;Flotr.addPlugin("crosshair",{options:{mode:null,color:"#FF0000",hideCursor:!0},callbacks:{"flotr:mousemove":function(e,t){this.options.crosshair.mode&&(this.crosshair.clearCrosshair(),this.crosshair.drawCrosshair(t))}},drawCrosshair:function(t){var n=this.octx,r=this.options.crosshair,i=this.plotOffset,s=i.left+Math.round(t.relX)+.5,o=i.top+Math.round(t.relY)+.5;if(t.relX<0||t.relY<0||t.relX>this.plotWidth||t.relY>this.plotHeight){this.el.style.cursor=null,e.removeClass(this.el,"flotr-crosshair");return}r.hideCursor&&(this.el.style.cursor="none",e.addClass(this.el,"flotr-crosshair")),n.save(),n.strokeStyle=r.color,n.lineWidth=1,n.beginPath(),r.mode.indexOf("x")!=-1&&(n.moveTo(s,i.top),n.lineTo(s,i.top+this.plotHeight)),r.mode.indexOf("y")!=-1&&(n.moveTo(i.left,o),n.lineTo(i.left+this.plotWidth,o)),n.stroke(),n.restore()},clearCrosshair:function(){var e=this.plotOffset,t=this.lastMousePos,n=this.octx;t&&(n.clearRect(Math.round(t.relX)+e.left,e.top,1,this.plotHeight+1),n.clearRect(e.left,Math.round(t.relY)+e.top,this.plotWidth+1,1))}})}(),function(){function n(e,t,n,r,i,s){var o="image/"+e,u=n.getImageData(0,0,r,i),a=new Image;return n.save(),n.globalCompositeOperation="destination-over",n.fillStyle=s,n.fillRect(0,0,r,i),a.src=t.toDataURL(o),n.restore(),n.clearRect(0,0,r,i),n.putImageData(u,0,0),a}var e=Flotr.DOM,t=Flotr._;Flotr.addPlugin("download",{saveImage:function(r,i,s,o){var u=this.options.grid,a;if(Flotr.isIE&&Flotr.isIE<9)return a="<html><body>"+this.canvas.firstChild.innerHTML+"</body></html>",window.open().document.write(a);if(r!=="jpeg"&&r!=="png")return;a=n(r,this.canvas,this.ctx,this.canvasWidth,this.canvasHeight,u&&u.backgroundColor||"#ffffff");if(!t.isElement(a)||!o)return window.open(a.src);this.download.restoreCanvas(),e.hide(this.canvas),e.hide(this.overlay),e.setStyles({position:"absolute"}),e.insert(this.el,a),this.saveImageElement=a},restoreCanvas:function(){e.show(this.canvas),e.show(this.overlay),this.saveImageElement&&this.el.removeChild(this.saveImageElement),this.saveImageElement=null}})}(),function(){var e=Flotr.EventAdapter,t=Flotr._;Flotr.addPlugin("graphGrid",{callbacks:{"flotr:beforedraw":function(){this.graphGrid.drawGrid()},"flotr:afterdraw":function(){this.graphGrid.drawOutline()}},drawGrid:function(){function v(e){for(p=0;p<e.length;++p){var t=e[p].v/c.max;for(d=0;d<=w;++d)n[d===0?"moveTo":"lineTo"](Math.cos(d*S+x)*b*t,Math.sin(d*S+x)*b*t)}}function m(e,r){t.each(t.pluck(e,"v"),function(e){if(e<=c.min||e>=c.max||(e==c.min||e==c.max)&&i.outlineWidth)return;r(Math.floor(c.d2p(e))+n.lineWidth/2)})}function g(e){n.moveTo(e,0),n.lineTo(e,f)}function y(e){n.moveTo(0,e),n.lineTo(l,e)}var n=this.ctx,r=this.options,i=r.grid,s=i.verticalLines,o=i.horizontalLines,u=i.minorVerticalLines,a=i.minorHorizontalLines,f=this.plotHeight,l=this.plotWidth,c,h,p,d;(s||u||o||a)&&e.fire(this.el,"flotr:beforegrid",[this.axes.x,this.axes.y,r,this]),n.save(),n.lineWidth=1,n.strokeStyle=i.tickColor;if(i.circular){n.translate(this.plotOffset.left+l/2,this.plotOffset.top+f/2);var b=Math.min(f,l)*r.radar.radiusRatio/2,w=this.axes.x.ticks.length,S=2*(Math.PI/w),x=-Math.PI/2;n.beginPath(),c=this.axes.y,o&&v(c.ticks),a&&v(c.minorTicks),s&&t.times(w,function(e){n.moveTo(0,0),n.lineTo(Math.cos(e*S+x)*b,Math.sin(e*S+x)*b)}),n.stroke()}else n.translate(this.plotOffset.left,this.plotOffset.top),i.backgroundColor&&(n.fillStyle=this.processColor(i.backgroundColor,{x1:0,y1:0,x2:l,y2:f}),n.fillRect(0,0,l,f)),n.beginPath(),c=this.axes.x,s&&m(c.ticks,g),u&&m(c.minorTicks,g),c=this.axes.y,o&&m(c.ticks,y),a&&m(c.minorTicks,y),n.stroke();n.restore(),(s||u||o||a)&&e.fire(this.el,"flotr:aftergrid",[this.axes.x,this.axes.y,r,this])},drawOutline:function(){var e=this,t=e.options,n=t.grid,r=n.outline,s=e.ctx,o=n.backgroundImage,u=e.plotOffset,a=u.left,f=u.top,l=e.plotWidth,c=e.plotHeight,h,p,d,v,m,g;if(!n.outlineWidth)return;s.save();if(n.circular){s.translate(a+l/2,f+c/2);var y=Math.min(c,l)*t.radar.radiusRatio/2,b=this.axes.x.ticks.length,w=2*(Math.PI/b),E=-Math.PI/2;s.beginPath(),s.lineWidth=n.outlineWidth,s.strokeStyle=n.color,s.lineJoin="round";for(i=0;i<=b;++i)s[i===0?"moveTo":"lineTo"](Math.cos(i*w+E)*y,Math.sin(i*w+E)*y);s.stroke()}else{s.translate(a,f);var S=n.outlineWidth,x=.5-S+(S+1)%2/2,T="lineTo",N="moveTo";s.lineWidth=S,s.strokeStyle=n.color,s.lineJoin="miter",s.beginPath(),s.moveTo(x,x),l-=S/2%1,c+=S/2,s[r.indexOf("n")!==-1?T:N](l,x),s[r.indexOf("e")!==-1?T:N](l,c),s[r.indexOf("s")!==-1?T:N](x,c),s[r.indexOf("w")!==-1?T:N](x,x),s.stroke(),s.closePath()}s.restore(),o&&(d=o.src||o,v=(parseInt(o.left,10)||0)+u.left,m=(parseInt(o.top,10)||0)+u.top,p=new Image,p.onload=function(){s.save(),o.alpha&&(s.globalAlpha=o.alpha),s.globalCompositeOperation="destination-over",s.drawImage(p,0,0,p.width,p.height,v,m,l,c),s.restore()},p.src=d)}})}(),function(){var e=Flotr.DOM,t=Flotr._,n=Flotr,r="opacity:0.7;background-color:#000;color:#fff;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;";Flotr.addPlugin("hit",{callbacks:{"flotr:mousemove":function(e,t){this.hit.track(t)},"flotr:click":function(e){var n=this.hit.track(e);n&&!t.isUndefined(n.index)&&(e.hit=n)},"flotr:mouseout":function(e){e.relatedTarget!==this.mouseTrack&&this.hit.clearHit()},"flotr:destroy":function(){this.options.mouse.container&&e.remove(this.mouseTrack),this.mouseTrack=null}},track:function(e){if(this.options.mouse.track||t.any(this.series,function(e){return e.mouse&&e.mouse.track}))return this.hit.hit(e)},executeOnType:function(e,r,i){function u(e,u){t.each(t.keys(n.graphTypes),function(t){e[t]&&e[t].show&&!e.hide&&this[t][r]&&(o=this.getOptions(e,t),o.fill=!!e.mouse.fillColor,o.fillStyle=this.processColor(e.mouse.fillColor||"#ffffff",{opacity:e.mouse.fillOpacity}),o.color=e.mouse.lineColor,o.context=this.octx,o.index=u,i&&(o.args=i),this[t][r].call(this[t],o),s=!0)},this)}var s=!1,o;return t.isArray(e)||(e=[e]),t.each(e,u,this),s},drawHit:function(e){var t=this.octx,n=e.series;if(n.mouse.lineColor){t.save(),t.lineWidth=n.points?n.points.lineWidth:1,t.strokeStyle=n.mouse.lineColor,t.fillStyle=this.processColor(n.mouse.fillColor||"#ffffff",{opacity:n.mouse.fillOpacity}),t.translate(this.plotOffset.left,this.plotOffset.top);if(!this.hit.executeOnType(n,"drawHit",e)){var r=e.xaxis,i=e.yaxis;t.beginPath(),t.arc(r.d2p(e.x),i.d2p(e.y),n.points.hitRadius||n.points.radius||n.mouse.radius,0,2*Math.PI,!0),t.fill(),t.stroke(),t.closePath()}t.restore(),this.clip(t)}this.prevHit=e},clearHit:function(){var t=this.prevHit,n=this.octx,r=this.plotOffset;n.save(),n.translate(r.left,r.top);if(t){if(!this.hit.executeOnType(t.series,"clearHit",this.prevHit)){var i=t.series,s=i.points?i.points.lineWidth:1;offset=(i.points.hitRadius||i.points.radius||i.mouse.radius)+s,n.clearRect(t.xaxis.d2p(t.x)-offset,t.yaxis.d2p(t.y)-offset,offset*2,offset*2)}e.hide(this.mouseTrack),this.prevHit=null}n.restore()},hit:function(e){var n=this.options,r=this.prevHit,i,s,o,u,a,f,l,c,h;if(this.series.length===0)return;h={relX:e.relX,relY:e.relY,absX:e.absX,absY:e.absY,series:this.series};if(n.mouse.trackY&&!n.mouse.trackAll&&this.hit.executeOnType(this.series,"hit",[e,h])&&!t.isUndefined(h.seriesIndex))a=this.series[h.seriesIndex],h.series=a,h.mouse=a.mouse,h.xaxis=a.xaxis,h.yaxis=a.yaxis;else{i=this.hit.closest(e);if(i){i=n.mouse.trackY?i.point:i.x,u=i.seriesIndex,a=this.series[u],l=a.xaxis,c=a.yaxis,s=2*a.mouse.sensibility;if(n.mouse.trackAll||i.distanceX<s/l.scale&&(!n.mouse.trackY||i.distanceY<s/c.scale))h.series=a,h.xaxis=a.xaxis,h.yaxis=a.yaxis,h.mouse=a.mouse,h.x=i.x,h.y=i.y,h.dist=i.distance,h.index=i.dataIndex,h.seriesIndex=u}}if(!r||r.index!==h.index||r.seriesIndex!==h.seriesIndex)this.hit.clearHit(),h.series&&h.mouse&&h.mouse.track&&(this.hit.drawMouseTrack(h),this.hit.drawHit(h),Flotr.EventAdapter.fire(this.el,"flotr:hit",[h,this]));return h},closest:function(e){function E(e){e.distance=h,e.distanceX=p,e.distanceY=d,e.seriesIndex=b,e.dataIndex=w,e.x=g,e.y=y,f=!0}var t=this.series,n=this.options,r=e.relX,i=e.relY,s=Number.MAX_VALUE,o=Number.MAX_VALUE,u={},a={},f=!1,l,c,h,p,d,v,m,g,y,b,w;for(b=0;b<t.length;b++){l=t[b],c=l.data,v=l.xaxis.p2d(r),m=l.yaxis.p2d(i);if(l.hide)continue;for(w=c.length;w--;){g=c[w][0],y=c[w][1],c[w].y0&&(y+=c[w].y0);if(g===null||y===null)continue;if(g<l.xaxis.min||g>l.xaxis.max)continue;p=Math.abs(g-v),d=Math.abs(y-m),h=p*p+d*d,h<s&&(s=h,E(u)),p<o&&(o=p,E(a))}}return f?{point:u,x:a}:!1},drawMouseTrack:function(n){var i="",s=n.series,o=n.mouse.position,u=n.mouse.margin,a=n.x,f=n.y,l=r,c=this.mouseTrack,h=this.plotOffset,p=h.left,d=h.right,v=h.bottom,m=h.top,g=n.mouse.trackDecimals,y=this.options,b=y.mouse.container,w=0,E=0,S,x,T;c||(c=e.node('<div class="flotr-mouse-value" style="'+l+'"></div>'),this.mouseTrack=c,e.insert(b||this.el,c));if(!g||g<0)g=0;a&&a.toFixed&&(a=a.toFixed(g)),f&&f.toFixed&&(f=f.toFixed(g)),T=n.mouse.trackFormatter({x:a,y:f,series:n.series,index:n.index,nearest:n,fraction:n.fraction});if(t.isNull(T)||t.isUndefined(T)){e.hide(c);return}c.innerHTML=T,e.show(c);if(!o)return;x=e.size(c),b&&(S=e.position(this.el),w=S.top,E=S.left);if(!n.mouse.relative)i+="top:",o.charAt(0)=="n"?i+=w+u+m:o.charAt(0)=="s"&&(i+=w-u+m+this.plotHeight-x.height),i+="px;bottom:auto;left:",o.charAt(1)=="e"?i+=E-u+p+this.plotWidth-x.width:o.charAt(1)=="w"&&(i+=E+u+p),i+="px;right:auto;";else if(s.pie&&s.pie.show){var N={x:this.plotWidth/2,y:this.plotHeight/2},C=Math.min(this.canvasWidth,this.canvasHeight)*s.pie.sizeRatio/2,k=n.sAngle<n.eAngle?(n.sAngle+n.eAngle)/2:(n.sAngle+n.eAngle+2*Math.PI)/2;i+="bottom:"+(u-m-N.y-Math.sin(k)*C/2+this.canvasHeight)+"px;top:auto;",i+="left:"+(u+p+N.x+Math.cos(k)*C/2)+"px;right:auto;"}else i+="top:",/n/.test(o)?i+=w-u+m+n.yaxis.d2p(n.y)-x.height:i+=w+u+m+n.yaxis.d2p(n.y),i+="px;bottom:auto;left:",/w/.test(o)?i+=E-u+p+n.xaxis.d2p(n.x)-x.width:i+=E+u+p+n.xaxis.d2p(n.x),i+="px;right:auto;";c.style.cssText=l+i,n.mouse.relative&&(/[ew]/.test(o)?/[ns]/.test(o)||(c.style.top=w+m+n.yaxis.d2p(n.y)-e.size(c).height/2+"px"):c.style.left=E+p+n.xaxis.d2p(n.x)-e.size(c).width/2+"px")}})}(),function(){function e(e,t){return e.which?e.which===1:e.button===0||e.button===1}function t(e,t){return Math.min(Math.max(0,e),t.plotWidth-1)}function n(e,t){return Math.min(Math.max(0,e),t.plotHeight)}var r=Flotr.DOM,i=Flotr.EventAdapter,s=Flotr._;Flotr.addPlugin("selection",{options:{pinchOnly:null,mode:null,color:"#B6D9FF",fps:20},callbacks:{"flotr:mouseup":function(e){var t=this.options.selection,n=this.selection,r=this.getEventPosition(e);if(!t||!t.mode)return;n.interval&&clearInterval(n.interval),this.multitouches?n.updateSelection():t.pinchOnly||n.setSelectionPos(n.selection.second,r),n.clearSelection(),n.selecting&&n.selectionIsSane()&&(n.drawSelection(),n.fireSelectEvent(),this.ignoreClick=!0)},"flotr:mousedown":function(t){var n=this.options.selection,r=this.selection,i=this.getEventPosition(t);if(!n||!n.mode)return;if(!n.mode||!e(t)&&s.isUndefined(t.touches))return;n.pinchOnly||r.setSelectionPos(r.selection.first,i),r.interval&&clearInterval(r.interval),this.lastMousePos.pageX=null,r.selecting=!1,r.interval=setInterval(s.bind(r.updateSelection,this),1e3/n.fps)},"flotr:destroy":function(e){clearInterval(this.selection.interval)}},getArea:function(){var e=this.selection.selection,t=this.axes,n=e.first,r=e.second,i,s,o,u;return i=t.x.p2d(e.first.x),s=t.x.p2d(e.second.x),o=t.y.p2d(e.first.y),u=t.y.p2d(e.second.y),{x1:Math.min(i,s),y1:Math.min(o,u),x2:Math.max(i,s),y2:Math.max(o,u),xfirst:i,xsecond:s,yfirst:o,ysecond:u}},selection:{first:{x:-1,y:-1},second:{x:-1,y:-1}},prevSelection:null,interval:null,fireSelectEvent:function(e){var t=this.selection.getArea();e=e||"select",t.selection=this.selection.selection,i.fire(this.el,"flotr:"+e,[t,this])},setSelection:function(e,r){var i=this.options,s=this.axes.x,o=this.axes.y,u=o.scale,a=s.scale,f=i.selection.mode.indexOf("x")!=-1,l=i.selection.mode.indexOf("y")!=-1,c=this.selection.selection;this.selection.clearSelection(),c.first.y=n(f&&!l?0:(o.max-e.y1)*u,this),c.second.y=n(f&&!l?this.plotHeight-1:(o.max-e.y2)*u,this),c.first.x=t(l&&!f?0:(e.x1-s.min)*a,this),c.second.x=t(l&&!f?this.plotWidth:(e.x2-s.min)*a,this),this.selection.drawSelection(),r||this.selection.fireSelectEvent()},setSelectionPos:function(e,r){var i=this.options.selection.mode,s=this.selection.selection;i.indexOf("x")==-1?e.x=e==s.first?0:this.plotWidth:e.x=t(r.relX,this),i.indexOf("y")==-1?e.y=e==s.first?0:this.plotHeight-1:e.y=n(r.relY,this)},drawSelection:function(){this.selection.fireSelectEvent("selecting");var e=this.selection.selection,t=this.octx,n=this.options,r=this.plotOffset,i=this.selection.prevSelection;if(i&&e.first.x==i.first.x&&e.first.y==i.first.y&&e.second.x==i.second.x&&e.second.y==i.second.y)return;t.save(),t.strokeStyle=this.processColor(n.selection.color,{opacity:.8}),t.lineWidth=1,t.lineJoin="miter",t.fillStyle=this.processColor(n.selection.color,{opacity:.4}),this.selection.prevSelection={first:{x:e.first.x,y:e.first.y},second:{x:e.second.x,y:e.second.y}};var s=Math.min(e.first.x,e.second.x),o=Math.min(e.first.y,e.second.y),u=Math.abs(e.second.x-e.first.x),a=Math.abs(e.second.y-e.first.y);t.fillRect(s+r.left+.5,o+r.top+.5,u,a),t.strokeRect(s+r.left+.5,o+r.top+.5,u,a),t.restore()},updateSelection:function(){if(!this.lastMousePos.pageX)return;this.selection.selecting=!0;if(this.multitouches)this.selection.setSelectionPos(this.selection.selection.first,this.getEventPosition(this.multitouches[0])),this.selection.setSelectionPos(this.selection.selection.second,this.getEventPosition(this.multitouches[1]));else{if(this.options.selection.pinchOnly)return;this.selection.setSelectionPos(this.selection.selection.second,this.lastMousePos)}this.selection.clearSelection(),this.selection.selectionIsSane()&&this.selection.drawSelection()},clearSelection:function(){if(!this.selection.prevSelection)return;var e=this.selection.prevSelection,t=1,n=this.plotOffset,r=Math.min(e.first.x,e.second.x),i=Math.min(e.first.y,e.second.y),s=Math.abs(e.second.x-e.first.x),o=Math.abs(e.second.y-e.first.y);this.octx.clearRect(r+n.left-t+.5,i+n.top-t,s+2*t+.5,o+2*t+.5),this.selection.prevSelection=null},selectionIsSane:function(){var e=this.selection.selection;return Math.abs(e.second.x-e.first.x)>=5||Math.abs(e.second.y-e.first.y)>=5}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("labels",{callbacks:{"flotr:afterdraw":function(){this.labels.draw()}},draw:function(){function b(e,t,r){var i=r?t.minorTicks:t.ticks,s=t.orientation===1,u=t.n===1,l,h;l={color:t.options.color||d.grid.color,angle:Flotr.toRad(t.options.labelsAngle),textBaseline:"middle"};for(c=0;c<i.length&&(r?t.options.showMinorLabels:t.options.showLabels);++c){n=i[c],n.label+="";if(!n.label||!n.label.length)continue;x=Math.cos(c*a+f)*o,y=Math.sin(c*a+f)*o,l.textAlign=s?Math.abs(x)<.1?"center":x<0?"right":"left":"left",Flotr.drawText(v,n.label,s?x:3,s?y:-(t.ticks[c].v/t.max)*(o-d.fontSize),l)}}function w(e,t,r,i){function f(e){return e.options.showLabels&&e.used}function l(e,t,n,r){return e.plotOffset.left+(t?r:n?-d.grid.labelMargin:d.grid.labelMargin+e.plotWidth)}function h(e,t,n,r){return e.plotOffset.top+(t?d.grid.labelMargin:r)+(t&&n?e.plotHeight:0)}var s=t.orientation===1,o=t.n===1,u,a;u={color:t.options.color||d.grid.color,textAlign:r,textBaseline:i,angle:Flotr.toRad(t.options.labelsAngle)},u=Flotr.getBestTextAlign(u.angle,u);for(c=0;c<t.ticks.length&&f(t);++c){n=t.ticks[c];if(!n.label||!n.label.length)continue;a=t.d2p(n.v);if(a<0||a>(s?e.plotWidth:e.plotHeight))continue;Flotr.drawText(v,n.label,l(e,s,o,a),h(e,s,o,a),u),!s&&!o&&(v.save(),v.strokeStyle=u.color,v.beginPath(),v.moveTo(e.plotOffset.left+e.plotWidth-8,e.plotOffset.top+t.d2p(n.v)),v.lineTo(e.plotOffset.left+e.plotWidth,e.plotOffset.top+t.d2p(n.v)),v.stroke(),v.restore())}}function E(e,t){var r=t.orientation===1,i=t.n===1,o="",u,a,f,l=e.plotOffset;!r&&!i&&(v.save(),v.strokeStyle=t.options.color||d.grid.color,v.beginPath());if(t.options.showLabels&&(i?!0:t.used))for(c=0;c<t.ticks.length;++c){n=t.ticks[c];if(!n.label||!n.label.length||(r?l.left:l.top)+t.d2p(n.v)<0||(r?l.left:l.top)+t.d2p(n.v)>(r?e.canvasWidth:e.canvasHeight))continue;f=l.top+(r?(i?1:-1)*(e.plotHeight+d.grid.labelMargin):t.d2p(n.v)-t.maxLabel.height/2),u=r?l.left+t.d2p(n.v)-s/2:0,o="",c===0?o=" first":c===t.ticks.length-1&&(o=" last"),o+=r?" flotr-grid-label-x":" flotr-grid-label-y",h+=['<div style="position:absolute; text-align:'+(r?"center":"right")+"; ","top:"+f+"px; ",(!r&&!i?"right:":"left:")+u+"px; ","width:"+(r?s:(i?l.left:l.right)-d.grid.labelMargin)+"px; ",t.options.color?"color:"+t.options.color+"; ":" ",'" class="flotr-grid-label'+o+'">'+n.label+"</div>"].join(" "),!r&&!i&&(v.moveTo(l.left+e.plotWidth-8,l.top+t.d2p(n.v)),v.lineTo(l.left+e.plotWidth,l.top+t.d2p(n.v)))}}var t,n,r,i,s,o,u,a,f,l,c,h="",p=0,d=this.options,v=this.ctx,m=this.axes,g={size:d.fontSize};for(c=0;c<m.x.ticks.length;++c)m.x.ticks[c].label&&++p;s=this.plotWidth/p,d.grid.circular&&(v.save(),v.translate(this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+this.plotHeight/2),o=this.plotHeight*d.radar.radiusRatio/2+d.fontSize,u=this.axes.x.ticks.length,a=2*(Math.PI/u),f=-Math.PI/2,b(this,m.x,!1),b(this,m.x,!0),b(this,m.y,!1),b(this,m.y,!0),v.restore()),!d.HtmlText&&this.textEnabled?(w(this,m.x,"center","top"),w(this,m.x2,"center","bottom"),w(this,m.y,"right","middle"),w(this,m.y2,"left","middle")):(m.x.options.showLabels||m.x2.options.showLabels||m.y.options.showLabels||m.y2.options.showLabels)&&!d.grid.circular&&(h="",E(this,m.x),E(this,m.x2),E(this,m.y),E(this,m.y2),v.stroke(),v.restore(),l=e.create("div"),e.setStyles(l,{fontSize:"smaller",color:d.grid.color}),l.className="flotr-labels",e.insert(this.el,l),e.insert(l,h))}})}(),function(){var e=Flotr.DOM,t=Flotr._;Flotr.addPlugin("legend",{options:{show:!0,noColumns:1,labelFormatter:function(e){return e},labelBoxBorderColor:"#CCCCCC",labelBoxWidth:14,labelBoxHeight:10,labelBoxMargin:5,container:null,position:"nw",margin:5,backgroundColor:"#F0F0F0",backgroundOpacity:.85},callbacks:{"flotr:afterinit":function(){this.legend.insertLegend()},"flotr:destroy":function(){var t=this.legend.markup;t&&(this.legend.markup=null,e.remove(t))}},insertLegend:function(){if(!this.options.legend.show)return;var n=this.series,r=this.plotOffset,i=this.options,s=i.legend,o=[],u=!1,a=this.ctx,f=t.filter(n,function(e){return e.label&&!e.hide}).length,l=s.position,c=s.margin,h=s.backgroundOpacity,p,d,v;if(f){var m=s.labelBoxWidth,g=s.labelBoxHeight,y=s.labelBoxMargin,b=r.left+c,w=r.top+c,E=0,S={size:i.fontSize*1.1,color:i.grid.color};for(p=n.length-1;p>-1;--p){if(!n[p].label||n[p].hide)continue;d=s.labelFormatter(n[p].label),E=Math.max(E,this._text.measureText(d,S).width)}var x=Math.round(m+y*3+E),T=Math.round(f*(y+g)+y);!h&&h!==0&&(h=.1);if(!i.HtmlText&&this.textEnabled&&!s.container){l.charAt(0)=="s"&&(w=r.top+this.plotHeight-(c+T)),l.charAt(0)=="c"&&(w=r.top+this.plotHeight/2-(c+T/2)),l.charAt(1)=="e"&&(b=r.left+this.plotWidth-(c+x)),v=this.processColor(s.backgroundColor,{opacity:h}),a.fillStyle=v,a.fillRect(b,w,x,T),a.strokeStyle=s.labelBoxBorderColor,a.strokeRect(Flotr.toPixel(b),Flotr.toPixel(w),x,T);var N=b+y,C=w+y;for(p=0;p<n.length;p++){if(!n[p].label||n[p].hide)continue;d=s.labelFormatter(n[p].label),a.fillStyle=n[p].color,a.fillRect(N,C,m-1,g-1),a.strokeStyle=s.labelBoxBorderColor,a.lineWidth=1,a.strokeRect(Math.ceil(N)-1.5,Math.ceil(C)-1.5,m+2,g+2),Flotr.drawText(a,d,N+m+y,C+g,S),C+=g+y}}else{for(p=0;p<n.length;++p){if(!n[p].label||n[p].hide)continue;p%s.noColumns===0&&(o.push(u?"</tr><tr>":"<tr>"),u=!0);var k=n[p],L=s.labelBoxWidth,A=s.labelBoxHeight;d=s.labelFormatter(k.label),v="background-color:"+(k.bars&&k.bars.show&&k.bars.fillColor&&k.bars.fill?k.bars.fillColor:k.color)+";",o.push('<td class="flotr-legend-color-box">','<div style="border:1px solid ',s.labelBoxBorderColor,';padding:1px">','<div style="width:',L-1,"px;height:",A-1,"px;border:1px solid ",n[p].color,'">','<div style="width:',L,"px;height:",A,"px;",v,'"></div>',"</div>","</div>","</td>",'<td class="flotr-legend-label">',d,"</td>")}u&&o.push("</tr>");if(o.length>0){var O='<table style="font-size:smaller;color:'+i.grid.color+'">'+o.join("")+"</table>";if(s.container)O=e.node(O),this.legend.markup=O,e.insert(s.container,O);else{var M={position:"absolute",zIndex:"2",border:"1px solid "+s.labelBoxBorderColor};l.charAt(0)=="n"?(M.top=c+r.top+"px",M.bottom="auto"):l.charAt(0)=="c"?(M.top=c+(this.plotHeight-T)/2+"px",M.bottom="auto"):l.charAt(0)=="s"&&(M.bottom=c+r.bottom+"px",M.top="auto"),l.charAt(1)=="e"?(M.right=c+r.right+"px",M.left="auto"):l.charAt(1)=="w"&&(M.left=c+r.left+"px",M.right="auto");var P=e.create("div"),H;P.className="flotr-legend",e.setStyles(P,M),e.insert(P,O),e.insert(this.el,P);if(!h)return;var B=s.backgroundColor||i.grid.backgroundColor||"#ffffff";t.extend(M,e.size(P),{backgroundColor:B,zIndex:"",border:""}),M.width+="px",M.height+="px",P=e.create("div"),P.className="flotr-legend-bg",e.setStyles(P,M),e.opacity(P,h),e.insert(P," "),e.insert(this.el,P)}}}}}})}(),function(){function e(e){if(this.options.spreadsheet.tickFormatter)return this.options.spreadsheet.tickFormatter(e);var t=n.find(this.axes.x.ticks,function(t){return t.v==e});return t?t.label:e}var t=Flotr.DOM,n=Flotr._;Flotr.addPlugin("spreadsheet",{options:{show:!1,tabGraphLabel:"Graph",tabDataLabel:"Data",toolbarDownload:"Download CSV",toolbarSelectAll:"Select all",csvFileSeparator:",",decimalSeparator:".",tickFormatter:null,initialTab:"graph"},callbacks:{"flotr:afterconstruct":function(){if(!this.options.spreadsheet.show)return;var e=this.spreadsheet,n=t.node('<div class="flotr-tabs-group" style="position:absolute;left:0px;width:'+this.canvasWidth+'px"></div>'),r=t.node('<div style="float:left" class="flotr-tab selected">'+this.options.spreadsheet.tabGraphLabel+"</div>"),i=t.node('<div style="float:left" class="flotr-tab">'+this.options.spreadsheet.tabDataLabel+"</div>"),s;e.tabsContainer=n,e.tabs={graph:r,data:i},t.insert(n,r),t.insert(n,i),t.insert(this.el,n),s=t.size(i).height+2,this.plotOffset.bottom+=s,t.setStyles(n,{top:this.canvasHeight-s+"px"}),this.observe(r,"click",function(){e.showTab("graph")}).observe(i,"click",function(){e.showTab("data")}),this.options.spreadsheet.initialTab!=="graph"&&e.showTab(this.options.spreadsheet.initialTab)}},loadDataGrid:function(){if(this.seriesData)return this.seriesData;var e=this.series,t={};return n.each(e,function(e,r){n.each(e.data,function(e){var n=e[0],s=e[1],o=t[n];if(o)o[r+1]=s;else{var u=[];u[0]=n,u[r+1]=s,t[n]=u}})}),this.seriesData=n.sortBy(t,function(e,t){return parseInt(t,10)}),this.seriesData},constructDataGrid:function(){if(this.spreadsheet.datagrid)return this.spreadsheet.datagrid;var r=this.series,i=this.spreadsheet.loadDataGrid(),s=["<colgroup><col />"],o,u,a,f=['<table class="flotr-datagrid"><tr class="first-row">'];f.push("<th> </th>"),n.each(r,function(e,t){f.push('<th scope="col">'+(e.label||String.fromCharCode(65+t))+"</th>"),s.push("<col />")}),f.push("</tr>"),n.each(i,function(t){f.push("<tr>"),n.times(r.length+1,function(r){var i="td",s=t[r],o=n.isUndefined(s)?"":Math.round(s*1e5)/1e5;if(r===0){i="th";var u=e.call(this,o);u&&(o=u)}f.push("<"+i+(i=="th"?' scope="row"':"")+">"+o+"</"+i+">")},this),f.push("</tr>")},this),s.push("</colgroup>"),a=t.node(f.join("")),o=t.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarDownload+"</button>"),u=t.node('<button type="button" class="flotr-datagrid-toolbar-button">'+this.options.spreadsheet.toolbarSelectAll+"</button>"),this.observe(o,"click",n.bind(this.spreadsheet.downloadCSV,this)).observe(u,"click",n.bind(this.spreadsheet.selectAllData,this));var l=t.node('<div class="flotr-datagrid-toolbar"></div>');t.insert(l,o),t.insert(l,u);var c=this.canvasHeight-t.size(this.spreadsheet.tabsContainer).height-2,h=t.node('<div class="flotr-datagrid-container" style="position:absolute;left:0px;top:0px;width:'+this.canvasWidth+"px;height:"+c+'px;overflow:auto;z-index:10"></div>');return t.insert(h,l),t.insert(h,a),t.insert(this.el,h),this.spreadsheet.datagrid=a,this.spreadsheet.container=h,a},showTab:function(e){if(this.spreadsheet.activeTab===e)return;switch(e){case"graph":t.hide(this.spreadsheet.container),t.removeClass(this.spreadsheet.tabs.data,"selected"),t.addClass(this.spreadsheet.tabs.graph,"selected");break;case"data":this.spreadsheet.datagrid||this.spreadsheet.constructDataGrid(),t.show(this.spreadsheet.container),t.addClass(this.spreadsheet.tabs.data,"selected"),t.removeClass(this.spreadsheet.tabs.graph,"selected");break;default:throw"Illegal tab name: "+e}this.spreadsheet.activeTab=e},selectAllData:function(){if(this.spreadsheet.tabs){var e,t,n,r,i=this.spreadsheet.constructDataGrid();return this.spreadsheet.showTab("data"),setTimeout(function(){(n=i.ownerDocument)&&(r=n.defaultView)&&r.getSelection&&n.createRange&&(e=window.getSelection())&&e.removeAllRanges?(t=n.createRange(),t.selectNode(i),e.removeAllRanges(),e.addRange(t)):document.body&&document.body.createTextRange&&(t=document.body.createTextRange())&&(t.moveToElementText(i),t.select())},0),!0}return!1},downloadCSV:function(){var t="",r=this.series,i=this.options,s=this.spreadsheet.loadDataGrid(),o=encodeURIComponent(i.spreadsheet.csvFileSeparator);if(i.spreadsheet.decimalSeparator===i.spreadsheet.csvFileSeparator)throw"The decimal separator is the same as the column separator ("+i.spreadsheet.decimalSeparator+")";n.each(r,function(e,n){t+=o+'"'+(e.label||String.fromCharCode(65+n)).replace(/\"/g,'\\"')+'"'}),t+="%0D%0A",t+=n.reduce(s,function(t,n){var r=e.call(this,n[0])||"";r='"'+(r+"").replace(/\"/g,'\\"')+'"';var s=n.slice(1).join(o);return i.spreadsheet.decimalSeparator!=="."&&(s=s.replace(/\./g,i.spreadsheet.decimalSeparator)),t+r+o+s+"%0D%0A"},"",this),Flotr.isIE&&Flotr.isIE<9?(t=t.replace(new RegExp(o,"g"),decodeURIComponent(o)).replace(/%0A/g,"\n").replace(/%0D/g,"\r"),window.open().document.write(t)):window.open("data:text/csv,"+t)}})}(),function(){var e=Flotr.DOM;Flotr.addPlugin("titles",{callbacks:{"flotr:afterdraw":function(){this.titles.drawTitles()}},drawTitles:function(){var t,n=this.options,r=n.grid.labelMargin,i=this.ctx,s=this.axes;if(!n.HtmlText&&this.textEnabled){var o={size:n.fontSize,color:n.grid.color,textAlign:"center"};n.subtitle&&Flotr.drawText(i,n.subtitle,this.plotOffset.left+this.plotWidth/2,this.titleHeight+this.subtitleHeight-2,o),o.weight=1.5,o.size*=1.5,n.title&&Flotr.drawText(i,n.title,this.plotOffset.left+this.plotWidth/2,this.titleHeight-2,o),o.weight=1.8,o.size*=.8,s.x.options.title&&s.x.used&&(o.textAlign=s.x.options.titleAlign||"center",o.textBaseline="top",o.angle=Flotr.toRad(s.x.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top+s.x.maxLabel.height+this.plotHeight+2*r,o)),s.x2.options.title&&s.x2.used&&(o.textAlign=s.x2.options.titleAlign||"center",o.textBaseline="bottom",o.angle=Flotr.toRad(s.x2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.x2.options.title,this.plotOffset.left+this.plotWidth/2,this.plotOffset.top-s.x2.maxLabel.height-2*r,o)),s.y.options.title&&s.y.used&&(o.textAlign=s.y.options.titleAlign||"right",o.textBaseline="middle",o.angle=Flotr.toRad(s.y.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y.options.title,this.plotOffset.left-s.y.maxLabel.width-2*r,this.plotOffset.top+this.plotHeight/2,o)),s.y2.options.title&&s.y2.used&&(o.textAlign=s.y2.options.titleAlign||"left",o.textBaseline="middle",o.angle=Flotr.toRad(s.y2.options.titleAngle),o=Flotr.getBestTextAlign(o.angle,o),Flotr.drawText(i,s.y2.options.title,this.plotOffset.left+this.plotWidth+s.y2.maxLabel.width+2*r,this.plotOffset.top+this.plotHeight/2,o))}else{t=[],n.title&&t.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;font-size:1em;font-weight:bold;text-align:center;width:",this.plotWidth,'px;" class="flotr-title">',n.title,"</div>"),n.subtitle&&t.push('<div style="position:absolute;top:',this.titleHeight,"px;left:",this.plotOffset.left,"px;font-size:smaller;text-align:center;width:",this.plotWidth,'px;" class="flotr-subtitle">',n.subtitle,"</div>"),t.push("</div>"),t.push('<div class="flotr-axis-title" style="font-weight:bold;">'),s.x.options.title&&s.x.used&&t.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight+n.grid.labelMargin+s.x.titleSize.height,"px;left:",this.plotOffset.left,"px;width:",this.plotWidth,"px;text-align:",s.x.options.titleAlign,';" class="flotr-axis-title flotr-axis-title-x1">',s.x.options.title,"</div>"),s.x2.options.title&&s.x2.used&&t.push('<div style="position:absolute;top:0;left:',this.plotOffset.left,"px;width:",this.plotWidth,"px;text-align:",s.x2.options.titleAlign,';" class="flotr-axis-title flotr-axis-title-x2">',s.x2.options.title,"</div>"),s.y.options.title&&s.y.used&&t.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-s.y.titleSize.height/2,"px;left:0;text-align:",s.y.options.titleAlign,';" class="flotr-axis-title flotr-axis-title-y1">',s.y.options.title,"</div>"),s.y2.options.title&&s.y2.used&&t.push('<div style="position:absolute;top:',this.plotOffset.top+this.plotHeight/2-s.y.titleSize.height/2,"px;right:0;text-align:",s.y2.options.titleAlign,';" class="flotr-axis-title flotr-axis-title-y2">',s.y2.options.title,"</div>"),t=t.join("");var u=e.create("div");e.setStyles({color:n.grid.color}),u.className="flotr-titles",e.insert(this.el,u),e.insert(u,t)}}})}(); diff --git a/p/scripts/global_view.js b/p/scripts/global_view.js new file mode 100644 index 000000000..7105520a6 --- /dev/null +++ b/p/scripts/global_view.js @@ -0,0 +1,84 @@ +"use strict"; +var panel_loading = false; + +function load_panel(link) { + if (panel_loading) { + return; + } + + panel_loading = true; + + $.get(link, function (data) { + $("#panel").append($(".nav_menu, #stream .day, #stream .flux, #stream .pagination", data)); + + $("#panel .nav_menu").children().not("#nav_menu_read_all").remove(); + + init_load_more($("#panel")); + init_posts(); + + $("#overlay").fadeIn(); + $("#panel").slideToggle(); + + // force le démarrage du scroll en haut. + // Sans ça, si l'on scroll en lisant une catégorie par exemple, + // en en ouvrant une autre ensuite, on se retrouve au même point de scroll + $("#panel").scrollTop(0); + + $('#panel').on('click', '#nav_menu_read_all > a, #nav_menu_read_all .item > a, #bigMarkAsRead', function () { + $.ajax({ + url: $(this).attr("href"), + async: false + }); + //$("#panel .close").first().click(); + window.location.reload(false); + return false; + }); + + panel_loading = false; + }); +} + +function init_close_panel() { + $("#panel .close").click(function () { + $("#panel").html('<a class="close" href="#">' + window.iconClose + '</a>'); + init_close_panel(); + $("#panel").slideToggle(); + $("#overlay").fadeOut(); + + return false; + }); +} + +function init_global_view() { + $("#stream .box-category a").click(function () { + var link = $(this).attr("href"); + + load_panel(link); + + return false; + }); + + $(".nav_menu #nav_menu_read_all, .nav_menu .toggle_aside").remove(); + + init_stream($("#panel")); +} + +function init_all_global_view() { + if (!(window.$ && window.init_stream)) { + if (window.console) { + console.log('FreshRSS Global view waiting for JS…'); + } + window.setTimeout(init_all_global_view, 50); //Wait for all js to be loaded + return; + } + init_global_view(); + init_close_panel(); +} + +if (document.readyState && document.readyState !== 'loading') { + init_all_global_view(); +} else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', function () { + init_all_global_view(); + }, false); +} diff --git a/p/scripts/jquery.min.js b/p/scripts/jquery.min.js new file mode 100644 index 000000000..e5ace116b --- /dev/null +++ b/p/scripts/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b) +},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:"0",fontWeight:"400"},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?zb.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=yb(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(n.cssHooks[a+b].set=Gb)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}n.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Kb.prototype.init,n.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||tb(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?tb(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ub(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return n.map(k,Ub,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xb,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xb(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),n.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Lb=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Lb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Mb||(Mb=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Mb),Mb=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Yb,Zb,$b=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b)) +},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||n.find.attr;$b[b]=function(a,b,d){var e,f;return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=n.now(),dc=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=l.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,n.ajaxSettings),b):tc(n.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=n.ajaxSettings.xhr();a.ActiveXObject&&n(a).on("unload",function(){for(var a in Dc)Dc[a]()}),k.cors=!!Fc&&"withCredentials"in Fc,k.ajax=Fc=!!Fc,n.ajaxTransport(function(a){var b;return k.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||n.expando+"_"+cc++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Ic=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Jc})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=yb(k.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Lc=a.jQuery,Mc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Mc),b&&a.jQuery===n&&(a.jQuery=Lc),n},typeof b===U&&(a.jQuery=a.$=n),n}); diff --git a/p/scripts/main.js b/p/scripts/main.js new file mode 100644 index 000000000..f6d5d2907 --- /dev/null +++ b/p/scripts/main.js @@ -0,0 +1,1297 @@ +"use strict"; +var $stream = null, + isCollapsed = true, + shares = 0, + ajax_loading = false; + +function is_normal_mode() { + return $stream.hasClass('normal'); +} + +function is_global_mode() { + return $stream.hasClass('global'); +} + +function redirect(url, new_tab) { + if (url) { + if (new_tab) { + window.open(url); + } else { + location.href = url; + } + } +} + +function needsScroll($elem) { + var $win = $(window), + winTop = $win.scrollTop(), + winHeight = $win.height(), + winBottom = winTop + winHeight, + elemTop = $elem.offset().top, + elemBottom = elemTop + $elem.outerHeight(); + return (elemTop < winTop || elemBottom > winBottom) ? elemTop - (winHeight / 2) : 0; +} + +function str2int(str) { + if (str == '') { + return 0; + } + return parseInt(str.replace(/\D/g, ''), 10) || 0; +} + +function numberFormat(nStr) { + if (nStr < 0) { + return 0; + } + // http://www.mredkj.com/javascript/numberFormat.html + nStr += ''; + var x = nStr.split('.'), + x1 = x[0], + x2 = x.length > 1 ? '.' + x[1] : '', + rgx = /(\d+)(\d{3})/; + while (rgx.test(x1)) { + x1 = x1.replace(rgx, '$1' + ' ' + '$2'); + } + return x1 + x2; +} + +function incLabel(p, inc, spaceAfter) { + var i = str2int(p) + inc; + return i > 0 + ? ((spaceAfter ? '' : ' ') + '(' + numberFormat(i) + ')' + (spaceAfter ? ' ' : '')) + : ''; +} + +function incUnreadsFeed(article, feed_id, nb) { + //Update unread: feed + var elem = $('#' + feed_id + '>.feed').get(0), + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0, + feed_priority = elem ? str2int(elem.getAttribute('data-priority')) : 0; + if (elem) { + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); + elem = $(elem).closest('li').get(0); + if (elem) { + elem.setAttribute('data-unread', feed_unreads + nb); + } + } + + //Update unread: category + elem = $('#' + feed_id).parent().prevAll('.category').children(':first').get(0); + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + if (elem) { + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); + elem = $(elem).closest('li').get(0); + if (elem) { + elem.setAttribute('data-unread', feed_unreads + nb); + } + } + + //Update unread: all + if (feed_priority > 0) { + elem = $('#aside_flux .all').children(':first').get(0); + if (elem) { + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); + } + } + + //Update unread: favourites + if (article && article.closest('div').hasClass('favorite')) { + elem = $('#aside_flux .favorites').children(':first').get(0); + if (elem) { + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); + } + } + + var isCurrentView = false; + //Update unread: title + document.title = document.title.replace(/^((?:\([ 0-9]+\) )?)/, function (m, p1) { + var $feed = $('#' + feed_id); + if (article || ($feed.closest('.active').length > 0 && $feed.siblings('.active').length === 0)) { + isCurrentView = true; + return incLabel(p1, nb, true); + } else if ($('.all.active').length > 0) { + isCurrentView = feed_priority > 0; + return incLabel(p1, feed_priority > 0 ? nb : 0, true); + } else { + return p1; + } + }); + return isCurrentView; +} + +var pending_feeds = []; +function mark_read(active, only_not_read) { + if (active.length === 0 || + (only_not_read === true && !active.hasClass("not_read"))) { + return false; + } + + var url = active.find("a.read").attr("href"); + if (url === undefined) { + return false; + } + + var feed_url = active.find(".website>a").attr("href"), + feed_id = feed_url.substr(feed_url.lastIndexOf('f_')), + index_pending = pending_feeds.indexOf(feed_id); + + if (index_pending !== -1) { + return false; + } + + pending_feeds.push(feed_id); + + $.ajax({ + type: 'POST', + url: url, + data : { ajax: true } + }).done(function (data) { + var $r = active.find("a.read").attr("href", data.url), + inc = 0; + if (active.hasClass("not_read")) { + active.removeClass("not_read"); + inc--; + } else if (only_not_read !== true || active.hasClass("not_read")) { + active.addClass("not_read"); + inc++; + } + $r.find('.icon').replaceWith(data.icon); + + incUnreadsFeed(active, feed_id, inc); + faviconNbUnread(); + + pending_feeds.splice(index_pending, 1); + }); +} + +function mark_favorite(active) { + if (active.length === 0) { + return false; + } + + var url = active.find("a.bookmark").attr("href"); + if (url === undefined) { + return false; + } + + var feed_url = active.find(".website>a").attr("href"), + feed_id = feed_url.substr(feed_url.lastIndexOf('f_')), + index_pending = pending_feeds.indexOf(feed_id); + + if (index_pending !== -1) { + return false; + } + + pending_feeds.push(feed_id); + + $.ajax({ + type: 'POST', + url: url, + data : { ajax: true } + }).done(function (data) { + var $b = active.find("a.bookmark").attr("href", data.url), + inc = 0; + if (active.hasClass("favorite")) { + active.removeClass("favorite"); + inc--; + } else { + active.addClass("favorite").find('.bookmark'); + inc++; + } + $b.find('.icon').replaceWith(data.icon); + + var favourites = $('.favorites>a').contents().last().get(0); + if (favourites && favourites.textContent) { + favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) { + return incLabel(p1, inc, false); + }); + } + + if (active.closest('div').hasClass('not_read')) { + var elem = $('#aside_flux .favorites').children(':first').get(0), + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + if (elem) { + elem.setAttribute('data-unread', numberFormat(feed_unreads + inc)); + } + } + + pending_feeds.splice(index_pending, 1); + }); +} + +function toggleContent(new_active, old_active) { + if (new_active.length === 0) { + return; + } + + if (does_lazyload) { + new_active.find('img[data-original], iframe[data-original]').each(function () { + this.setAttribute('src', this.getAttribute('data-original')); + this.removeAttribute('data-original'); + }); + } + + if (old_active[0] !== new_active[0]) { + if (isCollapsed) { + new_active.addClass("active"); + } + old_active.removeClass("active current"); + new_active.addClass("current"); + } else { + new_active.toggleClass('active'); + } + + var box_to_move = "html,body", + relative_move = false; + if (is_global_mode()) { + box_to_move = "#panel"; + relative_move = true; + } + + if (sticky_post) { + var prev_article = new_active.prevAll('.flux'), + new_pos = new_active.position().top, + old_scroll = $(box_to_move).scrollTop(); + + if (prev_article.length > 0 && new_pos - prev_article.position().top <= 150) { + new_pos = prev_article.position().top; + } + + if (hide_posts) { + if (relative_move) { + new_pos += old_scroll; + } + + if (old_active[0] !== new_active[0]) { + new_active.children(".flux_content").first().each(function () { + $(box_to_move).scrollTop(new_pos).scrollTop(); + }); + } + } else { + if (relative_move) { + new_pos += old_scroll; + } + + $(box_to_move).scrollTop(new_pos).scrollTop(); + } + } + + if (auto_mark_article && new_active.hasClass('active')) { + mark_read(new_active, true); + } +} + +function prev_entry() { + var old_active = $(".flux.current"), + new_active = old_active.length === 0 ? $(".flux:last") : old_active.prevAll(".flux:first"); + toggleContent(new_active, old_active); +} + +function next_entry() { + var old_active = $(".flux.current"), + new_active = old_active.length === 0 ? $(".flux:first") : old_active.nextAll(".flux:first"); + toggleContent(new_active, old_active); + + if (new_active.nextAll().length < 3) { + load_more_posts(); + } +} + +function prev_feed() { + var active_feed = $("#aside_flux .feeds li.active"); + if (active_feed.length > 0) { + active_feed.prevAll(':visible:first').find('a.feed').each(function(){this.click();}); + } else { + last_feed(); + } +} + +function next_feed() { + var active_feed = $("#aside_flux .feeds li.active"); + if (active_feed.length > 0) { + active_feed.nextAll(':visible:first').find('a.feed').each(function(){this.click();}); + } else { + first_feed(); + } +} + +function first_feed() { + var feed = $("#aside_flux .feeds.active li:visible:first"); + if (feed.length > 0) { + feed.find('a')[1].click(); + } +} + +function last_feed() { + var feed = $("#aside_flux .feeds.active li:visible:last"); + if (feed.length > 0) { + feed.find('a')[1].click(); + } +} + +function prev_category() { + var active_cat = $("#aside_flux .category.stick.active"); + + if (active_cat.length > 0) { + var prev_cat = active_cat.parent('li').prevAll(':visible:first').find('.category.stick a.btn'); + if (prev_cat.length > 0) { + prev_cat[0].click(); + } + } else { + last_category(); + } + return; +} + +function next_category() { + var active_cat = $("#aside_flux .category.stick.active"); + + if (active_cat.length > 0) { + var next_cat = active_cat.parent('li').nextAll(':visible:first').find('.category.stick a.btn'); + if (next_cat.length > 0) { + next_cat[0].click(); + } + } else { + first_category(); + } + return; +} + +function first_category() { + var cat = $("#aside_flux .category.stick:visible:first"); + if (cat.length > 0) { + cat.find('a.btn')[0].click(); + } +} + +function last_category() { + var cat = $("#aside_flux .category.stick:visible:last"); + if (cat.length > 0) { + cat.find('a.btn')[0].click(); + } +} + +function collapse_entry() { + isCollapsed = !isCollapsed; + + var flux_current = $(".flux.current"); + flux_current.toggleClass("active"); + if (isCollapsed && auto_mark_article) { + mark_read(flux_current, true); + } +} + +function user_filter(key) { + console.log('user filter'); + console.warn(key); + var filter = $('#dropdown-query'); + var filters = filter.siblings('.dropdown-menu').find('.item.query a'); + if (typeof key === "undefined") { + if (!filter.length) { + return; + } + // Display the filter div + window.location.hash = filter.attr('id'); + // Force scrolling to the filter div + var scroll = needsScroll($('.header')); + if (scroll !== 0) { + $('html,body').scrollTop(scroll); + } + // Force the key value if there is only one action, so we can trigger it automatically + if (filters.length === 1) { + key = 1; + } else { + return; + } + } + // Trigger selected share action + key = parseInt(key); + if (key <= filters.length) { + filters[key - 1].click(); + } +} + +function auto_share(key) { + var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]'); + var shares = share.siblings('.dropdown-menu').find('.item a'); + if (typeof key === "undefined") { + if (!share.length) { + return; + } + // Display the share div + window.location.hash = share.attr('id'); + // Force scrolling to the share div + var scroll = needsScroll(share.closest('.bottom')); + if (scroll !== 0) { + $('html,body').scrollTop(scroll); + } + // Force the key value if there is only one action, so we can trigger it automatically + if (shares.length === 1) { + key = 1; + } else { + return; + } + } + // Trigger selected share action and hide the share div + key = parseInt(key); + if (key <= shares.length) { + shares[key - 1].click(); + share.siblings('.dropdown-menu').find('.dropdown-close a')[0].click(); + } +} + +function inMarkViewport(flux, box_to_follow, relative_follow) { + var top = flux.position().top; + if (relative_follow) { + top += box_to_follow.scrollTop(); + } + var height = flux.height(), + begin = top + 3 * height / 4, + bot = Math.min(begin + 75, top + height), + windowTop = box_to_follow.scrollTop(), + windowBot = windowTop + box_to_follow.height() / 2; + + return (windowBot >= begin && bot >= windowBot); +} + +function init_posts() { + var box_to_follow = $(window), + relative_follow = false; + if (is_global_mode()) { + box_to_follow = $("#panel"); + relative_follow = true; + } + + if (auto_mark_scroll) { + box_to_follow.scroll(function () { + $('.not_read:visible').each(function () { + if ($(this).children(".flux_content").is(':visible') && inMarkViewport($(this), box_to_follow, relative_follow)) { + mark_read($(this), true); + } + }); + }); + } + + if (auto_load_more) { + box_to_follow.scroll(function () { + var load_more = $("#load_more"); + if (!load_more.is(':visible')) { + return; + } + var boxBot = box_to_follow.scrollTop() + box_to_follow.height(), + load_more_top = load_more.position().top; + if (relative_follow) { + load_more_top += box_to_follow.scrollTop(); + } + if (boxBot >= load_more_top) { + load_more_posts(); + } + }); + box_to_follow.scroll(); + } +} + +function init_column_categories() { + if (!is_normal_mode()) { + return; + } + $('#aside_flux').on('click', '.category>a.dropdown-toggle', function () { + $(this).children().each(function() { + if (this.alt === '▽') { + this.src = this.src.replace('/icons/down.', '/icons/up.'); + this.alt = '△'; + } else { + this.src = this.src.replace('/icons/up.', '/icons/down.'); + this.alt = '▽'; + } + }); + $(this).parent().next(".feeds").slideToggle(); + return false; + }); + $('#aside_flux').on('click', '.feeds .dropdown-toggle', function () { + if ($(this).nextAll('.dropdown-menu').length === 0) { + var feed_id = $(this).closest('li').attr('id').substr(2), + feed_web = $(this).data('fweb'), + template = $('#feed_config_template').html().replace(/!!!!!!/g, feed_id).replace('http://example.net/', feed_web); + $(this).attr('href', '#dropdown-' + feed_id).prev('.dropdown-target').attr('id', 'dropdown-' + feed_id).parent().append(template); + } + }); +} + +function init_shortcuts() { + if (!(window.shortcut && window.shortcuts)) { + if (window.console) { + console.log('FreshRSS waiting for sortcut.js…'); + } + window.setTimeout(init_shortcuts, 50); + return; + } + // Touches de manipulation + shortcut.add(shortcuts.mark_read, function () { + // on marque comme lu ou non lu + var active = $(".flux.current"); + mark_read(active, false); + }, { + 'disable_in_input': true + }); + shortcut.add("shift+" + shortcuts.mark_read, function () { + // on marque tout comme lu + var btn = $(".nav_menu .read_all"); + if (btn.hasClass('confirm')) { + if (confirm(str_confirmation)) { + btn.click(); + } + } else { + btn.click(); + } + }, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.mark_favorite, function () { + // on marque comme favori ou non favori + var active = $(".flux.current"); + mark_favorite(active); + }, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.collapse_entry, function () { + collapse_entry(); + }, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.auto_share, function () { + auto_share(); + }, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.user_filter, function () { + user_filter(); + }, { + 'disable_in_input': true + }); + for(var i = 1; i < 10; i++){ + shortcut.add(i.toString(), function (e) { + if ($('#dropdown-query').siblings('.dropdown-menu').is(':visible')) { + user_filter(String.fromCharCode(e.keyCode)); + } else { + auto_share(String.fromCharCode(e.keyCode)); + } + }, { + 'disable_in_input': true + }); + } + + // Touches de navigation pour les articles + shortcut.add(shortcuts.prev_entry, prev_entry, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.first_entry, function () { + var old_active = $(".flux.current"), + first = $(".flux:first"); + + if (first.hasClass("flux")) { + toggleContent(first, old_active); + } + }, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.next_entry, next_entry, { + 'disable_in_input': true + }); + shortcut.add(shortcuts.last_entry, function () { + var old_active = $(".flux.current"), + last = $(".flux:last"); + + if (last.hasClass("flux")) { + toggleContent(last, old_active); + } + }, { + 'disable_in_input': true + }); + // Touches de navigation pour les flux + shortcut.add("shift+" + shortcuts.prev_entry, prev_feed, { + 'disable_in_input': true + }); + shortcut.add("shift+" + shortcuts.next_entry, next_feed, { + 'disable_in_input': true + }); + shortcut.add("shift+" + shortcuts.first_entry, first_feed, { + 'disable_in_input': true + }); + shortcut.add("shift+" + shortcuts.last_entry, last_feed, { + 'disable_in_input': true + }); + // Touches de navigation pour les categories + shortcut.add("alt+" + shortcuts.prev_entry, prev_category, { + 'disable_in_input': true + }); + shortcut.add("alt+" + shortcuts.next_entry, next_category, { + 'disable_in_input': true + }); + shortcut.add("alt+" + shortcuts.first_entry, first_category, { + 'disable_in_input': true + }); + shortcut.add("alt+" + shortcuts.last_entry, last_category, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.go_website, function () { + var url_website = $('.flux.current > .flux_header > .title > a').attr("href"); + + if (auto_mark_site) { + $(".flux.current").each(function () { + mark_read($(this), true); + }); + } + + redirect(url_website, true); + }, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.load_more, function () { + load_more_posts(); + }, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.focus_search, function () { + focus_search(); + }, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.help, function () { + redirect(help_url, true); + }, { + 'disable_in_input': true + }); + +} + +function init_stream(divStream) { + divStream.on('click', '.flux_header,.flux_content', function (e) { //flux_toggle + if ($(e.target).closest('.content, .item.website, .item.link').length > 0) { + return; + } + var old_active = $(".flux.current"), + new_active = $(this).parent(); + isCollapsed = true; + if (e.target.tagName.toUpperCase() === 'A') { //Leave real links alone + if (auto_mark_article) { + mark_read(new_active, true); + } + return true; + } + toggleContent(new_active, old_active); + }); + + divStream.on('click', '.flux a.read', function () { + var active = $(this).parents(".flux"); + mark_read(active, false); + return false; + }); + + divStream.on('click', '.flux a.bookmark', function () { + var active = $(this).parents(".flux"); + mark_favorite(active); + return false; + }); + + divStream.on('click', '.item.title > a', function (e) { + // Allow default control-click behaviour such as open in backround-tab. + return e.ctrlKey; + }); + divStream.on('mouseup', '.item.title > a', function (e) { + // Mouseup enables us to catch middle click. + if (e.ctrlKey) { + // CTRL+click, it will be manage by previous rule. + return; + } + + if (e.which == 2) { + // If middle click, we want same behaviour as CTRL+click. + var e = jQuery.Event("click"); + e.ctrlKey = true; + $(this).trigger(e); + } else if(e.which == 1) { + // Normal click, just toggle article. + $(this).parent().click(); + } + }); + + divStream.on('click', '.flux .content a', function () { + $(this).attr('target', '_blank'); + }); + + if (auto_mark_site) { + // catch mouseup instead of click so we can have the correct behaviour + // with middle button click (scroll button). + divStream.on('mouseup', '.flux .link > a', function (e) { + if (e.which == 3) { + return; + } + + mark_read($(this).parents(".flux"), true); + }); + } +} + +function init_nav_entries() { + var $nav_entries = $('#nav_entries'); + $nav_entries.find('.previous_entry').click(function () { + prev_entry(); + return false; + }); + $nav_entries.find('.next_entry').click(function () { + next_entry(); + return false; + }); + $nav_entries.find('.up').click(function () { + var active_item = $(".flux.current"), + windowTop = $(window).scrollTop(), + item_top = active_item.position().top; + + if (windowTop > item_top) { + $("html,body").scrollTop(item_top); + } else { + $("html,body").scrollTop(0); + } + return false; + }); +} + +function init_actualize() { + var auto = false; + + $("#actualize").click(function () { + if (ajax_loading) { + return false; + } + + ajax_loading = true; + + $.getScript('./?c=javascript&a=actualize').done(function () { + if (auto && feed_count < 1) { + auto = false; + ajax_loading = false; + return false; + } + + updateFeeds(); + }); + + return false; + }); + + if (auto_actualize_feeds) { + auto = true; + $("#actualize").click(); + } +} + + +// <notification> +var notification = null, + notification_interval = null, + notification_working = false; + +function openNotification(msg, status) { + if (notification_working === true) { + return false; + } + + notification_working = true; + + notification.removeClass(); + notification.addClass("notification"); + notification.addClass(status); + notification.find(".msg").html(msg); + notification.fadeIn(300); + + notification_interval = window.setTimeout(closeNotification, 4000); +} + +function closeNotification() { + notification.fadeOut(600, function() { + notification.removeClass(); + notification.addClass('closed'); + + window.clearInterval(notification_interval); + notification_working = false; + }); +} + +function init_notifications() { + notification = $("#notification"); + + notification.find("a.close").click(function () { + closeNotification(); + return false; + }); + + if (notification.find(".msg").html().length > 0) { + notification_working = true; + notification_interval = window.setTimeout(closeNotification, 4000); + } +} +// </notification> + +// <notifs html5> +var notifs_html5_permission = 'denied'; + +function notifs_html5_is_supported() { + return window.Notification !== undefined; +} + +function notifs_html5_ask_permission() { + window.Notification.requestPermission(function () { + notifs_html5_permission = window.Notification.permission; + }); +} + +function notifs_html5_show(nb) { + if (notifs_html5_permission !== "granted") { + return + } + + var notification = new window.Notification(str_notif_title_articles, { + icon: "../themes/icons/favicon-256.png", + body: str_notif_body_articles.replace("\d", nb), + tag: "freshRssNewArticles" + }); + + notification.onclick = function() { + window.location.reload(); + } + + if (html5_notif_timeout !== 0){ + setTimeout(function() { + notification.close(); + }, html5_notif_timeout * 1000); + } +} + +function init_notifs_html5() { + if (!notifs_html5_is_supported()) { + return; + } + + notifs_html5_permission = notifs_html5_ask_permission(); +} +// </notifs html5> + +function refreshUnreads() { + $.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) { + var isAll = $('.category.all > .active').length > 0, + new_articles = false; + + $.each(data, function(feed_id, nbUnreads) { + feed_id = 'f_' + feed_id; + var elem = $('#' + feed_id + '>.feed').get(0), + feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; + + if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) && //Update of current view? + (nbUnreads - feed_unreads > 0)) { + $('#new-article').show(); + new_articles = true; + }; + }); + + var nb_unreads = str2int($('.category.all>a').attr('data-unread')); + + if (nb_unreads > 0 && new_articles) { + faviconNbUnread(nb_unreads); + notifs_html5_show(nb_unreads); + } + }); +} + +//<endless_mode> +var url_load_more = "", + load_more = false, + box_load_more = null; + +function load_more_posts() { + if (load_more || url_load_more === '' || box_load_more === null) { + return; + } + + load_more = true; + $('#load_more').addClass('loading'); + $.get(url_load_more, function (data) { + box_load_more.children('.flux:last').after($('#stream', data).children('.flux, .day')); + $('.pagination').replaceWith($('.pagination', data)); + if (display_order === 'ASC') { + $('#nav_menu_read_all > .read_all').attr( + 'formaction', $('#bigMarkAsRead').attr('formaction') + ); + } else { + $('#bigMarkAsRead').attr( + 'formaction', $('#nav_menu_read_all > .read_all').attr('formaction') + ); + } + + $('[id^=day_]').each(function (i) { + var ids = $('[id="' + this.id + '"]'); + if (ids.length > 1) { + $('[id="' + this.id + '"]:gt(0)').remove(); + } + }); + + init_load_more(box_load_more); + + $('#load_more').removeClass('loading'); + load_more = false; + }); +} + +function focus_search() { + $('#search').focus(); +} + +function init_load_more(box) { + box_load_more = box; + + if (!does_lazyload) { + $('img[postpone], audio[postpone], iframe[postpone], video[postpone]').each(function () { + this.removeAttribute('postpone'); + }); + } + + var $next_link = $("#load_more"); + if (!$next_link.length) { + // no more article to load + url_load_more = ""; + return; + } + + url_load_more = $next_link.attr("href"); + var $prefetch = $('#prefetch'); + if ($prefetch.attr('href') !== url_load_more) { + $prefetch.attr('rel', 'next'); //Remove prefetch + $.ajax({url: url_load_more, ifModified: true }); //TODO: Try to find a less agressive solution + $prefetch.attr('href', url_load_more); + } + + $next_link.click(function () { + load_more_posts(); + return false; + }); +} +//</endless_mode> + +//<crypto form (Web login)> +function poormanSalt() { //If crypto.getRandomValues is not available + var text = '$2a$04$', + base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; + for (var i = 22; i > 0; i--) { + text += base.charAt(Math.floor(Math.random() * 64)); + } + return text; +} + +function init_crypto_form() { + var $crypto_form = $('#crypto-form'); + if ($crypto_form.length === 0) { + return; + } + + if (!(window.dcodeIO)) { + if (window.console) { + console.log('FreshRSS waiting for bcrypt.js…'); + } + window.setTimeout(init_crypto_form, 100); + return; + } + + $crypto_form.on('submit', function() { + var $submit_button = $(this).find('button[type="submit"]'); + $submit_button.attr('disabled', ''); + + var success = false; + $.ajax({ + url: './?c=javascript&a=nonce&user=' + $('#username').val(), + dataType: 'json', + async: false + }).done(function (data) { + if (data.salt1 == '' || data.nonce == '') { + openNotification('Invalid user!', 'bad'); + } else { + try { + var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), + s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), + c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt()); + $('#challenge').val(c); + if (s == '' || c == '') { + openNotification('Crypto error!', 'bad'); + } else { + success = true; + } + } catch (e) { + openNotification('Crypto exception! ' + e, 'bad'); + } + } + }).fail(function() { + openNotification('Communication error!', 'bad'); + }); + + $submit_button.removeAttr('disabled'); + return success; + }); +} +//</crypto form (Web login)> + +//<persona> +function init_persona() { + if (!(navigator.id)) { + if (window.console) { + console.log('FreshRSS waiting for Persona…'); + } + window.setTimeout(init_persona, 100); + return; + } + $('a.signin').click(function() { + navigator.id.request(); + return false; + }); + + $('a.signout').click(function() { + navigator.id.logout(); + return false; + }); + + navigator.id.watch({ + loggedInUser: current_user_mail, + + onlogin: function(assertion) { + // A user has logged in! Here you need to: + // 1. Send the assertion to your backend for verification and to create a session. + // 2. Update your UI. + $.ajax ({ + type: 'POST', + url: url_login, + data: {assertion: assertion}, + success: function(res, status, xhr) { + /*if (res.status === 'failure') { + alert (res_obj.reason); + } else*/ if (res.status === 'okay') { + location.href = url_freshrss; + } + }, + error: function(res, status, xhr) { + alert("Login failure: " + res); + } + }); + }, + onlogout: function() { + // A user has logged out! Here you need to: + // Tear down the user's session by redirecting the user or making a call to your backend. + // Also, make sure loggedInUser will get set to null on the next page load. + // (That's a literal JavaScript null. Not false, 0, or undefined. null.) + $.ajax ({ + type: 'POST', + url: url_logout, + success: function(res, status, xhr) { + location.href = url_freshrss; + }, + error: function(res, status, xhr) { + //alert("logout failure" + res); + } + }); + } + }); +} +//</persona> + +function init_confirm_action() { + $('body').on('click', '.confirm', function () { + var str_confirmation = $(this).attr('data-str-confirm'); + if (!str_confirmation) { + str_confirmation = str_confirmation_default; + } + + return confirm(str_confirmation); + }); +} + +function init_print_action() { + $('.item.share > a[href="#"]').click(function () { + var content = "<html><head><style>" + + "body { font-family: Serif; text-align: justify; }" + + "a { color: #000; text-decoration: none; }" + + "a:after { content: ' [' attr(href) ']'}" + + "</style></head><body>" + + $(".flux.current .content").html() + + "</body></html>"; + + var tmp_window = window.open(); + tmp_window.document.writeln(content); + tmp_window.document.close(); + tmp_window.focus(); + tmp_window.print(); + tmp_window.close(); + + return false; + }); +} + +function init_share_observers() { + shares = $('.form-group:not(".form-actions")').length; + + $('.share.add').on('click', function(e) { + var opt = $(this).siblings('select').find(':selected'); + var row = $(this).parents('form').data(opt.data('form')); + row = row.replace('##label##', opt.html(), 'g'); + row = row.replace('##type##', opt.val(), 'g'); + row = row.replace('##help##', opt.data('help'), 'g'); + row = row.replace('##key##', shares, 'g'); + $(this).parents('.form-group').before(row); + shares++; + + return false; + }); +} + +function init_stats_observers() { + $('#feed_select').on('change', function(e) { + redirect($(this).find(':selected').data('url')); + }); +} + +function init_remove_observers() { + $('.post').on('click', 'a.remove', function(e) { + var remove_what = $(this).attr('data-remove'); + + if (remove_what !== undefined) { + var remove_obj = $('#' + remove_what); + remove_obj.remove(); + } + + return false; + }); +} + +function init_feed_observers() { + $('select[id="category"]').on('change', function() { + var detail = $('#new_category_name').parent(); + if ($(this).val() === 'nc') { + detail.show(); + detail.find('input').focus(); + } else { + detail.hide(); + } + }); +} + +function init_password_observers() { + $('input[type="password"] + a.btn.toggle-password').on('click', function(e) { + var button = $(this); + var passwordField = $(this).siblings('input[type="password"]'); + + passwordField.attr('type', 'text'); + button.addClass('active'); + + setTimeout(function() { + passwordField.attr('type', 'password'); + button.removeClass('active'); + }, 2000); + + return false; + }); +} + +function faviconNbUnread(n) { + if (typeof n === 'undefined') { + n = str2int($('.category.all>a').attr('data-unread')); + } + //http://remysharp.com/2010/08/24/dynamic-favicons/ + var canvas = document.createElement('canvas'), + link = document.getElementById('favicon').cloneNode(true); + if (canvas.getContext && link) { + canvas.height = canvas.width = 16; + var img = document.createElement('img'); + img.onload = function () { + var ctx = canvas.getContext('2d'); + ctx.drawImage(this, 0, 0, canvas.width, canvas.height); + if (n > 0) { + var text = ''; + if (n < 1000) { + text = n; + } else if (n < 100000) { + text = Math.floor(n / 1000) + 'k'; + } else { + text = 'E' + Math.floor(Math.log10(n)); + } + ctx.font = 'bold 9px "Arial", sans-serif'; + ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; + ctx.fillRect(0, 7, ctx.measureText(text).width, 9); + ctx.fillStyle = '#F00'; + ctx.fillText(text, 0, canvas.height - 1); + } + link.href = canvas.toDataURL('image/png'); + $('link[rel~=icon]').remove(); + document.head.appendChild(link); + }; + img.src = '../favicon.ico'; + } +} + +function init_all() { + if (!(window.$ && window.url_freshrss)) { + if (window.console) { + console.log('FreshRSS waiting for JS…'); + } + window.setTimeout(init_all, 50); + return; + } + init_notifications(); + switch (authType) { + case 'persona': + init_persona(); + break; + } + init_confirm_action(); + $stream = $('#stream'); + if ($stream.length > 0) { + init_actualize(); + init_column_categories(); + init_load_more($stream); + init_posts(); + init_stream($stream); + init_nav_entries(); + init_shortcuts(); + faviconNbUnread(); + init_print_action(); + init_notifs_html5(); + window.setInterval(refreshUnreads, 120000); + } else { + init_crypto_form(); + init_share_observers(); + init_remove_observers(); + init_feed_observers(); + init_password_observers(); + init_stats_observers(); + } + + if (window.console) { + console.log('FreshRSS init done.'); + } +} + +if (document.readyState && document.readyState !== 'loading') { + if (window.console) { + console.log('FreshRSS immediate init…'); + } + init_all(); +} else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', function () { + if (window.console) { + console.log('FreshRSS waiting for DOMContentLoaded…'); + } + init_all(); + }, false); +} diff --git a/public/scripts/shortcut.js b/p/scripts/shortcut.js index debaffbaa..debaffbaa 100644 --- a/public/scripts/shortcut.js +++ b/p/scripts/shortcut.js diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css new file mode 100644 index 000000000..10f6e655b --- /dev/null +++ b/p/themes/Dark/dark.css @@ -0,0 +1,995 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + background: #1c1c1c; + color: #888; +} + +/*=== Links */ +a, button.as-link { + color: #6986B2; + outline: none; +} + +/*=== Images */ +img.favicon { + background: #fff; + border-radius: 2px; +} + +/*=== Forms */ +legend { + margin: 20px 0 5px; + padding: 5px 0; + font-size: 1.4em; + border-bottom: 1px solid #2f2f2f; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 5px; + line-height: 25px; + vertical-align: middle; + background: #333; + border: 1px solid #000; + border-radius: 3px; + color: #999; + box-shadow: 0 2px 2px #1d1d1d inset; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { + color: #6986b2; + border-color: #2f2f2f; +} +input:invalid, select:invalid { + border-color: #f00; + box-shadow: 0 0 2px 1px #f00; +} +input:disabled, select:disabled { + background: #666; + color: #aaa; +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; + border: 1px solid #333; +} +th { + background: #222; +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group.form-actions { + padding: 5px 0; + background: #1a1a1a; + border-top: 1px solid #2f2f2f; +} +.form-group.form-actions .btn { + margin: 0 10px; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +.stick { + vertical-align: middle; + font-size: 0; +} +.stick input, +.stick .btn { + border-radius: 0; +} +.stick .btn:first-child, +.stick input:first-child { + border-radius: 3px 0 0 3px; +} +.stick .btn-important:first-child { + border-right: 1px solid #000; +} +.stick .btn:last-child, +.stick input:last-child { + border-radius: 0 3px 3px 0; +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { + border-left: none; +} +.stick input:focus+input { + border-left: 1px solid #000; +} +.stick input+input:focus { + border-left: 1px solid #333; +} +.stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; +} + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + margin: 0; + padding: 5px 10px; + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + background: #111; + border-radius: 3px; + border: 1px solid #000; + color: #888; +} +a.btn { + min-height: 25px; + line-height: 25px; +} +.btn:hover { + text-decoration: none; + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); +} +.btn.active, +.dropdown-target:target ~ .btn.dropdown-toggle { + background: #333; +} +.btn:active { + background: #26303F; +} + +.btn-important { + font-weight: normal; + background: #26303F; +} +.btn-important:hover { + background: linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); +} +.btn-important:active { + background: #26303F; +} + +.btn-attention { + background: #880011; +} +.btn-attention:hover { + background: linear-gradient(top, #cc0044 0%, #880011 100%); + background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -o-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%); +} +.btn-attention:active { + background: #880011; +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover { + background: #26303F; +} +.nav-list .item.active { + background: #333; +} +.nav-list .item:hover a, +.nav-list .item.active a { + color: #888; +} +.nav-list .disable { + text-align: center; + color: #aaa; + background: #fafafa; +} +.nav-list .item > a { + padding: 0 10px; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { + color: #c95; +} +.nav-list .item:hover.empty a, +.nav-list .item.active.empty a { + color: #fff; + background: #c95; +} +.nav-list .item.error a { + color: #a44; +} +.nav-list .item:hover.error a, +.nav-list .item.active.error a { + color: #fff; + background: #a44; +} + +.nav-list .nav-header { + padding: 0 10px; + font-weight: bold; + background: #111; + border-bottom: 1px solid #333; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + text-align: right; + background: #1c1c1c; + border-bottom: 1px solid #333; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + font-size: 0.8rem; + text-align: left; + background: #1a1a1a; + border: 1px solid #888; + border-radius: 5px; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + background: #1a1a1a; + border-top: 1px solid #888; + border-left: 1px solid #888; +} +.dropdown-header { + padding: 0 5px 5px; + font-weight: bold; + text-align: left; + color: #888; +} +.dropdown-menu > .item > a { + padding: 0 25px; + line-height: 2.5em; +} +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; +} +.dropdown-menu > .item:hover { + background: #26303F; + color: #888; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + text-decoration: none; + color: #888; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; +} + +.separator { + margin: 5px 0; + border-bottom: 1px solid #333; +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + font-size: 0.9em; + background: #111; + border: 1px solid #888; + border-radius: 5px; + color: #aaa; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + text-decoration: underline; + color: inherit; +} +.alert-warn { + border: 1px solid #c95; + color: #c95; +} +.alert-success { + border: 1px solid #484; + color: #484; +} +.alert-error { + border: 1px solid #a44; + color: #a44; +} + +/*=== Pagination */ +.pagination { + text-align: center; + font-size: 0.8em; + background: #1c1c1c; + color: #888; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; + background: #111; +} +.pagination .item a { + display: block; + font-style: italic; + line-height: 3em; + text-decoration: none; + color: #666; +} +.pagination .item a:hover { + background-color: #111; +} +.pagination:first-child .item { + border-bottom: 1px solid #333; +} +.pagination:last-child .item { + border-top: 1px solid #333; +} + +.pagination .loading, +.pagination a:hover.loading { + font-size: 0; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 85px; +} +.header > .item { + padding: 10px; + vertical-align: middle; + text-align: center; + border-bottom: 1px solid #333; +} +.header > .item.title{ + width: 230px; +} +.header > .item.title h1 { + margin: 0.5em 0; +} +.header > .item.title h1 a { + text-decoration: none; +} +.header > .item.search input { + width: 230px; +} +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + height: calc(100% - 85px); +} +.aside { + border-right: 1px solid #333; + background: #1c1c1c; +} +.aside.aside_flux { + padding: 10px 0 50px; + border-right: 1px solid #333; + background: #1c1c1c; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.category { + width: 235px; + margin: 10px auto; + text-align: left; +} +.category .btn:first-child { + position: relative; + width: 213px; +} +.category.stick .btn:first-child { + width: 176px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: #111; + color: #888; + border: 1px solid #000; + border-radius: 5px; +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { + background: #333; +} +.categories .feeds .item.active .feed { + color: #888; +} +.categories .feeds .item.empty .feed { + color: #c95; +} +.categories .feeds .item.empty.active { + background: #c95; +} +.categories .feeds .item.error .feed { + color: #a44; +} +.categories .feeds .item.error.active { + background: #a44; +} +.categories .feeds .item.empty.active .feed, +.categories .feeds .item.error.active .feed { + color: #fff; +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + vertical-align: middle; + background-color: #111; + border-radius: 3px; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 200px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} + +/*=== New article notification */ +#new-article { + text-align: center; + font-size: 0.9em; + background: #26303F; +} +#new-article:hover { + background: #4A5D7A; +} +#new-article > a { + line-height: 3em; + font-weight: bold; + color: #fff; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-weight: bold; + line-height: 3em; + border-top: 1px solid #333; + border-bottom: 1px solid #333; +} +.day .name { + padding: 0 10px 0 0; + font-size: 1.8em; + opacity: 0.3; + font-style: italic; + text-align: right; + color: #aab; + text-shadow: 0px -1px 0px #333; +} + +/*=== Index menu */ +.nav_menu { + text-align: center; + padding: 5px 0; + border-bottom: 1px solid #2f2f2f; +} + +/*=== Feed articles */ +.flux { + border-left: 2px solid #2f2f2f; +} +.flux:hover { + background: #111; +} +.flux.current { + border-left: 2px solid #0062BE; + background: #111; +} +.flux.not_read { + border-left: 2px solid #FF5300; +} +.flux.favorite { + border-left: 2px solid #FFC300; +} + + +.flux_header { + font-size: 0.8rem; + cursor: pointer; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux_header .item.title a { + color: #888; +} +.flux .website .favicon { + margin: 5px; +} +.flux .date { + font-size: 0.7rem; + color: #666; +} +.flux:not(.current):hover .item.title { + background: #111; +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { + color: #888; +} + +.content hr { + margin: 30px 10px; + height: 1px; + background: #666; + border: 0; + box-shadow: 0 2px 5px #666; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #222; + color: #fff; + border: 1px solid #000; + font-size: 0.9rem; + border-radius: 3px; +} +.content code { + padding: 2px 5px; + color: #dd1144; + background: #000; + border: 1px solid #333; + border-radius: 3px; +} +.content pre code { + background: transparent; + color: #fff; + border: none; +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; + border-top: 1px solid #444; + border-bottom: 1px solid #444; + background: #222; + color: #999; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + z-index: 10; + vertical-align: middle; + border-radius: 5px; + box-shadow: 0 0 5px #666; + background: #111; + color: #c95; + border: 1px solid #c95; +} +.notification.good { + border-color: #484; + color: #484; +} +.notification.bad { + border-color: #a44; + color: #a44; +} +.notification a.close { + padding: 0 15px; + line-height: 3em; +} +.notification a.close:hover { + background: #222; + border-radius: 0 3px 3px 0; +} +.notification.good a.close:hover { + background: #484; +} +.notification.bad a.close:hover { + background: #a44; +} + +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; +} +#bigMarkAsRead:hover { + background: #111; + color: #aaa; +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; + background: #111; + border-top: 1px solid #333; +} + +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; + border: none; + background: #111; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global .box-category { + text-align: left; + background: #1a1a1a; + border: 1px solid #000; + border-radius: 5px; + text-align: left; +} +#stream.global .category { + margin: 0; +} +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + line-height: 2em; + font-size: 1.2rem; + background: #26303F; + border: none; + border-bottom: 1px solid #000; + border-radius: 5px 5px 0 0; +} +#stream.global .btn:not([data-unread="0"]) { + font-weight: bold; + background: #34495e; + color: #fff; +} +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + font-weight: bold; + border: 0; + background: none; + color: #fff; +} +#stream.global .box-category .feeds { + max-height: 250px; +} +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; +} + +/*=== Panel */ +#panel { + background: #1c1c1c; + border: 1px solid #666; + border-radius: 3px; +} + +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input, +.aside.aside_feed .nav-form select { + width: 140px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { + border: none; +} +.stat > table td, +.stat > table th { + border-bottom: 1px solid #333; +} + +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 270px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + overflow: hidden; + border: 1px solid #333; +} +.log { + padding: 5px 10px; + font-size: 0.8rem; + background: #111; + color: #888; +} +.log+.log { + border-top: 1px solid #333; +} +.log .date { + display: block; + font-weight: bold; +} +.log.error { + background: #a44; + color: #fff; +} +.log.warning { + background: #c95; + color: #fff; +} +.log.notice { + background: #ec9; + color: #000; +} +.log.debug { + background: #111; + color: #eee; +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .aside { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + background: #111; + border-left: 1px solid #333; + border-bottom: 1px solid #333; + border-radius: 0 0 0 5px; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + font-size: 1.1rem; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification { + border-top: none; + border-right: none; + border-left: none; + border-radius: 0; + } + .notification a.close { + display: block; + left: 0; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } +} diff --git a/p/themes/Dark/icons/icon.svg b/p/themes/Dark/icons/icon.svg new file mode 100644 index 000000000..4eb53dda9 --- /dev/null +++ b/p/themes/Dark/icons/icon.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <title>Logo FreshRSS</title> + <circle fill="#6986B2" cx="128" cy="128" r="33"/> + <g fill="none" stroke="#6986B2" stroke-width="24"> + <g stroke-opacity="0.3"> + <path d="M12,128 A116,116 0 1,1 128,244"/> + <path d="M54,128 A74,74 0 1,1 128,202"/> + </g> + <path d="M128,12 A116,116 0 0,1 244,128"/> + <path d="M128,54 A74,74 0 0,1 202,128"/> + </g> +</svg> diff --git a/p/themes/Dark/loader.gif b/p/themes/Dark/loader.gif Binary files differnew file mode 100644 index 000000000..86022be71 --- /dev/null +++ b/p/themes/Dark/loader.gif diff --git a/p/themes/Dark/metadata.json b/p/themes/Dark/metadata.json new file mode 100644 index 000000000..bdc068c2e --- /dev/null +++ b/p/themes/Dark/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Dark", + "author": "AD", + "description": "Le coté obscur du thème “Origine”", + "version": 0.2, + "files": ["_template.css", "dark.css"] +} diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css new file mode 100644 index 000000000..484cee9f3 --- /dev/null +++ b/p/themes/Flat/flat.css @@ -0,0 +1,977 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + background: #fafafa; +} + +/*=== Links */ +a, button.as-link { + color: #2980b9; + outline: none; +} + +/*=== Forms */ +legend { + display: inline-block; + width: auto; + margin: 20px 0 5px; + padding: 5px 20px; + font-size: 1.4em; + clear: both; + background: #ecf0f1; + border-radius: 20px; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; + color: #444; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 5px; + line-height: 25px; + vertical-align: middle; + background: #fff; + border: none; + border-bottom: 3px solid #ddd; + border-left-color: #ddd; + color: #666; + border-radius: 5px; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { + color: #333; + border-color: #2980b9; +} +input:invalid, select:invalid { + color: #f00; + border-color: #f00; + box-shadow: none; +} +input:disabled, select:disabled { + background: #eee; +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; + border: 1px solid #ddd; +} +th { + background: #f6f6f6; +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group { + padding: 5px; + border: 1px solid transparent; + border-radius: 3px; +} +.form-group:after { + content: ""; + display: block; + clear: both; +} +.form-group:hover { + background: #fff; + border: 1px solid #eee; + border-radius: 3px; + border: 1px solid #eee; +} +.form-group.form-actions { + margin: 15px 0 25px; + padding: 5px 0; + background: #ecf0f1; + border-top: 3px solid #bdc3c7; + border-radius: 5px 5px 0 0; +} +.form-group.form-actions .btn { + margin: 0 10px; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; +} +.form-group .group-controls .control { + line-height: 2.0em; +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +.stick { + vertical-align: middle; + font-size: 0; +} +.stick input, +.stick .btn { + border-radius: 0; +} +.stick .btn:first-child, +.stick input:first-child { + border-radius: 5px 0 0 5px; +} +.stick .btn:last-child, +.stick input:last-child, +.stick .btn + .dropdown > .btn { + border-radius: 0 5px 5px 0; +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { + border-left-width: 1px; + border-left-style: solid; +} + +.btn { + display: inline-block; + min-height: 38px; + min-width: 15px; + margin: 0; + padding: 5px 10px; + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; + background: #3498db; + border-radius: 5px; + border: none; + border-bottom: 3px solid #2980b9; + border-left-color: #2980b9; + color: #fff; +} +a.btn { + min-height: 25px; + line-height: 25px; +} +.btn:hover { + text-decoration: none; +} +.btn.active, +.btn:active, +.btn:hover, +.dropdown-target:target ~ .btn.dropdown-toggle { + background: #2980b9; +} + +.btn-important { + font-weight: normal; + background: #e67e22; + color: #fff; + border-bottom: 3px solid #d35400; + border-left-color: #d35400; +} +.btn-important:hover, +.btn-important:active { + background: #d35400; +} + +.btn-attention { + background: #e74c3c; + color: #fff; + border-bottom: 3px solid #c0392b; + border-left-color: #c0392b; +} +.btn-attention:hover, +.btn-attention:active { + background: #c0392b; +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover, +.nav-list .item.active { + background: #2980b9; + color: #fff; +} +.nav-list .item:hover a, +.nav-list .item.active a { + color: #fff; +} +.nav-list .disable { + text-align: center; + background: #fafafa; + color: #aaa; +} +.nav-list .item > a { + padding: 0 10px; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { + color: #f39c12; +} +.nav-list .item:hover.empty a, +.nav-list .item.active.empty a { + color: #fff; + background: #f39c12; +} +.nav-list .item.error a { + color: #bd362f; +} +.nav-list .item:hover.error a, +.nav-list .item.active.error a { + color: #fff; + background: #bd362f; +} + +.nav-list .nav-header { + padding: 0 10px; + font-weight: bold; + background: #34495e; + color: #fff; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + text-align: right; + background: #34495e; + color: #fff; +} +.nav-head a { + color: #fff; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + font-size: 0.8rem; + text-align: left; + border: 1px solid #95a5a6; + border-radius: 3px; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + background: #fff; + border-top: 1px solid #95a5a6; + border-left: 1px solid #95a5a6; +} +.dropdown-header { + padding: 0 5px 5px; + font-weight: bold; + text-align: left; + color: #34495e; +} +.dropdown-menu > .item > a { + padding: 0 25px; + line-height: 2.5em; +} +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; +} +.dropdown-menu > .item:hover { + background: #2980b9; + color: #fff; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + text-decoration: none; + color: #fff; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; +} + +.separator { + margin: 5px 0; + border-bottom: 1px solid #ddd; +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + font-size: 0.9em; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + text-shadow: 0 0 1px #eee; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + text-decoration: underline; + color: inherit; +} +.alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; +} +.alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} + +/*=== Pagination */ +.pagination { + text-align: center; + font-size: 0.8em; + background: #ecf0f1; + color: #000; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; + background: #34495e; + color: #ecf0f1; +} +.pagination .item a { + display: block; + font-style: italic; + line-height: 3em; + text-decoration: none; + color: #000; +} +.pagination .item a:hover { + background: #34495e; + color: #ecf0f1; +} + +.pagination .loading, +.pagination a:hover.loading { + font-size: 0; + background: url("loader.gif") center center no-repeat #34495e; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 85px; + background: #ecf0f1; +} +.header > .item { + padding: 10px; + vertical-align: middle; + text-align: center; +} +.header > .item.title{ + width: 230px; +} +.header > .item.title h1 { + margin: 0.5em 0; +} +.header > .item.title h1 a { + text-decoration: none; +} +.header > .item.search input { + width: 230px; +} +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + height: calc(100% - 85px); +} +.aside { + background: #ecf0f1; +} +.aside.aside_flux { + padding: 10px 0 50px; + background: #ecf0f1; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.category { + width: 233px; + margin: 10px auto; + text-align: left; +} +.category .btn:first-child { + position: relative; + width: 212px; +} +.category.stick .btn:first-child { + width: 175px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 5px; + padding: 1px 5px; + background: #3498DB; + color: #fff; + border-radius: 5px; +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { + background: #2980b9; +} +.categories .feeds .item.empty.active { + background: #f39c12; +} +.categories .feeds .item.error.active { + background: #bd362f; +} +.categories .feeds .item.empty .feed { + color: #e67e22; +} +.categories .feeds .item.error .feed { + color: #bd362f; +} +.categories .feeds .item.active .feed, +.categories .feeds .item.empty.active .feed, +.categories .feeds .item.error.active .feed { + color: #fff; +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + vertical-align: middle; + background-color: #95a5a6; + border-radius: 3px; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 200px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} + +/*=== New article notification */ +#new-article { + text-align: center; + font-size: 0.9em; + background: #3498db; +} +#new-article:hover { + background: #2980b9; +} +#new-article > a { + line-height: 3em; + font-weight: bold; + color: #fff; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-weight: bold; + line-height: 3em; + border-left: 2px solid #ecf0f1; +} +.day .name { + padding: 0 10px 0 0; + font-size: 1.8em; + opacity: 0.3; + font-style: italic; + text-align: right; + color: #aab; +} + +/*=== Index menu */ +.nav_menu { + text-align: center; + padding: 5px 0; +} + +/*=== Feed articles */ +.flux { + border-left: 2px solid #ecf0f1; +} +.flux:hover { + background: #fff; +} +.flux.current { + border-left-color: #3498db; +} +.flux.not_read { + background: #FFF3ED; + border-left-color: #FF5300; +} +.flux.not_read:not(.current):hover .item.title { + background: #FFF3ED; +} +.flux.favorite { + background: #FFF6DA; + border-left-color: #FFC300; +} +.flux.favorite:not(.current):hover .item.title { + background: #FFF6DA; +} +.flux.current { + background: #fff; +} + + +.flux_header { + font-size: 0.8rem; + cursor: pointer; + border-top: 1px solid #ecf0f1; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux .website .favicon { + padding: 5px; +} +.flux .date { + font-size: 0.7rem; + color: #666; +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { + color: #000; +} + +.content hr { + margin: 30px 10px; + height: 1px; + background: #ddd; + border: 0; + box-shadow: 0 2px 5px #ccc; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #222; + color: #fff; + font-size: 0.9rem; + border-radius: 3px; +} +.content code { + padding: 2px 5px; + color: #dd1144; + background: #fafafa; + border: 1px solid #eee; + border-radius: 3px; +} +.content pre code { + background: transparent; + color: #fff; + border: none; +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + background: #fafafa; + color: #333; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + z-index: 10; + vertical-align: middle; + background: #ddd; + color: #666; + border-radius: 3px; + border: none; +} +.notification.good { + background: #1abc9c; + color: #fff; +} +.notification.bad { + background: #e74c3c; + color: #fff; +} +.notification a.close { + padding: 0 15px; + line-height: 3em; + border-radius: 0 3px 3px 0; +} +.notification.good a.close:hover { + background: #16a085; +} +.notification.bad a.close:hover { + background: #c0392b; +} + +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; + background: #ecf0f1; +} +#bigMarkAsRead:hover { + background: #34495e; + color: #fff; +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; + background: #34495e; +} + +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; + background: #ecf0f1; + color: #34495e; + border: none; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #999; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global .box-category { + text-align: left; + border: 1px solid #ddd; + border-radius: 5px; +} +#stream.global .category { + margin: 0; +} +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + line-height: 2em; + font-size: 1.2rem; + background: #ecf0f1; + color: #333; + border-bottom: 1px solid #ddd; + border-radius: 5px 5px 0 0; +} +#stream.global .btn:not([data-unread="0"]) { + font-weight: bold; + background: #3498db; + color: #fff; +} +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + font-weight: bold; + background: none; + border: 0; + color: #fff; +} +#stream.global .box-category .feeds { + max-height: 250px; +} +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; +} + +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input, +.aside.aside_feed .nav-form select { + width: 140px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { + border: none; +} +.stat > table td, +.stat > table th { + border-bottom: 1px solid #ddd; +} + +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 270px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + overflow: hidden; + border: 1px solid #aaa; +} +.log { + margin: 10px 0; + padding: 5px 2%; + overflow: auto; + font-size: 0.8rem; + background: #fafafa; + color: #666; +} + +.log > .date { + margin: 0 10px 0 0; + padding: 5px 10px; + border-radius: 20px; +} +.log.error > .date { + background: #e74c3c; + color: #fff; +} +.log.warning > .date { + background: #f39c12; +} +.log.notice > .date { + background: #ecf0f1; +} +.log.debug > .date { + background: #111; + color: #eee; +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .aside { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 32px; + height: 32px; + line-height: 30px; + text-align: center; + background: #34495e; + border-radius: 0 0 0 5px; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + font-size: 1.1rem; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification { + border-radius: 0; + } + .notification a.close { + display: block; + left: 0; + background: transparent; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } +} diff --git a/public/themes/flat-design/icons/add.svg b/p/themes/Flat/icons/add.svg index 15767a3ad..15767a3ad 100644 --- a/public/themes/flat-design/icons/add.svg +++ b/p/themes/Flat/icons/add.svg diff --git a/public/themes/flat-design/icons/all.svg b/p/themes/Flat/icons/all.svg index d20e0f5bf..d20e0f5bf 100644 --- a/public/themes/flat-design/icons/all.svg +++ b/p/themes/Flat/icons/all.svg diff --git a/public/themes/flat-design/icons/category.svg b/p/themes/Flat/icons/category.svg index 5e61aeea5..5e61aeea5 100644 --- a/public/themes/flat-design/icons/category.svg +++ b/p/themes/Flat/icons/category.svg diff --git a/public/themes/flat-design/icons/close.svg b/p/themes/Flat/icons/close.svg index 629fda7ff..629fda7ff 100644 --- a/public/themes/flat-design/icons/close.svg +++ b/p/themes/Flat/icons/close.svg diff --git a/public/themes/flat-design/icons/configure.svg b/p/themes/Flat/icons/configure.svg index 969c5719f..969c5719f 100644 --- a/public/themes/flat-design/icons/configure.svg +++ b/p/themes/Flat/icons/configure.svg diff --git a/public/themes/flat-design/icons/down.svg b/p/themes/Flat/icons/down.svg index 31730626f..31730626f 100644 --- a/public/themes/flat-design/icons/down.svg +++ b/p/themes/Flat/icons/down.svg diff --git a/p/themes/Flat/icons/icon.svg b/p/themes/Flat/icons/icon.svg new file mode 100644 index 000000000..9657d020c --- /dev/null +++ b/p/themes/Flat/icons/icon.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <title>Logo FreshRSS</title> + <circle fill="#2980b9" cx="128" cy="128" r="33"/> + <g fill="none" stroke="#2980b9" stroke-width="24"> + <g stroke-opacity="0.3"> + <path d="M12,128 A116,116 0 1,1 128,244"/> + <path d="M54,128 A74,74 0 1,1 128,202"/> + </g> + <path d="M128,12 A116,116 0 0,1 244,128"/> + <path d="M128,54 A74,74 0 0,1 202,128"/> + </g> +</svg> diff --git a/p/themes/Flat/icons/key.svg b/p/themes/Flat/icons/key.svg new file mode 100644 index 000000000..77d0218e5 --- /dev/null +++ b/p/themes/Flat/icons/key.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(-340.99994,-257)" fill="#ffffff"> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m346,260c-2.7496,0-5,2.2504-5,5s2.2504,5,5,5c1.5862,0,2.9034-0.84459,3.8125-2h4.8438,0.75l0.21875-0.75,1.0312-4,0.3125-1.25h-1.2812-5.875c-0.90914-1.1554-2.2263-2-3.8125-2zm0,2c1.1158,0,2.0379,0.59507,2.5625,1.5l0.3125,0.5h0.5625,4.9688l-0.53125,2h-4.4375-0.5625l-0.3125,0.5c-0.52462,0.90493-1.4466,1.5-2.5625,1.5-1.6687,0-3-1.3313-3-3s1.3313-3,3-3z"/> +<path opacity="0.35" style="enable-background:accumulate;color:#000000;" d="M355.5,265,350,265,349.44,267,355,267z" fill-rule="nonzero"/> +<path style="enable-background:accumulate;color:#000000;" d="m346,265c0,0.55228-0.44772,1-1,1s-1-0.44772-1-1,0.44772-1,1-1,1,0.44772,1,1z" fill-rule="nonzero"/> +</g> +</svg> diff --git a/public/themes/flat-design/icons/next.svg b/p/themes/Flat/icons/next.svg index d75cc40f5..d75cc40f5 100644 --- a/public/themes/flat-design/icons/next.svg +++ b/p/themes/Flat/icons/next.svg diff --git a/public/themes/flat-design/icons/previous.svg b/p/themes/Flat/icons/prev.svg index 9ba03ceb2..9ba03ceb2 100644 --- a/public/themes/flat-design/icons/previous.svg +++ b/p/themes/Flat/icons/prev.svg diff --git a/public/themes/flat-design/icons/refresh.svg b/p/themes/Flat/icons/refresh.svg index 8f95bf443..8f95bf443 100644 --- a/public/themes/flat-design/icons/refresh.svg +++ b/p/themes/Flat/icons/refresh.svg diff --git a/p/themes/Flat/icons/rss.svg b/p/themes/Flat/icons/rss.svg new file mode 100644 index 000000000..4d4faf7ae --- /dev/null +++ b/p/themes/Flat/icons/rss.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g fill-rule="nonzero" transform="translate(-561,-301.00012)" fill="#fff"> +<path style="enable-background:new;color:#000000;" d="m325.06,97.188c0,1.7872-0.89543,3.2361-2,3.2361s-2-1.4488-2-3.2361c0-1.7872,0.89543-3.2361,2-3.2361s2,1.4488,2,3.2361z" transform="matrix(1.0000007,0,0,0.61803426,241.93747,252.93479)"/> +<path style="enable-background:new;color:#000000;" d="m563,303,0,1c0,0.55016,0.45347,1,1,1,4.9706,0,9,4.0294,9,9,0,0.55016,0.45347,1,1,1h1v-1c0-6.0751-4.9249-11-11-11h-1zm0,4,0,1c0,0.55016,0.45347,1,1,1,2.7614,0,5,2.2386,5,5,0,0.55016,0.45347,1,1,1h1v-1c0-3.866-3.134-7-7-7h-1z"/> +</g> +</svg>
\ No newline at end of file diff --git a/public/themes/flat-design/icons/search.svg b/p/themes/Flat/icons/search.svg index bca7571b4..bca7571b4 100644 --- a/public/themes/flat-design/icons/search.svg +++ b/p/themes/Flat/icons/search.svg diff --git a/public/themes/flat-design/icons/up.svg b/p/themes/Flat/icons/up.svg index 3ab11b168..3ab11b168 100644 --- a/public/themes/flat-design/icons/up.svg +++ b/p/themes/Flat/icons/up.svg diff --git a/p/themes/Flat/icons/view-global.svg b/p/themes/Flat/icons/view-global.svg new file mode 100644 index 000000000..9d2cd0f32 --- /dev/null +++ b/p/themes/Flat/icons/view-global.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-61-867)" fill="#fff" color="#000"><rect height="2" rx=".385" ry=".379" width="2" x="64" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="64" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="64" y="878"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="878"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="878"/></g></svg>
\ No newline at end of file diff --git a/p/themes/Flat/icons/view-normal.svg b/p/themes/Flat/icons/view-normal.svg new file mode 100644 index 000000000..8eda2047e --- /dev/null +++ b/p/themes/Flat/icons/view-normal.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-40-746)" fill="#fff" color="#fff"><path d="m43 749h10v2h-10z"/><path d="m43 753h10v2h-10z"/><path d="m43 757h10v2h-10z"/></g></svg>
\ No newline at end of file diff --git a/p/themes/Flat/icons/view-reader.svg b/p/themes/Flat/icons/view-reader.svg new file mode 100644 index 000000000..0e4f60a1a --- /dev/null +++ b/p/themes/Flat/icons/view-reader.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-181-867)" fill="#fff" color="#fff"><path d="m 181,868 0,1 0,11 0,1 1,0 5,0 c 0.1754,0 0.52538,0.15166 0.8125,0.34375 0.28712,0.19209 0.46875,0.375 0.46875,0.375 L 189,882.4375 l 0.71875,-0.75 c 0,0 0.8963,-0.6875 1.28125,-0.6875 l 5,0 1,0 0,-1 0,-11 0,-1 -1,0 -5,0 c -0.87652,0 -1.56017,0.34756 -2.03125,0.6875 -0.0301,-0.0207 -0.031,-0.0105 -0.0625,-0.0312 C 188.44557,868.35254 187.82811,868 187,868 l -5,0 -1,0 z m 2,2 4,0 c 0.13821,0 0.51476,0.14746 0.8125,0.34375 0.29774,0.19629 0.5,0.375 0.5,0.375 l 0.71875,0.6875 0.6875,-0.71875 c 0,0 0.89975,-0.6875 1.28125,-0.6875 l 4,0 0,9 -4,0 c -0.87693,0 -1.56008,0.34735 -2.03125,0.6875 -0.0196,-0.0135 -0.011,-0.0177 -0.0312,-0.0312 C 188.47725,879.34834 187.83512,879 187,879 l -4,0 0,-9 z"/><g transform="scale(-1 1)"><rect height="2" rx=".375" width="3" x="-187" y="872"/><rect height="2" rx=".375" width="3" x="-187" y="875"/><rect height="2" rx=".375" width="3" x="-194" y="872"/><rect height="2" rx=".375" width="3" x="-194" y="875"/></g></g></svg>
\ No newline at end of file diff --git a/public/themes/flat-design/loader.gif b/p/themes/Flat/loader.gif Binary files differindex ce36565b3..ce36565b3 100644 --- a/public/themes/flat-design/loader.gif +++ b/p/themes/Flat/loader.gif diff --git a/public/themes/flat-design/metadata.json b/p/themes/Flat/metadata.json index 6b94d11c2..3afdc98af 100644 --- a/public/themes/flat-design/metadata.json +++ b/p/themes/Flat/metadata.json @@ -2,6 +2,6 @@ "name": "Flat design", "author": "Marien Fressinaud", "description": "Thème plat pour FreshRSS", - "version": 0.1, - "files": ["global.css", "freshrss.css"] + "version": 0.2, + "files": ["_template.css", "flat.css"] }
\ No newline at end of file diff --git a/public/themes/default/loader.gif b/p/themes/Origine/loader.gif Binary files differindex 5ff26f0e3..5ff26f0e3 100644 --- a/public/themes/default/loader.gif +++ b/p/themes/Origine/loader.gif diff --git a/public/themes/default/metadata.json b/p/themes/Origine/metadata.json index d316ec517..774320eb4 100644 --- a/public/themes/default/metadata.json +++ b/p/themes/Origine/metadata.json @@ -1,7 +1,7 @@ { - "name": "Default", + "name": "Origine", "author": "Marien Fressinaud", "description": "Le thème par défaut pour FreshRSS", - "version": 0.1, - "files": ["global.css", "freshrss.css"] + "version": 0.2, + "files": ["_template.css", "origine.css"] } diff --git a/p/themes/Origine/origine.css b/p/themes/Origine/origine.css new file mode 100644 index 000000000..08fc08379 --- /dev/null +++ b/p/themes/Origine/origine.css @@ -0,0 +1,1034 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + background: #fafafa; +} + +/*=== Links */ +a, button.as-link { + color: #0062be; + outline: none; +} + +/*=== Forms */ +legend { + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #ddd; + font-size: 1.4em; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 5px; + background: #fdfdfd; + border: 1px solid #bbb; + border-radius: 3px; + color: #666; + line-height: 25px; + vertical-align: middle; + box-shadow: 0 2px 2px #eee inset; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { + color: #0062be; + border-color: #33bbff; + box-shadow: 0 2px 2px #ddddff inset; +} +input:invalid, select:invalid { + border-color: #f00; + box-shadow: 0 0 2px 2px #fdd inset; +} +input:disabled, select:disabled { + background: #eee; +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; + border: 1px solid #ddd; +} +th { + background: #f6f6f6; +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group.form-actions { + padding: 5px 0; + background: #f4f4f4; + border-top: 1px solid #ddd; +} +.form-group.form-actions .btn { + margin: 0 10px; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +.stick { + vertical-align: middle; + font-size: 0; +} +.stick input, +.stick .btn { + border-radius: 0; +} +.stick .btn:first-child, +.stick input:first-child { + border-radius: 3px 0 0 3px; +} +.stick .btn-important:first-child { + border-right: 1px solid #06f; +} +.stick .btn:last-child, +.stick input:last-child { + border-radius: 0 3px 3px 0; +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { + border-left: none; +} +.stick input + .btn { + border-top: 1px solid #bbb; +} +.stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; +} + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + margin: 0; + padding: 5px 10px; + background: #fff; + background: linear-gradient(to bottom, #fff 0%, #eee 100%); + background: -moz-linear-gradient(top, #fff 0%, #eee 100%); + background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); + background: -o-linear-gradient(top, #fff 0%, #eee 100%); + background: -ms-linear-gradient(top, #fff 0%, #eee 100%); + border-radius: 3px; + border: 1px solid #ddd; + border-bottom: 1px solid #aaa; + border-right: 1px solid #aaa; + color: #666; + text-shadow: 0px -1px 0 #ddd; + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} +a.btn { + min-height: 25px; + line-height: 25px; +} +.btn:hover { + background: #f0f0f0; + background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); + background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); + text-decoration: none; +} +.btn.active, +.btn:active, +.dropdown-target:target ~ .btn.dropdown-toggle { + box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; + background: #eee; +} + +.btn-important { + background: #0084CC; + background: linear-gradient(to bottom, #0084CC, #0045CC); + background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); + background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); + color: #fff; + border: 1px solid #0062B7; + text-shadow: 0px -1px 0 #aaa; + font-weight: normal; +} +.btn-important:hover { + background: linear-gradient(to bottom, #0066CC, #0045CC); + background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); + background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); +} +.btn-important:active { + background: #0044CB; + box-shadow: none; +} + +.btn-attention { + background: #E95B57; + background: linear-gradient(to bottom, #E95B57, #BD362F); + background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); + background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); + color: #fff; + border: 1px solid #C44742; + text-shadow: 0px -1px 0px #666; +} +.btn-attention:hover { + background: linear-gradient(to bottom, #D14641, #BD362F); + background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); + background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); +} +.btn-attention:active { + background: #BD362F; + box-shadow: none; +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover { + background: #fafafa; +} +.nav-list .item:hover a { + color: #003388; +} +.nav-list .item.active { + background: #0062BE; + color: #fff; +} +.nav-list .item.active a { + color: #fff; +} +.nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; +} +.nav-list .item > a { + padding: 0 10px; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { + color: #f39c12; +} +.nav-list .item.active.empty a { + color: #fff; + background: #f39c12; +} +.nav-list .item.error a { + color: #BD362F; +} +.nav-list .item.active.error a { + color: #fff; + background: #BD362F; +} + +.nav-list .nav-header { + padding: 0 10px; + color: #888; + background: #f4f4f4; + border-bottom: 1px solid #ddd; + font-weight: bold; + text-shadow: 0 0 1px #ddd; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + background: #fff; + background: linear-gradient(to bottom, #fff, #f0f0f0); + background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); + border-bottom: 1px solid #ddd; + text-align: right; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + border: 1px solid #ddd; + border-radius: 5px; + box-shadow: 3px 3px 3px #ddd; + font-size: 0.8rem; + text-align: left; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #fff; + border-top: 1px solid #ddd; + border-left: 1px solid #ddd; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} +.dropdown-header { + padding: 0 5px 5px; + color: #888; + font-weight: bold; + text-align: left; +} +.dropdown-menu > .item { +} +.dropdown-menu > .item > a { + padding: 0 25px; + line-height: 2.5em; +} +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; +} +.dropdown-menu > .item:hover { + background: #0062BE; + color: #fff; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + color: #fff; + text-decoration: none; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; +} + +.separator { + margin: 5px 0; + border-bottom: 1px solid #ddd; +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + text-shadow: 0 0 1px #eee; + font-size: 0.9em; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + color: inherit; + text-decoration: underline; +} +.alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; +} +.alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} + +/*=== Pagination */ +.pagination { + background: #fafafa; + text-align: center; + color: #333; + font-size: 0.8em; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; +} +.pagination .item a { + display: block; + color: #333; + font-style: italic; + line-height: 3em; + text-decoration: none; +} +.pagination .item a:hover { + background: #ddd; +} +.pagination:first-child .item { + border-bottom: 1px solid #aaa; +} +.pagination:last-child .item { + border-top: 1px solid #aaa; +} + +.pagination .loading, +.pagination a:hover.loading { + background: url("loader.gif") center center no-repeat #fff; + font-size: 0; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 85px; + background: #f4f4f4; +} +.header > .item { + padding: 10px; + border-bottom: 1px solid #aaa; + vertical-align: middle; + text-align: center; +} +.header > .item.title{ + width: 230px; +} +.header > .item.title h1 { + margin: 0.5em 0; + text-shadow: 1px -1px 0 #ccc; +} +.header > .item.title h1 a { + text-decoration: none; +} +.header > .item.search input { + width: 230px; +} +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + height: calc(100% - 85px); +} +.aside { + border-right: 1px solid #aaa; + background: #fff; +} +.aside.aside_flux { + padding: 10px 0 50px; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.category { + width: 235px; + margin: 10px auto; + text-align: left; +} +.category .btn:first-child { + position: relative; + width: 213px; +} +.category.stick .btn:first-child { + width: 176px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: #ccc; + color: #fff; + border: 1px solid #bbb; + border-radius: 5px; + box-shadow: 1px 3px 3px #aaa inset; + text-shadow: 0 0 1px #aaa; +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { + background: #0062BE; +} +.categories .feeds .item.empty.active { + background: #e67e22; +} +.categories .feeds .item.error.active { + background: #BD362F; +} +.categories .feeds .item.empty .feed { + color: #e67e22; +} +.categories .feeds .item.error .feed { + color: #BD362F; +} +.categories .feeds .item.active .feed, +.categories .feeds .item.empty.active .feed, +.categories .feeds .item.error.active .feed { + color: #fff; +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #fff; + border-radius: 3px; + vertical-align: middle; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 200px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} + +/*=== New article notification */ +#new-article { + background: #0084CC; + text-align: center; + font-size: 0.9em; +} +#new-article:hover { + background: #0066CC; +} +#new-article > a { + line-height: 3em; + color: #fff; + font-weight: bold; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-weight: bold; + line-height: 3em; + background: #fff; + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; +} +#new-article + .day { + border-top: none; +} +.day .name { + padding: 0 10px 0 0; + color: #aab; + font-size: 1.8em; + opacity: 0.3; + text-shadow: 0px -1px 0px #666; + font-style: italic; + text-align: right; +} + +/*=== Index menu */ +.nav_menu { + background: #fafafa; + border-bottom: 1px solid #aaa; + text-align: center; + padding: 5px 0; +} + +/*=== Feed articles */ +.flux { + border-left: 2px solid #aaa; + background: #fafafa; +} +.flux:hover { + background: #fff; +} +.flux.current { + border-left: 2px solid #0062BE; +} +.flux.not_read { + border-left: 2px solid #FF5300; + background: #FFF3ED; +} +.flux.not_read:not(.current):hover .item.title { + background: #FFF3ED; +} +.flux.favorite { + border-left: 2px solid #FFC300; + background: #FFF6DA; +} +.flux.favorite:not(.current):hover .item.title { + background: #FFF6DA; +} +.flux.current { + background: #fff; +} + + +.flux_header { + border-top: 1px solid #ddd; + font-size: 0.8rem; + cursor: pointer; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux .website .favicon { + padding: 5px; +} +.flux .date { + color: #666; + font-size: 0.7rem; +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { + color: #000; +} + +.content hr { + margin: 30px 10px; + height: 1px; + background: #ddd; + border: 0; + box-shadow: 0 2px 5px #ccc; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #222; + color: #fff; + font-size: 0.9rem; + border-radius: 3px; +} +.content code { + padding: 2px 5px; + color: #dd1144; + background: #fafafa; + border: 1px solid #eee; + border-radius: 3px; +} +.content pre code { + background: transparent; + color: #fff; + border: none; +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + background: #fafafa; + color: #333; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + border: 1px solid #eeb; + border-radius: 3px; + box-shadow: 0 0 5px #ddd; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + z-index: 10; + vertical-align: middle; +} +.notification.good { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.notification.bad { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} +.notification a.close { + padding: 0 15px; + line-height: 3em; +} +.notification.good a.close:hover { + background: #eeb; +} +.notification.bad a.close:hover { + background: #ecc; +} + +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 #aaa; + color: #666; + background: #fafafa; +} +#bigMarkAsRead:hover { + color: #0062be; + background: #fff; + box-shadow: 0 -5px 10px #eee inset; +} +#bigMarkAsRead:hover .bigTick { + text-shadow: 0 0 5px #0062be; +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + margin: 0; + background: #fff; + border-top: 1px solid #ddd; + text-align: center; + line-height: 3em; + table-layout: fixed; +} + +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; + border: none; + background: #f0f0f0; + color: #333; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global .box-category { + background: #fff; + border-radius: 5px; + text-align: left; + box-shadow: 0 0 3px #bbb; +} +#stream.global .category { + margin: 0; +} +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + background: #f6f6f6; + border: none; + border-bottom: 1px solid #ddd; + border-radius: 5px 5px 0 0; + line-height: 2em; + font-size: 1.2rem; +} +#stream.global .btn:not([data-unread="0"]) { + background: #0084CC; + color: #fff; + font-weight: bold; + text-shadow: none; +} +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #fff; + font-weight: bold; + box-shadow: none; + text-shadow: none; +} +#stream.global .box-category .feeds { + max-height: 250px; +} +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; +} + +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input, +.aside.aside_feed .nav-form select { + width: 140px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { + border: none; +} +.stat > table td, +.stat > table th { + border-bottom: 1px solid #ddd; +} + +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 270px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + border: 1px solid #aaa; + border-radius: 5px; + overflow: hidden; +} +.log { + padding: 5px 10px; + background: #fafafa; + color: #333; + font-size: 0.8rem; +} +.log+.log { + border-top: 1px solid #aaa; +} +.log .date { + display: block; + font-weight: bold; +} +.log.error { + background: #fdd; + color: #844; +} +.log.warning { + background: #ffe; + color: #c95; +} +.log.notice { + background: #f4f4f4; + color: #aaa; +} +.log.debug { + background: #333; + color: #eee; +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .aside { + box-shadow: 3px 0 3px #aaa; + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + background: #f6f6f6; + border-left: 1px solid #ddd; + border-bottom: 1px solid #ddd; + border-radius: 0 0 0 5px; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + font-size: 1.1rem; + text-shadow: none; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification a.close { + display: block; + left: 0; + background: transparent; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } +} diff --git a/p/themes/Pafat/README.md b/p/themes/Pafat/README.md new file mode 100644 index 000000000..adddc1bf0 --- /dev/null +++ b/p/themes/Pafat/README.md @@ -0,0 +1,4 @@ +Pafat +===== + +Thème Pafat pour FreshRSS diff --git a/p/themes/Pafat/icons/all.svg b/p/themes/Pafat/icons/all.svg new file mode 100644 index 000000000..14fa80366 --- /dev/null +++ b/p/themes/Pafat/icons/all.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-40.0002,-746)" fill="#FFF"> +<rect style="color:#FFF;" height="2.0002" width="9.9996" y="749" x="43"/> +<rect style="color:#FFF;" height="2.0002" width="9.9996" y="753" x="43"/> +<rect style="color:#FFF;" height="2.0002" width="9.9996" y="757" x="43"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/bookmark.svg b/p/themes/Pafat/icons/bookmark.svg new file mode 100644 index 000000000..70d0c81fb --- /dev/null +++ b/p/themes/Pafat/icons/bookmark.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="enable-background:accumulate;color:#000000;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#FFF"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/down.svg b/p/themes/Pafat/icons/down.svg new file mode 100644 index 000000000..050d6b969 --- /dev/null +++ b/p/themes/Pafat/icons/down.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-181.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m195.03,751,0,1c-0.00091,0.0111,0.00059,0.021-0.00009,0.0312-0.0112,0.25496-0.12835,0.50994-0.31251,0.6875l-5.7188,6.2977-5.7188-6.2977c-0.18821-0.1881-0.28121-0.45346-0.28122-0.71875v-1h1c0.26531,0.00007,0.53059,0.0931,0.71873,0.28131l4.2812,4.829,4.2813-4.829c0.19464-0.21073,0.46925-0.30315,0.74998-0.2813z" fill-rule="nonzero" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/icon.svg b/p/themes/Pafat/icons/icon.svg new file mode 100644 index 000000000..b489e18da --- /dev/null +++ b/p/themes/Pafat/icons/icon.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <title>Logo FreshRSS</title> + <circle fill="#C5C6CA" cx="128" cy="128" r="33"/> + <g fill="none" stroke="#C5C6CA" stroke-width="24"> + <g stroke-opacity="0.3"> + <path d="M12,128 A116,116 0 1,1 128,244"/> + <path d="M54,128 A74,74 0 1,1 128,202"/> + </g> + <path d="M128,12 A116,116 0 0,1 244,128"/> + <path d="M128,54 A74,74 0 0,1 202,128"/> + </g> +</svg> diff --git a/p/themes/Pafat/icons/link.svg b/p/themes/Pafat/icons/link.svg new file mode 100644 index 000000000..cc30f9f8b --- /dev/null +++ b/p/themes/Pafat/icons/link.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16.004" width="16"> +<g fill="#666" transform="translate(-183,-529)"> +<path style="enable-background:accumulate;color:#000000;" d="m191,533.85,0,10.38-2.344-2.2882-1.3394,2.7346c-0.32808,0.73962-2.0337,0.14492-1.5487-0.84412l1.3255-2.8393h-2.9579l6.8645-7.1436z" fill-rule="nonzero" display="block"/> +<path d="m190.16,530.06c-3.8266,0.46006-6.5788,3.9578-6.0938,7.7812,0.13127,1.0347,0.29377,1.3818,0.29377,1.3818l1.675-1.6318c-0.33104-2.7534,1.6216-5.2315,4.375-5.5625,2.7534-0.33104,5.2315,1.6216,5.5625,4.375,0.31355,2.608-1.3913,5.0249-3.9688,5.5312l0.0312,2s0.52086-0.1059,0.62354-0.13097c3.4156-0.83385,5.7063-4.1273,5.2827-7.6503-0.46006-3.8266-3.9547-6.5538-7.7812-6.0938z" style="baseline-shift:baseline;block-progression:tb;color:#666;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;"/> +<path opacity="0.3" style="enable-background:accumulate;color:#666;" d="m187.11,536.81,0-0.20574-0.19826,0.0186c0.0165-0.13095,0.0329-0.26167,0.0496-0.3926h-0.11578l-0.11556,0.14959-0.11578,0.0559-0.1653-0.0932-0.0165-0.20575,0.0331-0.22438,0.24798-0.18688h0.19826l0.0329-0.11229,0.24786,0.0559,0.18183,0.2246,0.0331-0.37419,0.31401-0.26167,0.11567-0.28055,0.23133-0.0934,0.1322-0.18688,0.29738-0.0564,0.14885-0.22415h-0.44623l0.28094-0.13095h0.19814l0.28106-0.0937,0.0331-0.11186-0.0992-0.0937-0.11567-0.0375,0.0331-0.11208-0.0826-0.16822-0.19837,0.0746,0.0331-0.14947-0.23134-0.13096-0.18171,0.3177,0.0165,0.11229-0.18171,0.075-0.11578,0.24302-0.0495-0.22438-0.31402-0.13095-0.0496-0.16822,0.41315-0.24325,0.18182-0.16822,0.0165-0.20563-0.0991-0.0562-0.13219-0.0188-0.0826,0.20575s-0.1382,0.0271-0.17373,0.0358c-0.45378,0.41804-1.3707,1.3204-1.5837,3.024,0.008,0.0395,0.15441,0.26854,0.15441,0.26854l0.347,0.20552,0.347,0.0937m3.9661-4.3003-0.4298-0.16833-0.49552,0.0561-0.61161,0.16822-0.11567,0.11229,0.38008,0.26167,0,0.14959-0.14875,0.14959,0.19846,0.39294,0.13188-0.075,0.16561-0.26168c0.2553-0.0789,0.4842-0.16833,0.72686-0.28053l0.19846-0.5048m2.5292,0.34192-0.375,0.0937-0.21875,0.15625,0,0.125-0.375,0.25,0.0937,0.34375,0.21875-0.15625,0.125,0.15625,0.15625,0.0937,0.0937-0.28125-0.0625-0.15625,0.0625-0.0937,0.21875-0.1875,0.0937,0-0.0937,0.21875,0,0.1875c0.0892-0.0242,0.1588-0.051,0.25-0.0625l-0.25,0.1875v0.125l-0.3125,0.21875-0.28125-0.0625v-0.15625l-0.125,0.0625,0.0625,0.15625h-0.21875l-0.125,0.21875-0.15625,0.15625-0.0937,0.0312v0.1875l0.0312,0.15625h-0.0312v0.53125l0.0625-0.0312,0.0937-0.21875,0.1875-0.125,0.0312-0.0937,0.28125-0.0625,0.15625,0.1875,0.1875,0.0937-0.0937,0.1875,0.15625-0.0312,0.0625-0.21875-0.1875-0.21875h0.0625l0.21875,0.15625,0.0312,0.21875,0.15625,0.21875,0.0625-0.3125,0.0937-0.0312c0.0959,0.0996,0.1692,0.23163,0.25,0.34375h0.28125l0.1875,0.125-0.0937,0.0937-0.15625,0.15625h-0.25l-0.34375-0.0937h-0.1875l-0.125,0.15625-0.34375-0.375-0.25-0.0625-0.375,0.0625-0.15625,0.0937v2.4062l0.0312,0.0312,0.25-0.15625,0.0937,0.0937h0.28125l0.125,0.15625-0.0937,0.3125,0.1875,0.1875v0.375l0.125,0.25-0.0937,0.25c-0.009,0.16159,0,0.30714,0,0.46875,0.0795,0.21894,0.14355,0.43575,0.21875,0.65625l0.0625,0.34375v0.1875h0.125l0.21875-0.125h0.25l0.375-0.4375-0.0312-0.15625,0.25-0.21875-0.1875-0.1875,0.21875-0.1875,0.21875-0.125,0.0937-0.125-0.0625-0.25v-0.59375l0.1875-0.375,0.1875-0.25,0.25-0.5625v-0.15625c-0.11654,0.0146-0.22972,0.0231-0.34375,0.0312-0.0722,0.005-0.14446,0-0.21875,0-0.12359-0.25961-0.2183-0.50966-0.3125-0.78125l-0.15625-0.1875-0.0937-0.3125,0.0625-0.0625,0.21875,0.25,0.25,0.5625,0.15625,0.15625-0.0625,0.21875,0.15625,0.15625,0.25-0.25,0.3125-0.21875,0.15625-0.1875v-0.21875c-0.0389-0.0732-0.0547-0.14545-0.0937-0.21875l-0.15625,0.1875-0.125-0.15625-0.1875-0.125v-0.28125l0.21875,0.21875,0.21875-0.0312c0.10166,0.0923,0.19205,0.20751,0.28125,0.3125l0.15625-0.1875c0-0.17463-0.19976-1.0204-0.625-1.75-0.42526-0.72932-1.1562-1.4062-1.1562-1.4062l-0.0625,0.0937-0.21875,0.21875-0.25-0.25h0.25l0.125-0.125-0.46875-0.0937-0.25-0.0937z" fill-rule="nonzero"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/login.svg b/p/themes/Pafat/icons/login.svg new file mode 100644 index 000000000..cf2b347ad --- /dev/null +++ b/p/themes/Pafat/icons/login.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-181.0002,-237)" fill="#666"> +<path style="color:#666;" d="m184,244c-0.554,0-1,0.446-1,1v0.53125,5.4688h12v-5.4688-0.53c0-0.554-0.446-1-1-1h-10z" fill-rule="nonzero"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m188,238c-1.6447,0-3,1.3553-3,3v7c0,1.6447,1.3553,3,3,3h2c1.6447,0,3-1.3553,3-3v-7c0-1.6447-1.3553-3-3-3h-2zm0,2,2,0c0.5713,0,1,0.4287,1,1v7c0,0.5713-0.4287,1-1,1h-2c-0.5713,0-1-0.4287-1-1v-7c0-0.5713,0.4287-1,1-1z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/logout.svg b/p/themes/Pafat/icons/logout.svg new file mode 100644 index 000000000..ac2d180d8 --- /dev/null +++ b/p/themes/Pafat/icons/logout.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-201.0002,-237)" fill="#666"> +<path style="color:#666;" d="m204,246c-0.554,0-1,0.446-1,1v0.53125,5.4688h12v-5.4688-0.53c0-0.554-0.446-1-1-1h-10z" fill-rule="nonzero"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m208,237c-1.6447,0-3,1.3553-3,3v3h2v-3c0-0.57129,0.42873-1,1-1h2c0.57127,0,1,0.42871,1,1v7h2v-7c0-1.6447-1.3553-3-3-3h-2z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/next.svg b/p/themes/Pafat/icons/next.svg new file mode 100644 index 000000000..59d512450 --- /dev/null +++ b/p/themes/Pafat/icons/next.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-121.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m125,749,1,0c0.0104-0.00012,0.0208-0.00046,0.0313,0,0.25495,0.0112,0.50987,0.12858,0.6875,0.3125l6.2977,5.7188-6.2977,5.7188c-0.18816,0.18819-0.45346,0.28125-0.71875,0.28125h-1v-1c0-0.26529,0.0931-0.53058,0.28125-0.71875l4.829-4.2812-4.829-4.2812c-0.21074-0.19463-0.30316-0.46925-0.28125-0.75z" fill-rule="nonzero" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/non-starred.svg b/p/themes/Pafat/icons/non-starred.svg new file mode 100644 index 000000000..e019daffd --- /dev/null +++ b/p/themes/Pafat/icons/non-starred.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-61.000202,-397)"> +<path style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" fill="#666" d="m69.003,398.01c-0.2659-0.00099-0.49859,0.1006-0.6647,0.2393-0.16611,0.13869-0.27742,0.32137-0.38968,0.50028-0.22453,0.35782-0.39269,0.76873-0.56546,1.2036-0.17277,0.43483-0.34713,0.88766-0.5046,1.2323-0.15747,0.34465-0.3456,0.57871-0.35862,0.58776-0.01287,0.009-0.30722,0.117-0.69576,0.15181-0.38855,0.0348-0.88122,0.0492-1.3632,0.0694-0.48202,0.0202-0.9703,0.0362-1.393,0.12817-0.21138,0.046-0.4154,0.10148-0.60434,0.20956s-0.36689,0.28631-0.45004,0.53002-0.04901,0.50627,0.03636,0.70157c0.08537,0.19531,0.22656,0.36514,0.3682,0.52344,0.2833,0.31663,0.6327,0.60557,1.0079,0.89849s0.77671,0.55926,1.0678,0.81027c0.29104,0.25101,0.45422,0.50796,0.45912,0.52271,0.0048,0.0146,0.03119,0.33498-0.05456,0.70231-0.08575,0.36732-0.2238,0.81174-0.35282,1.2603-0.12902,0.44861-0.25176,0.90196-0.2917,1.3184-0.01997,0.20819-0.04285,0.40729,0.0053,0.61409,0.04814,0.20679,0.1845,0.43007,0.39902,0.58168,0.21451,0.15161,0.44936,0.1881,0.66823,0.1701s0.43535-0.0703,0.63515-0.15132c0.39961-0.16214,0.80177-0.42851,1.2064-0.68231,0.40465-0.2538,0.80822-0.52155,1.1456-0.71107,0.33734-0.18952,0.6484-0.2686,0.66445-0.26854,0.01586,0.00006,0.30338,0.0951,0.63894,0.28732,0.33556,0.19221,0.72532,0.46503,1.1276,0.72205,0.40229,0.25702,0.81996,0.49752,1.218,0.66284,0.19903,0.0827,0.38893,0.15086,0.60762,0.17059,0.2187,0.0197,0.47978-0.031,0.69551-0.18105,0.21572-0.15001,0.33928-0.35235,0.38918-0.55877s0.04291-0.43517,0.02476-0.64358c-0.03632-0.41683-0.15899-0.86394-0.2841-1.3137-0.12511-0.44978-0.26165-0.88661-0.34421-1.2548-0.08256-0.36813-0.07169-0.68662-0.06666-0.70133,0.005-0.0145,0.18746-0.25247,0.4806-0.50101,0.29313-0.24854,0.67599-0.53755,1.0536-0.82731,0.37764-0.28976,0.76716-0.57158,1.0531-0.88579,0.14298-0.1571,0.2418-0.33444,0.32882-0.52904s0.136-0.42874,0.05481-0.67306-0.27108-0.41314-0.45912-0.52272-0.36679-0.18158-0.57782-0.22931c-0.42206-0.0955-0.91359-0.14009-1.3956-0.16426-0.48198-0.0242-0.94584-0.039-1.3342-0.077s-0.71565-0.13122-0.72859-0.14037c-0.01279-0.009-0.18402-0.23636-0.3384-0.58217s-0.32658-0.78819-0.49548-1.2243c-0.1689-0.4361-0.33477-0.86429-0.55609-1.2238-0.11067-0.17977-0.23335-0.35397-0.39826-0.49396s-0.40309-0.25684-0.66899-0.25783zm0.0086,0.99424c0.0422,0.0358,0.10671,0.13602,0.1841,0.26173,0.15477,0.25141,0.33068,0.6272,0.49397,1.0488,0.16329,0.42163,0.31905,0.88645,0.49598,1.2828,0.17693,0.39633,0.31456,0.73379,0.6753,0.98889,0.36072,0.25509,0.77019,0.29763,1.2152,0.34118,0.44506,0.0435,0.92882,0.0532,1.3948,0.0765,0.46599,0.0234,0.91824,0.0697,1.2135,0.13647,0.14763,0.0334,0.25558,0.0572,0.30381,0.0853-0.02227,0.0498-0.07958,0.15478-0.17956,0.26464-0.19997,0.21973-0.53749,0.48746-0.90261,0.7676-0.36511,0.28015-0.77804,0.57219-1.114,0.85704-0.33595,0.28485-0.63992,0.53221-0.77987,0.94209-0.13995,0.40986-0.05396,0.77681,0.04065,1.1987s0.25406,0.87813,0.37502,1.313c0.12096,0.43486,0.22688,0.84692,0.25228,1.1385,0.01156,0.13264-0.01699,0.23485-0.02778,0.29267-0.05993-0.008-0.1764-0.0324-0.30381-0.0853-0.27836-0.11561-0.64649-0.32808-1.0354-0.57657-0.38894-0.24849-0.8039-0.53053-1.1885-0.75081-0.38457-0.22028-0.70791-0.39837-1.1551-0.40015-0.44722-0.002-0.79205,0.17245-1.1786,0.38965-0.38659,0.21719-0.78438,0.49517-1.1756,0.74055-0.39123,0.24538-0.77636,0.4507-1.0559,0.56412-0.13977,0.0567-0.24616,0.0856-0.3023,0.0902-0.01026-0.0578-0.01522-0.16008-0.0025-0.29243,0.02793-0.29118,0.13818-0.70893,0.26291-1.1426,0.12473-0.43372,0.25404-0.89785,0.35232-1.3188,0.09828-0.42099,0.18672-0.78846,0.05027-1.1994-0.13644-0.41097-0.43218-0.64202-0.76571-0.92967-0.33353-0.28766-0.70551-0.58555-1.0683-0.86876-0.36275-0.2832-0.7178-0.56075-0.91597-0.78224-0.09908-0.11075-0.16221-0.21163-0.1841-0.26173,0.04834-0.0276,0.15448-0.058,0.3023-0.0902,0.29562-0.0643,0.74518-0.10714,1.2112-0.1267,0.46603-0.0196,0.94824-0.0298,1.3935-0.0697,0.44531-0.0399,0.84679-0.0499,1.2097-0.30216,0.36289-0.25221,0.5383-0.60511,0.71876-1.0001,0.18046-0.39497,0.33731-0.84115,0.50435-1.2616,0.16704-0.42041,0.31848-0.83556,0.47556-1.0859,0.07853-0.12515,0.16751-0.20007,0.21012-0.23565z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/prev.svg b/p/themes/Pafat/icons/prev.svg new file mode 100644 index 000000000..000ecb10d --- /dev/null +++ b/p/themes/Pafat/icons/prev.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-301.0002,-747)"> +<path style="block-progression:tb;color:#666;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m313.01,749-1,0c-0.0104-0.00012-0.0208-0.00046-0.0313,0-0.25495,0.0112-0.50987,0.12858-0.6875,0.3125l-6.2977,5.7188,6.2977,5.7188c0.18816,0.18819,0.45346,0.28125,0.71875,0.28125h1v-1c0-0.26529-0.0931-0.53058-0.28125-0.71875l-4.829-4.2812,4.829-4.2812c0.21074-0.19463,0.30316-0.46925,0.28125-0.75z" fill-rule="nonzero" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/read.svg b/p/themes/Pafat/icons/read.svg new file mode 100644 index 000000000..f3ce7532d --- /dev/null +++ b/p/themes/Pafat/icons/read.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16.001" width="16"> +<g transform="translate(-60.99995,-296.9989)"> +<path opacity="1" style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" fill="#666" d="m68.875,297a1.0001,1.0001,0,0,0,-0.5,0.25l-4.9062,4a1.0001,1.0001,0,0,0,-0.0625,0.0312s-0.32587,0.29728-0.65625,0.75c-0.22334,0.30605-0.3527,0.8316-0.5,1.3125a1.0001,1.0001,0,0,0,-0.03125,0.0312,1.0001,1.0001,0,0,0,-0.21875,0.5625c-0.00051,0.0118,0.00036,0.0195,0,0.0312a1.0001,1.0001,0,0,0,0,0.0312,1.0001,1.0001,0,0,0,0,0.15625v7.8438a1.0001,1.0001,0,0,0,1,1h12a1.0001,1.0001,0,0,0,1,-1v-7.8438a1.0001,1.0001,0,0,0,0,-0.15625,1.0001,1.0001,0,0,0,-0.21875,-0.65625,1.0001,1.0001,0,0,0,-0.03125,-0.0312c-0.32774-1.1879-1.125-2-1.125-2a1.0001,1.0001,0,0,0,-0.0312,-0.0312l-4.969-4.02a1.0001,1.0001,0,0,0,-0.65625,-0.25,1.0001,1.0001,0,0,0,-0.0937,0zm0.125,2.2812,4.3125,3.5312,0.0312,0.0312c0.021,0.0255,0.18032,0.24952,0.34375,0.5l-4.6874,3.5312-4.6875-3.5312c0.0259-0.0394,0.0349-0.0872,0.0625-0.125,0.1908-0.26146,0.31874-0.41421,0.34375-0.4375l0.03125-0.0312,4.25-3.4688zm-5,5.0938,4.6875,3.5312,0.3125,0.21875,0.3125-0.21875,4.6875-3.5312,0,6.625-10,0,0-6.625z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/share.svg b/p/themes/Pafat/icons/share.svg new file mode 100644 index 000000000..5010082cb --- /dev/null +++ b/p/themes/Pafat/icons/share.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g fill="#666" transform="translate(-581.0002,-196)"> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,148.15963,-64.49107)"/> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,158.12818,-59.49107)"/> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,158.12818,-69.49107)"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m593.62,198.16-10.062,4.875-1.8125,0.90625,1.8125,0.90625,10.031,5.0625,0.90625-1.8125-8.2188-4.1562,8.2188-4-0.875-1.7812z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/starred.svg b/p/themes/Pafat/icons/starred.svg new file mode 100644 index 000000000..b9ab2f9f7 --- /dev/null +++ b/p/themes/Pafat/icons/starred.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="enable-background:accumulate;color:#000000;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/tag.svg b/p/themes/Pafat/icons/tag.svg new file mode 100644 index 000000000..9908d4665 --- /dev/null +++ b/p/themes/Pafat/icons/tag.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-141.0002,-807)"> +<path d="m149,809,0,13,4-4,4,4c0.0525-6.8494-0.0285-10.584,0-13z" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/unread.svg b/p/themes/Pafat/icons/unread.svg new file mode 100644 index 000000000..77ce9c8b4 --- /dev/null +++ b/p/themes/Pafat/icons/unread.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-40.99995,-297)" fill="#666"> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m43.781,301a1.0001,1.0001,0,0,0,-0.40625,1.7812l5,4,0.625,0.5,0.625-0.5,5-4a1.0005,1.0005,0,1,0,-1.25,-1.5625l-4.375,3.5-4.375-3.5a1.0001,1.0001,0,0,0,-0.844,-0.22z"/> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="M42.906,300a1.0001,1.0001,0,0,0,-0.906,1v9a1.0001,1.0001,0,0,0,1,1h12a1.0001,1.0001,0,0,0,1,-1v-9a1.0001,1.0001,0,0,0,-1,-1h-12a1.0001,1.0001,0,0,0,-0.09375,0zm1.094,2h10v7h-10v-7z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/icons/up.svg b/p/themes/Pafat/icons/up.svg new file mode 100644 index 000000000..bd9be09e9 --- /dev/null +++ b/p/themes/Pafat/icons/up.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-201.0002,-747)"> +<path style="block-progression:tb;color:#666;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m215.03,759,0-1c-0.00091-0.0111,0.00059-0.021-0.00009-0.0312-0.0112-0.25496-0.12835-0.50994-0.31251-0.6875l-5.7188-6.2977-5.7188,6.2977c-0.18821,0.1881-0.28121,0.45346-0.28122,0.71875v1h1c0.26531-0.00007,0.53059-0.0931,0.71873-0.28131l4.2812-4.829,4.2813,4.829c0.19464,0.21073,0.46925,0.30315,0.74998,0.2813z" fill-rule="nonzero" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Pafat/loader.gif b/p/themes/Pafat/loader.gif Binary files differnew file mode 100644 index 000000000..a3d327a18 --- /dev/null +++ b/p/themes/Pafat/loader.gif diff --git a/p/themes/Pafat/metadata.json b/p/themes/Pafat/metadata.json new file mode 100644 index 000000000..ba82e0a09 --- /dev/null +++ b/p/themes/Pafat/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Pafat", + "author": "Plopoyop", + "description": "Un thème pour FreshRSS", + "version": 0.2, + "files": ["_template.css", "pafat.css"] +} diff --git a/p/themes/Pafat/pafat.css b/p/themes/Pafat/pafat.css new file mode 100644 index 000000000..a35ac861d --- /dev/null +++ b/p/themes/Pafat/pafat.css @@ -0,0 +1,1084 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + background: #fafafa; + color : #666; +} + +/*=== Links */ +a { + color: #2980b9; + outline: none; +} + +/*=== Forms */ +legend { + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #ddd; + font-size: 1.4em; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 1px; + background: #fdfdfd; + border: 1px solid #bbb; + border-radius: 3px; + color: #666; + line-height: 21px; + vertical-align: middle; +} + +select{ + height:29px; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { + outline-color: #aaa; +} + +input:invalid, select:invalid { + border-color: #f00; + box-shadow: 0 0 2px 2px #fdd inset; + outline-color: #fdd; +} +input:disabled, select:disabled { + background: #eee; +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; + border: 1px solid #ddd; +} +th { + background: #f6f6f6; +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group.form-actions { + padding: 5px 0; + background: #f4f4f4; + border-top: 1px solid #ddd; +} +.form-group.form-actions .btn { + margin: 0 10px; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; + margin : 10px 0 10px 220px; + +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +.stick { + vertical-align: middle; + font-size: 0; + min-width: 215px; +} +.stick input, +.stick .btn { + border-radius: 0; +} +.stick .btn:first-child, +.stick input:first-child { + border-radius: 3px 0 0 3px; +} +.stick .btn-important:first-child { + width:176px; +} +.stick .btn:last-child, +.stick input:last-child { + border-radius: 0 3px 3px 0; +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { + border-left: none; + +} + +.stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; +} + +.btn { + display: inline-block; + min-height: 29px; + min-width: 15px; + margin: 0; + padding: 1px 5px; + background: #fff; + border-radius: 3px; + border: 1px solid #aaa; + color: #666; + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} + +a.btn { + min-height: 25px; + line-height: 25px; +} + + +a.btn { + min-height: 25px; + line-height: 25px; +} + +.read_all.btn { + height:29px; +} + +.btn:hover { + background: #f0f0f0; + text-decoration: none; +} + + +.category.stick .btn { + background:#5bc0de; + color : #FFF; + border-color :#5bc0de; +} + +.category.stick .btn:first-child:hover, .category.stick .btn:last-child:hover, .category.stick .btn.active:first-child, .category.stick.active .btn:last-child { + background:#39b3d7; + border-color : #39b3d7; +} + + +.btn.active, +.btn:active, +.dropdown-target:target ~ .btn.dropdown-toggle { + background: #eee; +} + +.category.all > .btn { + background: #428bca; + color : #FFF; + border-color : #428bca; +} + +.category.all > .btn:hover { + background: #3276b1; + border-color : #3276b1; +} + +.category.favorites > .btn { + background:#f0ad4e; + border-color: #f0ad4e; + color : #fff; +} + +.category.favorites > .btn:hover { + background: #ed9c28; + border-color : #ed9c28; + color : white; +} + +.btn-important { + background: #5cb85c; + color: #fff; + border-color: #5cb85c; + font-weight: normal; +} +.btn-important:hover, .btn-important:active { + background:#47a447; + border-color : #47a447; + box-shadow: none; +} + +.btn-attention { + background: #d9534f; + color: #fff; + border: 1px solid #d9534f; + outline-color : #aaa; +} +.btn-attention:hover { + background: #d2322d; + border-color : #d2322d; +} +.btn-attention:active { + background: #d2322d; + box-shadow: none; +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover { + background: #fafafa; +} +.nav-list .item:hover a { + color: #003388; +} +.nav-list .item.active { + background: #3498DB; + color: #fff; +} +.nav-list .item.active a { + color: #fff; +} +.nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; +} +.nav-list .item > a { + padding: 0 10px; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { + color: #f39c12; +} +.nav-list .item.active.empty a { + color: #fff; + background: #f39c12; +} +.nav-list .item.error a { + color: #BD362F; +} +.nav-list .item.active.error a { + color: #fff; + background: #BD362F; +} + +.nav-list .nav-header { + padding: 0 10px; + color: #888; + background: #f4f4f4; + border-bottom: 1px solid #ddd; + font-weight: bold; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + background: #fff; + background: linear-gradient(to bottom, #fff, #f0f0f0); + background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); + border-bottom: 1px solid #ddd; + text-align: right; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + border: 1px solid #aaa; + border-radius: 5px; + font-size: 0.8rem; + text-align: left; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #fff; + border-top: 1px solid #aaa; + border-left: 1px solid #aaa; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} +.dropdown-header { + padding: 0 5px 5px; + color: #888; + font-weight: bold; + text-align: left; +} +.dropdown-menu > .item { +} + +.dropdown-menu > .item > a { + padding: 0 22px; + line-height: 2.5em; + color: #666; + font-size: 0.8rem; +} + +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; + font-size: 0.8rem; +} + +.dropdown-menu > .item:hover { + background: #eee; + color: #666; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + color: #666; + text-decoration: none; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; +} + +.separator { + margin: 5px 0; + border-bottom: 1px solid #ddd; +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + font-size: 0.9em; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + color: inherit; + text-decoration: underline; +} +.alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; +} +.alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} + +/*=== Pagination */ +.pagination { + background: #fff; + text-align: center; + color: #41444f; + font-size: 0.8em; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; +} +.pagination .item a { + display: block; + color: #41444f; + font-style: italic; + line-height: 3em; + text-decoration: none; +} +.pagination .item a:hover { + background: #ddd; +} +.pagination:first-child .item { + border-bottom: 1px solid #aaa; +} +.pagination:last-child .item { + border-top: 1px solid #aaa; +} + +.pagination .loading, +.pagination a:hover.loading { + background: url("loader.gif") center center no-repeat #fff; + font-size: 0; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 85px; + background: #41444f; +} +.header > .item { + padding: 10px; + border-bottom: 1px solid #aaa; + vertical-align: middle; + text-align: center; +} +.header > .item.title{ + width: 230px; +} +.header > .item.title h1 { + margin: 0.5em 0; +} + +.header > .item.title h1 a, a.signin { + text-decoration: none; + color : #C5C6CA; +} + +.header > .item.search input { + width: 230px; + height : 29px; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +.header > .item.search button { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + height : 29px; +} + +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + height: calc(100% - 85px); +} +.aside { + border-right: 1px solid #aaa; + background: #fff; +} +.aside.aside_flux { + padding: 10px 0 50px; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.category { + width: 215px; + margin: 10px auto; + text-align: left; +} +.category .btn:first-child { + position: relative; + width: 203px; +} +.category.stick .btn:first-child { + width: 176px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 2px; right: 3px; + padding: 0px 3px; + border: 1px solid ; + border-radius: 3px; + font-size:10pt; + line-height : 20px; +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { + background: #5cb85c; +} +.categories .feeds .item.active .feed { + color: #fff; +} +.categories .feeds .item.empty .feed { + color: #e67e22; +} +.categories .feeds .item.empty.active { + background: #e67e22; +} +.categories .feeds .item.empty.active .feed { + color: #fff; +} +.categories .feeds .item.error .feed { + color: #BD362F; +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #fff; + border-radius: 3px; + vertical-align: middle; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 200px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} + +/*=== New article notification */ +#new-article { + background: #428bca; + text-align: center; + font-size: 0.9em; +} +#new-article:hover { + background: #3276b1; +} +#new-article > a { + line-height: 3em; + color: #fff; + font-weight: bold; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-weight: bold; + line-height: 3em; + background: #fff; + border-top: 1px solid #aaa; + border-bottom: 1px solid #aaa; + color : #666; +} +#new-article + .day { + border-top: none; +} +.day .name { + padding: 0 10px 0 0; + color : #666; + font-size: 1.8em; + opacity: 0.3; + font-style: italic; + text-align: right; +} + +/*=== Index menu */ +.nav_menu { + background: #fafafa; + border-bottom: 1px solid #aaa; + text-align: center; + padding: 5px 0; +} + +/*=== Feed articles */ +.flux { + border-left: 3px solid #5cb85c; + background: #fafafa; +} +.flux:hover { + background: #fff; +} +.flux.current { + border-left: 3px solid #39b3d7; +} +.flux.not_read { + border-left: 3px solid #d9534f; +} +.flux .item.title a, .flux.not_read:not(.current):hover .item.title { + color : #333; +} +.flux.favorite { + border-left: 2px solid #428bca; + background: #FFF6DA; +} +.flux.favorite:not(.current):hover .item.title { + background: #FFF6DA; +} +.flux.current { + background: #fff; +} + + +.flux_header { + border-top: 1px solid #ddd; + font-size: 0.8rem; + cursor: pointer; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux .website .favicon { + padding: 5px; +} +.flux .date { + color: #666; + font-size: 0.7rem; +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { + color: #333; +} + +.content hr { + margin: 30px 10px; + height: 1px; + background: #ddd; + border: 0; + box-shadow: 0 2px 5px #ccc; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #222; + color: #fff; + font-size: 0.9rem; + border-radius: 3px; +} +.content code { + padding: 2px 5px; + color: #dd1144; + background: #fafafa; + border: 1px solid #eee; + border-radius: 3px; +} +.content pre code { + background: transparent; + color: #fff; + border: none; +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + background: #fafafa; + color: #41444f; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + border: 1px solid #eeb; + border-radius: 3px; + box-shadow: 0 0 5px #ddd; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + z-index: 10; + vertical-align: middle; +} +.notification.good { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.notification.bad { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} +.notification a.close { + padding: 0 15px; + line-height: 3em; +} +.notification.good a.close:hover { + background: #eeb; +} +.notification.bad a.close:hover { + background: #ecc; +} + +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; + color: #666; + background: #fafafa; +} +#bigMarkAsRead:hover { + color: #000; + background: #f0f0f0; +} + +#bigMarkAsRead:hover .bigTick { +/* text-shadow: 0 0 10px #666;*/ +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + margin: 0; + background: #fff; + border-top: 1px solid #ddd; + text-align: center; + line-height: 3em; + table-layout: fixed; +} + +#nav_entries .item:hover { + background:#eee ; +} +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; + border: none; + background: #f0f0f0; + color: #41444f; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global .box-category { + background: #fff; + border:none; + text-align: left; +} + +#stream.global .category { + margin: 0; +} + +#stream.global .category:first-child { + margin: 0; +} + + +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + background: #f6f6f6; + border-bottom: 1px solid #aaa; + border-radius: 5px 5px 0 0; + line-height: 2em; + font-size: 1.2rem; +} + +#stream.global .btn:not([data-unread="0"]) { + background: #5bc0de; + border-color : #5bc0de; + color: #fff; + font-weight: bold; + text-shadow: none; + +} + + +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #fff; + font-weight: bold; + box-shadow: none; + text-shadow: none; +} + +#stream.global .box-category .feeds { + max-height: 250px; + width: 302px; + border : solid #aaa 1px; + border-top : none; +} + +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; +} + +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input, +.aside.aside_feed .nav-form select { + width: 140px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { + border: none; +} +.stat > table td, +.stat > table th { + border-bottom: 1px solid #ddd; + text-align: center; +} +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 250px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + border: 1px solid #aaa; + border-radius: 5px; + overflow: hidden; +} +.log { + padding: 5px 10px; + background: #fafafa; + color: #41444f; + font-size: 0.8rem; +} +.log+.log { + border-top: 1px solid #aaa; +} +.log .date { + display: block; + font-weight: bold; +} +.log.error { + background: #fdd; + color: #844; +} +.log.warning { + background: #ffe; + color: #c95; +} +.log.notice { + background: #f4f4f4; + color: #aaa; +} +.log.debug { + background: #41444f; + color: #eee; +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .aside { + box-shadow: 3px 0 3px #aaa; + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + background: #f6f6f6; + border-left: 1px solid #ddd; + border-bottom: 1px solid #ddd; + border-radius: 0 0 0 5px; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + font-size: 1.1rem; + text-shadow: none; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification a.close { + display: block; + left: 0; + background: transparent; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } +}
\ No newline at end of file diff --git a/p/themes/Screwdriver/README.md b/p/themes/Screwdriver/README.md new file mode 100644 index 000000000..4b10eb90f --- /dev/null +++ b/p/themes/Screwdriver/README.md @@ -0,0 +1,36 @@ +Screwdriver +======= + +**C'est un cocktail! C'est chaud mais "fresh" à la fois. C'est... c'est... un thème pour l'agrégateur de flux RSS<a href="https://github.com/marienfressinaud/FreshRSS/" target="blank">FreshRSS</a>!!** +En toute modestie, ce thème tue du chaton. + + + + +Installation +----------------- +1. Placez le dossier du thème dans ledossier /FreshRSS/p/themes/Screwdriver de votre FreshRSS; +2. Allez dans les paramètres d'Affichage et changez de thème; +3. Profitez de votre Screwdriver! +4. Remontez les problèmes sur Github (facultatif mais fortement apprécié) + + + +Screwdriver est distribué sous license BeerWare: +----------------- + +« LICENCE BEERWARE » (Révision 42): + +mister.air@gmail.com a créé ce fichier. Tant que vous conservez cet avertissement, + +vous pouvez faire ce que vous voulez de ce truc. Si on se rencontre un jour et + +que vous pensez que ce truc vaut le coup, vous pouvez me payer une bière en retour. + +*Mister aiR* + + + + + + diff --git a/p/themes/Screwdriver/icons/bookmark.svg b/p/themes/Screwdriver/icons/bookmark.svg new file mode 100644 index 000000000..edf5a02db --- /dev/null +++ b/p/themes/Screwdriver/icons/bookmark.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="color:#000000;enable-background:accumulate;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#d18104"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Screwdriver/icons/read.svg b/p/themes/Screwdriver/icons/read.svg new file mode 100644 index 000000000..5e454e36b --- /dev/null +++ b/p/themes/Screwdriver/icons/read.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16.001" width="16"> +<path fill="#CCC" d="m8.0005,3.2833c-3.4339,0-6.4103,1.9996-7.8729,4.9206,1.4626,2.9209,4.439,4.9206,7.8729,4.9206,3.4338,0,6.4102-1.9996,7.8729-4.9206-1.462-2.9204-4.439-4.9201-7.8725-4.9201zm3.8819,2.6095c0.9251,0.59007,1.709,1.3804,2.2978,2.3111-0.58881,0.93063-1.3727,1.721-2.2978,2.3111-1.1624,0.7414-2.5047,1.1333-3.8818,1.1333s-2.7195-0.39189-3.8819-1.1333c-0.92507-0.59004-1.709-1.3804-2.2978-2.3111,0.58881-0.93066,1.3727-1.721,2.2978-2.3111,0.06025-0.038442,0.12111-0.075682,0.18234-0.11225-0.15312,0.42021-0.23692,0.87374-0.23692,1.3469,0,2.174,1.7624,3.9364,3.9364,3.9364s3.9365-1.7624,3.9365-3.9364c0-0.4732-0.0838-0.92673-0.23692-1.347,0.0612,0.036566,0.12206,0.073839,0.18234,0.11228zm-3.8815,0.7427c0,0.81528-0.66089,1.4762-1.4762,1.4762-0.81528,0-1.4762-0.66089-1.4762-1.4762,0-0.81528,0.66089-1.4762,1.4762-1.4762,0.81527,0,1.4762,0.66089,1.4762,1.4762z"/> +</svg>
\ No newline at end of file diff --git a/p/themes/Screwdriver/icons/starred.svg b/p/themes/Screwdriver/icons/starred.svg new file mode 100644 index 000000000..cd06525fd --- /dev/null +++ b/p/themes/Screwdriver/icons/starred.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="color:#000000;enable-background:accumulate;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#eaa904"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/Screwdriver/icons/unread.svg b/p/themes/Screwdriver/icons/unread.svg new file mode 100644 index 000000000..a27acf2b6 --- /dev/null +++ b/p/themes/Screwdriver/icons/unread.svg @@ -0,0 +1,3 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<path fill="#666" d="m7.9824,3.529c-3.4339,0-6.4103,1.9996-7.8729,4.9206,1.4626,2.9214,4.439,4.9204,7.8729,4.9204,3.4338,0,6.4102-1.9996,7.8729-4.9206-1.462-2.9207-4.439-4.9204-7.8726-4.9204zm3.8819,2.6095c0.9251,0.59007,1.709,1.3804,2.2978,2.3111-0.58881,0.93063-1.3727,1.721-2.2978,2.3111-1.1624,0.7414-2.5047,1.1333-3.8818,1.1333s-2.7195-0.39189-3.8819-1.1333c-0.9248-0.59-1.7087-1.3807-2.2975-2.3114,0.5888-0.9307,1.3727-1.721,2.2978-2.3111,0.0602-0.0384,0.1211-0.0757,0.1823-0.1122-0.15312,0.42021-0.23692,0.87374-0.23692,1.3469,0,2.174,1.7624,3.9364,3.9365,3.9364,2.174,0,3.9365-1.7624,3.9365-3.9364,0-0.4732-0.0838-0.92673-0.23692-1.347,0.0612,0.036566,0.12206,0.073839,0.18234,0.11228zm-3.8816,0.7427c0,0.81528-0.66089,1.4762-1.4762,1.4762-0.81528,0-1.4762-0.66089-1.4762-1.4762,0-0.81528,0.66089-1.4762,1.4762-1.4762,0.81527,0,1.4762,0.66089,1.4762,1.4762z"/> +</svg>
\ No newline at end of file diff --git a/p/themes/Screwdriver/loader.gif b/p/themes/Screwdriver/loader.gif Binary files differnew file mode 100644 index 000000000..5ff26f0e3 --- /dev/null +++ b/p/themes/Screwdriver/loader.gif diff --git a/p/themes/Screwdriver/metadata.json b/p/themes/Screwdriver/metadata.json new file mode 100644 index 000000000..46095e507 --- /dev/null +++ b/p/themes/Screwdriver/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Screwdriver", + "author": "Mister aiR", + "description": "C'est un cocktail ! C'est chaud mais « fresh » à la fois. Ce thème tue du chaton.", + "version": 1.1, + "files": ["_template.css","screwdriver.css"] +} diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css new file mode 100644 index 000000000..37fa18e10 --- /dev/null +++ b/p/themes/Screwdriver/screwdriver.css @@ -0,0 +1,1183 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + background: #fafafa; + font-size: 92%; +} + +/*=== Links */ +a, button.as-link { + color: #D18114; + outline: none; +} + +/*=== Forms */ +.form-group{ + width: 100%; + float: left; + height: auto; + display: inline-block; +} +legend { + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #ddd; + font-size: 1.4em; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 5px; + background: #fff; + border: 1px solid #ccc; + border-radius: 3px; + color: #222; + line-height: 25px; + vertical-align: middle; + box-shadow: 0 2px 2px #eee inset, 0 1px #fff; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { + color: #0F0F0F; + box-shadow: 0 0 3px #E7AB34; + border: solid 1px #E7AB34; +} +input:invalid, select:invalid { + border-color: #f00; + box-shadow: 0 0 2px 2px #fdd inset; +} +input:disabled, select:disabled { + background: #eee; +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; + border: 1px solid #ddd; +} +th { + background: #f6f6f6; +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group.form-actions { + padding: 5px 0; + background: #f4f4f4; + border-top: 1px solid #ddd; +} +.form-group.form-actions .btn { + margin: 0 10px; + border-radius: 4px; + box-shadow:0 1px rgba(255,255,255,0.08) inset; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +form#add_rss .stick input, .dropdown-menu select{ + background:#393939; + box-shadow: 0 2px 2px #171717 inset,0 1px rgba(255,255,255,0.08); + border-left:solid 1px #171717; + border-top:solid 1px #171717; + border-bottom:solid 1px #171717; + border-right:none; + color:#fff; +} +form#add_rss .stick .btn{ + background:linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + box-shadow:0 1px rgba(255,255,255,0.08), 0px 1px rgba(255, 255, 255, 0.08) inset; + border-right:solid 1px #171717; + border-top:solid 1px #171717; + border-bottom:solid 1px #171717; + border-left:none; +} +form#add_rss .stick .btn.dropdown-toggle{ + border-right:solid 1px #171717; + border-top:solid 1px #171717; + border-bottom:solid 1px #171717; + border-left:solid 1px #171717; +} +.stick { + vertical-align: middle; + font-size: 0; +} +.stick input, +.stick .btn { + border-radius: 0; +} +.stick .btn:first-child,.stick input:first-child { + border-radius: 6px 0 0 6px; +} +.stick .btn-important:first-child { +} +.stick .btn:last-child, .stick input:last-child { + border-radius: 0 6px 6px 0; +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { + border-left: none; +} +.stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; +} + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + margin: 0; + padding: 5px 10px; + color:#222; + border: solid 1px #ccc; + border-radius: 4px; + background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + text-shadow: 0px -1px rgba(255,255,255,0.08); + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} +a.btn { + min-height: 25px; + line-height: 25px; +} +.btn:hover { + text-shadow: 0 0 2px #fff; + text-decoration:none; +} +.btn.active,.btn:active,.dropdown-target:target ~ .btn.dropdown-toggle { + background: linear-gradient(180deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(top, #EDE7DE 0%, #FFF 100%); +} +#loginButton.btn{ + border:none; + box-shadow: 0px 1px rgba(255, 255, 255, 0.08) inset,0 -1px #171717,0px 1px rgba(255, 255, 255, 0.08); +} +.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle{ + box-shadow: 0px 2px #E2972A; + border-radius: 0; + background:transparent; +} +.nav_menu .btn { + border: 0; + background:transparent; +} + +.read_all { + color:#222; +} +.btn.dropdown-toggle[href="#dropdown-configure"]{ + background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + border-radius: 4px; + border: solid 1px #ccc; + box-shadow: 0 1px #fff; +} +.btn.dropdown-toggle:active { + background:transparent; +} +.btn-important { + background: linear-gradient(180deg, #E4992C 0%, #D18114 100%) #E4992C; + background: -webkit-linear-gradient(top, #E4992C 0%, #D18114 100%); + color: #FFF; + box-shadow: 0 1px rgba(255,255,255,0.08) inset; + border-radius: 4px; + text-shadow: 0px -1px rgba(255,255,255,0.08); + font-weight: normal; +} +.btn-important:hover { +} +.btn-important:active { + background: linear-gradient(0deg, #E4992C 0%, #D18114 100%) #E4992C; + background: -webkit-linear-gradient(bottom, #E4992C 0%, #D18114 100%); +} + +.btn-attention { + background: #E95B57; + background: linear-gradient(to bottom, #E95B57, #BD362F); + background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); + color: #fff; + border: 1px solid #C44742; + text-shadow: 0px -1px 0px #666; +} +.btn-attention:hover { + background: linear-gradient(to bottom, #D14641, #BD362F); + background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); +} +.btn-attention:active { + background: #BD362F; + box-shadow: none; +} +.btn[type="reset"]{ + color: #fff; + background:linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + box-shadow:0 -1px rgba(255,255,255,0.08) inset; +} +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover { + text-shadow: 0 0 2px rgba(255,255,255,0.28); + color:#fff; +} + +.nav-list .item.active { + background: linear-gradient(180deg, #222 0%, #171717 100%) repeat scroll 0% 0% #171717; + background: -webkit-linear-gradient(180deg, #222 0%, #171717 100%); + border-width: medium medium 1px; + border-style: none none solid; + border-color: -moz-use-text-color -moz-use-text-color #171717; + box-shadow: -1px 2px 2px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; + margin: 0; +} +.nav-list .item.active a { + color: #D18114; +} +.nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; +} +.nav-list .item > a { + padding: 0 10px; + color:#ccc; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { + color: #f39c12; +} +.nav-list .item.active.empty a { + color: #fff; + background: linear-gradient(180deg, #E4992C 0%, #D18114 100%) #E4992C; + background: -webkit-linear-gradient(180deg, #E4992C 0%, #D18114 100%); +} +.nav-list .item.error a { + color: #BD362F; +} +.nav-list .item.active.error a { + color: #fff; + background: #BD362F; +} + +.nav-list .nav-header { + padding: 0 10px; + color: #222; + background: transparent; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + text-align: right; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + border: 1px solid #171717; + border-radius: 4px; + box-shadow: 0 0 3px #000; + font-size: 0.8rem; + text-align: left; + background: #222; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #222; + border-top: 1px solid #171717; + border-left: 1px solid #171717; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} +.dropdown-header { + display:none; +} +.dropdown-menu > .item { +} +.dropdown-menu > .item > a { + padding: 0 25px; + line-height: 2.5em; + color: #ccc; +} +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; + color: #ccc; +} +.dropdown-menu > .item:hover { + background: #171717; + color: #fff; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + color: #fff; + text-decoration: none; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; + border-radius: 3px; +} + +.separator { + margin: 5px 0; + border-bottom: 1px solid #171717; + box-shadow: 0 1px rgba(255,255,255,0.08); +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + background: #f4f4f4; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; + text-shadow: 0 0 1px #eee; + font-size: 0.9em; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + color: inherit; + text-decoration: underline; +} +.alert-warn { + background: #ffe; + border: 1px solid #eeb; + color: #c95; +} +.alert-success { + background: #dfd; + border: 1px solid #cec; + color: #484; +} +.alert-error { + background: #fdd; + border: 1px solid #ecc; + color: #844; +} + +/*=== Pagination */ +.pagination { + background: #fafafa; + text-align: center; + color: #333; + font-size: 0.8em; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; +} +.pagination .item a { + display: block; + color: #333; + font-style: italic; + line-height: 3em; + text-decoration: none; +} +.pagination .item a:hover { + background: #ddd; +} +.pagination:first-child .item { + border-bottom: 1px solid #aaa; +} +.pagination:last-child .item { + border-top: 1px solid #ddd; +} + +.pagination .loading, +.pagination a:hover.loading { + background: url("loader.gif") center center no-repeat #fff; + font-size: 0; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 55px; + background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); +} +.header > .item { + padding: 0; + vertical-align: middle; + text-align: center; +} +.header > .item.title .logo { + display: none; +} +.header > .item.title{ + width: 250px; +} +.header > .item.title h1 { + margin: 0.5em 0; +} +.header > .item.title h1 a { + text-decoration: none; + font-size: 38px; + color:#ccc; + text-shadow: 0 1px #fff, 0 -1px rgba(162, 162, 162, 1); +} +.header > .item.search input { + width: 230px; +} +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + background:#EDE7DE; + height: calc(100% - 85px); +} +.aside { + border-radius: 0px 12px 0px 0px; + box-shadow: 0px -1px #FFF, 0 2px 2px #171717 inset; + border-top: 1px solid #CCC; + background: #222; + width: 235px; +} +.aside.aside_flux { + padding: 10px 0 50px; + background: #222; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.categories .btn-important { + border: none; +} +.category { + width: 235px; + margin: 10px auto 0; + text-align: left; +} +#aside_flux ul.feeds{ + box-shadow: 0 4px 4px #171717 inset, 0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0.08); +} +ul.feeds{ + background:#171717; + padding:8px 0; + box-shadow: 0 4px 4px #EDE7DE inset; +} +ul.feeds.active{ + box-shadow: 0 0 0 #171717 inset, 0 -2px 2px #111 inset,0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0); +} +.category.stick.active{ + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; +} +.category .btn { + color: #fff; + border: none; + background: transparent; +} +.category .btn:first-child { + position: relative; + width: 213px; + background: transparent; +} +.category.stick .btn:first-child { + width: 176px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: transparent; + color: #fff; + text-shadow: 0 1px rgba(255,255,255,0.08); +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + border-radius: 4px; + margin: 0px 8px; + box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset, 0 2px 2px #111; +} +.categories .feeds .item.active .feed { + color: #fff; +} +.categories .feeds .item.empty .feed { + color: #e67e22; +} +.categories .feeds .item.empty.active { + background: #e67e22; +} +.categories .feeds .item.empty.active .feed { + color: #fff; +} +.categories .feeds .item.error .feed { + color: #BD362F; +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; + color:#ccc; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + background-color: transparent; + border-radius: 3px; + vertical-align: middle; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; + color: #FFF; + background: #222; + padding: 14px 0px; + box-shadow: 0px -1px #FFF, 0px 1px #FFF, 0px 2px 2px #171717 inset, 0px -2px 2px #171717 inset; + text-shadow: 0 -1px #171717, 0 1px rgba(255,255,255,0.08); +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 180px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} +.prompt input#username,.prompt input#passwordPlain{ + border:none; + box-shadow: 0px 1px rgba(255, 255, 255, 0.08) inset,0 -1px #171717,0px 1px rgba(255, 255, 255, 0.08); + background:#EDE7DE; +} +.prompt input#username:focus,.prompt input#passwordPlain:focus{ + border: solid 1px #E7AB34; + box-shadow: 0 0 3px #E7AB34; +} + +/*=== New article notification */ +#new-article { + background: #0084CC; + text-align: center; + font-size: 0.9em; +} +#new-article:hover { + background: #0066CC; +} +#new-article > a { + line-height: 3em; + color: #fff; + font-weight: bold; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-style:italic; + line-height: 3em; + background: #fff; + text-align: center; +} +#new-article + .day { + border-top: none; +} +.day .name { + display: none; +} + +/*=== Index menu */ +.nav_menu { + background: #EDE7DE; + border-bottom: 1px solid #ccc; + box-shadow:0 -1px #fff inset; + text-align: center; + padding: 5px 0; +} + +/*=== Feed articles */ +.flux_content { + background: #FFF; + border-radius: 10px; +} +.flux { + background: #EDE7DE; +} +.flux:hover { + background: #F9F7F4; +} +.flux:not(.current):hover .item.title { + background: #F9F7F4; +} +.flux.current .flux .item.title a { + text-shadow:0 0 2px #ccc; +} +.flux.not_read:not(.current):hover .item.title { + opacity:0.85; +} +.flux.favorite { + background: #FFF6DA; +} +.flux.favorite:not(.current):hover{ + background: #F9F7F4; +} +.flux.favorite:not(.current):hover .item.title { + background: #F9F7F4; +} +.flux.current { + background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%); + box-shadow: 0 -1px #fff inset, 0 2px #ccc; + border-radius: 10px; + margin: 3px 6px; +} + +.flux .item.title { +opacity: 0.35; +} +.flux.favorite .item.title { +opacity: 1; +} +.flux.not_read .item.title { +opacity: 1; +} +.flux.current .item.title a { + color: #0f0f0f; +} +.flux .item.title a { + color: #333; +} + +.flux_header { + border-top: 1px solid #ddd; + font-size: 0.8rem; + cursor: pointer; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux .website .favicon { + padding: 5px; +} +.flux .date { + color: #666; + font-size: 0.7rem; +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { + color: #000; +} + +.content hr { + margin: 30px 10px; + height: 1px; + background: #ddd; + border: 0; + box-shadow: 0 2px 5px #ccc; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + background: #222; + color: #fff; + font-size: 0.9rem; + border-radius: 3px; +} +.content code { + padding: 2px 5px; + color: #dd1144; + background: #fafafa; + border: 1px solid #eee; + border-radius: 3px; +} +.content pre code { + background: transparent; + color: #fff; + border: none; +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + background: #fafafa; + color: #333; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + background:#222; + border: none; + border-radius: 0 0 12px 12px; + box-shadow: 0px 0px 4px rgba(0,0,0,0.45), 0 -1px rgba(255,255,255,0.08) inset, 0 2px 2px #171717 inset; + color:#fff; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + position:absolute; + top:0; + z-index: 10; + vertical-align: middle; +} +.notification.good { + color: #c95; +} +.notification.bad { + background: #fdd; + color: #844; +} +.notification a.close { + padding: 0 15px; + line-height: 3em; +} +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 #aaa; + color: #666; + background: #EDE7DE; +} +#bigMarkAsRead:hover { + color: #000; + background: #EDE7DE; + background: radial-gradient(circle at 50% -25% , #ccc 0%, #EDE7DE 50%); +} +#bigMarkAsRead:hover .bigTick { + text-shadow: 0 0 10px #666; +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + background: linear-gradient(180deg, #222 0%, #171717 100%) #222; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + border-top: 1px solid #171717; + text-align: center; + line-height: 3em; + table-layout: fixed; + box-shadow: 0 1px rgba(255,255,255,0.08) inset, 0 -2px 2px #171717; + width:235px; +} + +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; + border: none; + background: #f0f0f0; + color: #333; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global{ + background:#222; + padding: 24px 0; + box-shadow: 0 1px #fff, 0 -2px 2px #171717 inset, 0 2px 2px #171717 inset; +} +#stream.global .box-category { + background: #fff; + border-radius: 4px 4px 0 0; + text-align: left; + box-shadow: 0 0 4px #171717; + overflow:hidden; +} +#stream.global .category { + margin: 0; +} +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); + border: none; + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + border-radius: none; + line-height: 2em; + font-size: 1.2rem; + color:#888; + text-shadow:0 1px #ccc; +} +#stream.global .btn:not([data-unread="0"]) { + color: #222; + font-weight: bold; +} +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; + right: 5px; + border: 0; + background: none; + color: #222; + font-weight: bold; + box-shadow: none; + text-shadow: none; +} +#stream.global .box-category .feeds { + max-height: 250px; + color:#222; + background:#EDE7DE; +} +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; + overflow:hidden; +} +#stream.global .box-category .feed { + color:#222; +} +/*=== PANEL */ +/*===========*/ +#panel { + box-shadow: 0px 0px 4px #000; + border-radius: 8px; +} +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input,.aside.aside_feed .nav-form select { + width: 130px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { + border: none; +} +.stat > table td, +.stat > table th { + border-bottom: 1px solid #ccc; + background: rgba(255,255,255,0.38); + box-shadow: 0 1px #fff; +} + +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 250px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + border: 1px solid #aaa; + border-radius: 5px; + overflow: hidden; +} +.log { + padding: 5px 10px; + background: #fafafa; + color: #333; + font-size: 0.8rem; +} +.log+.log { + border-top: 1px solid #aaa; +} +.log .date { + display: block; + font-weight: bold; +} +.log.error { + background: #fdd; + color: #844; +} +.log.warning { + background: #ffe; + color: #c95; +} +.log.notice { + background: #f4f4f4; + color: #aaa; +} +.log.debug { + background: #333; + color: #eee; +} + +/*=== MOBILE */ +/*===========*/ +@media screen and (max-width: 840px) { + .header { + display: table; + } + .nav-login { + display: none; + } + + .aside { + width: 0; + border-top: none; + box-shadow: 3px 0 3px #000; + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside:target { + width: 235px; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + background: #171717; + box-shadow: 0 1px rgba(255,255,255,0.08); + border-radius: 0 8px 0 8px; + } + .aside .btn-important { + display: inline-block; + margin: 20px 0 0; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + display: none; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification a.close { + display: block; + left: 0; + background: transparent; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } + .nav_menu .search { + display: none; + } + + #nav_entries { + width: 100%; + } +} + +@media (max-width: 700px) { + .header{ + display: none; + } + .nav-login { + display: inline-block; + width: 100%; + } + .nav_menu .search { + display: inline-block; + } + .aside .btn-important { + display: none; + } +} diff --git a/p/themes/base-theme/README.md b/p/themes/base-theme/README.md new file mode 100644 index 000000000..6f186e15c --- /dev/null +++ b/p/themes/base-theme/README.md @@ -0,0 +1,12 @@ +FreshRSS-base-theme +=================== + +A base theme for [FreshRSS](http://freshrss.org) + +1. Custom ```base.css``` file with colors, backgrounds and borders +2. Change information in ```metadata.json``` file (at least, give a name!) +3. Choose your new theme in FreshRSS configuration +4. Enjoy your wonderful theme! + +Don't hesitate to share your theme with us [on Github](https://github.com/marienfressinaud/FreshRSS/issues) :) + diff --git a/p/themes/base-theme/base.css b/p/themes/base-theme/base.css new file mode 100644 index 000000000..ccfab10df --- /dev/null +++ b/p/themes/base-theme/base.css @@ -0,0 +1,777 @@ +@charset "UTF-8"; + +/*=== FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + +/*=== GENERAL */ +/*============*/ +html, body { + height: 100%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; +} + +/*=== Links */ +a, button.as-link { + outline: none; +} + +/*=== Forms */ +legend { + margin: 20px 0 5px; + padding: 5px 0; + font-size: 1.4em; +} +label { + min-height: 25px; + padding: 5px 0; + cursor: pointer; +} +textarea { + width: 360px; + height: 100px; +} +input, select, textarea { + min-height: 25px; + padding: 5px; + line-height: 25px; + vertical-align: middle; +} +option { + padding: 0 .5em; +} +input:focus, select:focus, textarea:focus { +} +input:invalid, select:invalid { +} +input:disabled, select:disabled { +} +input.extend { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +/*=== Tables */ +table { + border-collapse: collapse; +} + +tr, th, td { + padding: 0.5em; +} +th { +} +form td, +form th { + font-weight: normal; + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group.form-actions { + padding: 5px 0; +} +.form-group.form-actions .btn { + margin: 0 10px; +} +.form-group .group-name { + padding: 10px 0; + text-align: right; +} +.form-group .group-controls { + min-height: 25px; + padding: 5px 0; +} +.form-group table { + margin: 10px 0 0 220px; +} + +/*=== Buttons */ +.stick { + vertical-align: middle; + font-size: 0; +} +.stick input, +.stick .btn { +} +.stick .btn:first-child, +.stick input:first-child { +} +.stick .btn-important:first-child { +} +.stick .btn:last-child, +.stick input:last-child { +} +.stick .btn + .btn, +.stick .btn + input, +.stick .btn + .dropdown > .btn, +.stick input + .btn, +.stick input + input, +.stick input + .dropdown > .btn, +.stick .dropdown + .btn, +.stick .dropdown + input, +.stick .dropdown + .dropdown > .btn { +} +.stick input + .btn { +} +.stick .btn + .dropdown > .btn { +} + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + margin: 0; + padding: 5px 10px; + font-size: 0.9rem; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} +a.btn { + min-height: 25px; + line-height: 25px; +} +.btn:hover { + text-decoration: none; +} +.btn.active, +.btn:active, +.dropdown-target:target ~ .btn.dropdown-toggle { +} + +.btn-important { + font-weight: normal; +} +.btn-important:hover { +} +.btn-important:active { +} + +.btn-attention { +} +.btn-attention:hover { +} +.btn-attention:active { +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + height: 2.5em; + line-height: 2.5em; + font-size: 0.9rem; +} +.nav-list .item:hover { +} +.nav-list .item:hover a { +} +.nav-list .item.active { +} +.nav-list .item.active a { +} +.nav-list .disable { + text-align: center; +} +.nav-list .item > a { + padding: 0 10px; +} +.nav-list a:hover { + text-decoration: none; +} +.nav-list .item.empty a { +} +.nav-list .item.active.empty a { +} +.nav-list .item.error a { +} +.nav-list .item.active.error a { +} + +.nav-list .nav-header { + padding: 0 10px; + font-weight: bold; +} + +.nav-list .nav-form { + padding: 3px; + text-align: center; +} + +.nav-head { + margin: 0; + text-align: right; +} +.nav-head .item { + padding: 5px 10px; + font-size: 0.9rem; + line-height: 1.5rem; +} + +/*=== Horizontal-list */ +.horizontal-list { + margin: 0; + padding: 0; +} +.horizontal-list .item { + vertical-align: middle; +} + +/*=== Dropdown */ +.dropdown-menu { + margin: 5px 0 0; + padding: 5px 0; + font-size: 0.8rem; + text-align: left; +} +.dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + z-index: -10; + transform: rotate(45deg); + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} +.dropdown-header { + padding: 0 5px 5px; + font-weight: bold; + text-align: left; +} +.dropdown-menu > .item { +} +.dropdown-menu > .item > a { + padding: 0 25px; + line-height: 2.5em; +} +.dropdown-menu > .item > span, +.dropdown-menu > .item > .as-link { + padding: 0 22px; + line-height: 2em; +} +.dropdown-menu > .item:hover { +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + font-weight: bold; + margin: 0 0 0 -14px; +} +.dropdown-menu > .item:hover > a { + text-decoration: none; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + margin: 0 auto 5px; + padding: 2px 5px; +} + +.separator { + margin: 5px 0; +} + +/*=== Alerts */ +.alert { + margin: 15px auto; + padding: 10px 15px; + font-size: 0.9em; +} +.alert-head { + font-size: 1.15em; +} +.alert > a { + text-decoration: underline; +} +.alert-warn { +} +.alert-success { +} +.alert-error { +} + +/*=== Pagination */ +.pagination { + text-align: center; + font-size: 0.8em; +} +.content .pagination { + margin: 0; + padding: 0; +} +.pagination .item.pager-current { + font-weight: bold; + font-size: 1.5em; +} +.pagination .item a { + display: block; + font-style: italic; + line-height: 3em; + text-decoration: none; +} +.pagination .item a:hover { +} +.pagination:first-child .item { +} +.pagination:last-child .item { +} + +.pagination .loading, +.pagination a:hover.loading { + font-size: 0; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + height: 85px; +} +.header > .item { + padding: 10px; + vertical-align: middle; + text-align: center; +} +.header > .item.title{ + width: 230px; +} +.header > .item.title h1 { + margin: 0.5em 0; +} +.header > .item.title h1 a { + text-decoration: none; +} +.header > .item.search input { + width: 230px; +} +.header .item.search input:focus { + width: 350px; +} + +/*=== Body */ +#global { + height: calc(100% - 85px); +} +.aside { +} +.aside.aside_flux { + padding: 10px 0 50px; +} + +/*=== Aside main page (categories) */ +.categories { + text-align: center; +} +.category { + width: 235px; + margin: 10px auto; + text-align: left; +} +.category .btn:first-child { + position: relative; + width: 213px; +} +.category.stick .btn:first-child { + width: 176px; +} +.category .btn:first-child:not([data-unread="0"]):after { + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; +} + +/*=== Aside main page (feeds) */ +.categories .feeds .item.active { +} +.categories .feeds .item.empty.active { +} +.categories .feeds .item.error.active { +} +.categories .feeds .item.empty .feed { +} +.categories .feeds .item.error .feed { +} +.categories .feeds .item.active .feed, +.categories .feeds .item.empty.active .feed, +.categories .feeds .item.error.active .feed { +} +.categories .feeds .item .feed { + margin: 0; + width: 165px; + line-height: 3em; + font-size: 0.8em; + text-align: left; + text-decoration: none; +} +.categories .feeds .feed:not([data-unread="0"]) { + font-weight: bold; +} +.categories .feeds .dropdown-menu:after { + left: 2px; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + vertical-align: middle; +} + +/*=== Configuration pages */ +.post { + padding: 10px 50px; + font-size: 0.9em; +} +.post form { + margin: 10px 0; +} +.post.content { + max-width: 550px; +} + +/*=== Prompt (centered) */ +.prompt { + text-align: center; +} +.prompt label { + text-align: left; +} +.prompt form { + margin: 10px auto 20px auto; + width: 180px; +} +.prompt input { + margin: 5px auto; + width: 100%; +} +.prompt p { + margin: 20px 0; +} + +/*=== New article notification */ +#new-article { + text-align: center; + font-size: 0.9em; +} +#new-article:hover { +} +#new-article > a { + line-height: 3em; + font-weight: bold; +} +#new-article > a:hover { + text-decoration: none; +} + +/*=== Day indication */ +.day { + padding: 0 10px; + font-weight: bold; + line-height: 3em; +} +#new-article + .day { +} +.day .name { + padding: 0 10px 0 0; + font-size: 1.8em; + opacity: 0.3; + font-style: italic; + text-align: right; +} + +/*=== Index menu */ +.nav_menu { + text-align: center; + padding: 5px 0; +} + +/*=== Feed articles */ +.flux { +} +.flux:hover { +} +.flux.current { +} +.flux.not_read { +} +.flux.not_read:not(.current):hover .item.title { +} +.flux.favorite { +} +.flux.favorite:not(.current):hover .item.title { +} +.flux.current { +} + + +.flux_header { + font-size: 0.8rem; + cursor: pointer; +} +.flux_header .title { + font-size: 0.9rem; +} +.flux .website .favicon { + padding: 5px; +} +.flux .date { + font-size: 0.7rem; +} +.flux:not(.current):hover .item.title { +} + +.flux .bottom { + font-size: 0.8rem; + text-align: center; +} + +/*=== Content of feed articles */ +.content { + padding: 20px 10px; +} +.content > h1.title > a { +} + +.content hr { + margin: 30px 10px; + height: 1px; +} + +.content pre { + margin: 10px auto; + padding: 10px 20px; + overflow: auto; + font-size: 0.9rem; +} +.content code { + padding: 2px 5px; +} +.content pre code { +} + +.content blockquote { + display: block; + margin: 0; + padding: 5px 20px; +} +.content blockquote p { + margin: 0; +} + +/*=== Notification and actualize notification */ +.notification { + padding: 0 0 0 5px; + text-align: center; + font-weight: bold; + font-size: 0.9em; + line-height: 3em; + z-index: 10; + vertical-align: middle; +} +.notification.good { +} +.notification.bad { +} +.notification a.close { + padding: 0 15px; + line-height: 3em; +} +.notification.good a.close:hover { +} +.notification.bad a.close:hover { +} + +.notification#actualizeProgress { + line-height: 2em; +} + +/*=== "Load more" part */ +#bigMarkAsRead { + text-align: center; + text-decoration: none; +} +#bigMarkAsRead:hover { +} +#bigMarkAsRead:hover .bigTick { +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + margin: 0; + text-align: center; + line-height: 3em; + table-layout: fixed; +} + +/*=== READER VIEW */ +/*================*/ +#stream.reader .flux { + padding: 0 0 50px; +} +#stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; +} + +/*=== GLOBAL VIEW */ +/*================*/ +#stream.global .box-category { + text-align: left; +} +#stream.global .category { + margin: 0; +} +#stream.global .btn { + width: auto; + height: 2em; + margin: 0; + padding: 0 10px; + line-height: 2em; + font-size: 1.2rem; +} +#stream.global .btn:not([data-unread="0"]) { + font-weight: bold; +} +#stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + font-weight: bold; +} +#stream.global .box-category .feeds { + max-height: 250px; +} +#stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 0.9rem; +} + +/*=== DIVERS */ +/*===========*/ +.aside.aside_feed .nav-form input, +.aside.aside_feed .nav-form select { + width: 140px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; +} +.aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; +} + +/*=== STATISTICS */ +/*===============*/ +.stat { + margin: 10px 0 20px; +} + +.stat th, +.stat td, +.stat tr { +} +.stat > table td, +.stat > table th { + text-align: center; +} + +.stat > .horizontal-list { + margin: 0 0 5px; +} +.stat > .horizontal-list .item { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.stat > .horizontal-list .item:first-child { + width: 250px; +} + +/*=== LOGS */ +/*=========*/ +.logs { + overflow: hidden; +} +.log { + padding: 5px 10px; + font-size: 0.8rem; +} +.log+.log { +} +.log .date { + display: block; + font-weight: bold; +} +.log.error { +} +.log.warning { +} +.log.notice { +} +.log.debug { +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .aside { + transition: width 200ms linear; + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } + .aside .toggle_aside, + #panel .close { + position: absolute; + display: block; + top: 0; right: 0; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + .day .name { + font-size: 1.1rem; + } + + .pagination { + margin: 0 0 3.5em; + } + + .notification a.close { + display: block; + left: 0; + } + .notification a.close:hover { + opacity: 0.5; + } + .notification a.close .icon { + display: none; + } +} diff --git a/p/themes/base-theme/metadata.json b/p/themes/base-theme/metadata.json new file mode 100644 index 000000000..4d59365a8 --- /dev/null +++ b/p/themes/base-theme/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "", + "author": "Your name", + "description": "A wonderful base theme", + "version": 0.1, + "files": ["template.css", "base.css"] +} diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css new file mode 100644 index 000000000..dc011503d --- /dev/null +++ b/p/themes/base-theme/template.css @@ -0,0 +1,734 @@ +@charset "UTF-8"; + +/*=== GENERAL */ +/*============*/ +html, body { + margin: 0; + padding: 0; + font-size: 100%; +} + +/*=== Links */ +a { + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +/*=== Lists */ +ul, ol, dd { + margin: 0; + padding: 0; +} + +/*=== Titles */ +h1 { + margin: 0.6em 0 0.3em; + font-size: 1.5em; + line-height: 1.6em; +} +h2 { + margin: 0.5em 0 0.25em; + font-size: 1.3em; + line-height: 2em; +} +h3 { + margin: 0.5em 0 0.25em; + font-size: 1.1em; + line-height: 2em; +} + +/*=== Paragraphs */ +p { + margin: 1em 0 0.5em; + font-size: 1em; +} + +/*=== Images */ +img { + height: auto; + max-width: 100%; +} +img.favicon { + height: 16px; + width: 16px; + vertical-align: middle; +} + +/*=== Videos */ +iframe, embed, object, video { + max-width: 100%; +} + +/*=== Forms */ +legend { + display: block; + width: 100%; + clear: both; +} +label { + display: block; +} +input { + width: 180px; +} +textarea { + width: 300px; +} +input, select, textarea { + display: inline-block; + max-width: 100%; +} +input[type="radio"], +input[type="checkbox"] { + width: 15px !important; + min-height: 15px !important; +} +input.extend:focus { + width: 300px; +} +button.as-link, +button.as-link:hover, +button.as-link:active { + background: transparent; + border: none; + color: inherit; + cursor: pointer; + font-size: 1.1em; + text-align: left; +} + +/*=== Tables */ +table { + max-width: 100%; +} +th.numeric, +td.numeric { + text-align: center; +} + +/*=== COMPONENTS */ +/*===============*/ +/*=== Forms */ +.form-group:after { + content: ""; + display: block; + clear: both; +} +.form-group.form-actions { + min-width: 250px; +} +.form-group .group-name { + display: block; + float: left; + width: 200px; +} +.form-group .group-controls { + min-width: 250px; + margin: 0 0 0 220px; +} +.form-group .group-controls .control { + display: block; +} + +/*=== Buttons */ +.stick { + display: inline-block; + white-space: nowrap; +} +.btn, +a.btn { + display: inline-block; + cursor: pointer; + overflow: hidden; +} +.btn-important { + font-weight: bold; +} + +/*=== Navigation */ +.nav-list .nav-header, +.nav-list .item { + display: block; +} +.nav-list .item, +.nav-list .item > a { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.nav-head { + display: block; +} +.nav-head .item { + display: inline-block; +} + +/*=== Horizontal-list */ +.horizontal-list { + display: table; + table-layout: fixed; + width: 100%; +} +.horizontal-list .item { + display: table-cell; +} + +/*=== Dropdown */ +.dropdown { + position: relative; + display: inline-block; +} +.dropdown-target { + display: none; +} +.dropdown-menu { + display: none; + min-width: 200px; + margin: 0; + position: absolute; + right: 0; + background: #fff; + border: 1px solid #aaa; +} +.dropdown-header { + display: block; +} +.dropdown-menu > .item { + display: block; +} +.dropdown-menu > .item > a, +.dropdown-menu > .item > .as-link, +.dropdown-menu > .item > span { + display: block; + min-width: 200px; +} +.dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓'; +} +.dropdown-menu .input { + display: block; +} +.dropdown-menu .input select, +.dropdown-menu .input input { + display: block; + max-width: 95%; +} +.dropdown-target:target ~ .dropdown-menu { + display: block; + z-index: 10; +} +.dropdown-close { + display: inline; +} +.dropdown-close a { + font-size: 0; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; + z-index: -10; +} +.separator { + display: block; + height: 0; + border-bottom: 1px solid #aaa; +} + +/*=== Alerts */ +.alert { + display: block; + width: 90%; +} +.group-controls .alert { + width: 100% +} +.alert-head { + margin: 0; + font-weight: bold; +} +.alert ul { + margin: 5px 20px; +} + +/*=== Icons */ +.icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + line-height: 16px; +} + +/*=== Pagination */ +.pagination { + display: table; + width: 100%; + margin: 0; + padding: 0; + table-layout: fixed; +} +.pagination .item { + display: table-cell; +} +.pagination .pager-first, +.pagination .pager-previous, +.pagination .pager-next, +.pagination .pager-last { + width: 100px; +} + +/*=== STRUCTURE */ +/*===============*/ +/*=== Header */ +.header { + display: table; + width: 100%; + table-layout: fixed; +} +.header > .item { + display: table-cell; +} +.header > .item.title { + width: 250px; + white-space: nowrap; +} +.header > .item.title h1 { + display: inline-block; +} +.header > .item.title .logo { + display: inline-block; + height: 32px; + width: 32px; + vertical-align: middle; +} +.header > .item.configure { + width: 100px; +} + +/*=== Body */ +#global { + display: table; + width: 100%; + height: 100%; + table-layout: fixed; +} +.aside { + display: table-cell; + height: 100%; + width: 250px; + vertical-align: top; +} +.aside.aside_flux { + background: #fff; +} + +/*=== Aside main page (categories) */ +.categories { + list-style: none; + margin: 0; +} +.state_unread li:not(.active)[data-unread="0"] { + display: none; +} +.category { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.category .btn:not([data-unread="0"]):after { + content: attr(data-unread); +} + +/*=== Aside main page (feeds) */ +.categories .feeds { + width: 100%; + list-style: none; +} +.categories .feeds:not(.active) { + display: none; +} +.categories .feeds .feed { + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: middle; +} +.categories .feeds .feed:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; +} +.categories .feeds .dropdown-menu { + left: 0; +} +.categories .feeds .item .dropdown-toggle > .icon { + visibility: hidden; + cursor: pointer; + vertical-align: top; +} +.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.categories .feeds .item:hover .dropdown-toggle > .icon, +.categories .feeds .item.active .dropdown-toggle > .icon { + visibility: visible; +} + +/*=== New article notification */ +#new-article { + display: none; +} +#new-article > a { + display: block; +} + +/*=== Day indication */ +.day .name { + position: absolute; + right: 0; + width: 50%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +/*=== Feed article header and footer */ +.flux_header { + position: relative; +} +.flux .item { + line-height: 40px; + white-space: nowrap; +} +.flux .item.manage, +.flux .item.link { + width: 40px; + text-align: center; +} +.flux .item.website { + width: 200px; +} +.flux.not_read .item.title, +.flux.current .item.title { + font-weight: bold; +} +.flux:not(.current):hover .item.title { + position: absolute; + max-width: calc(100% - 320px); + background: #fff; +} +.flux .item.title a { + color: #000; + text-decoration: none; +} +.flux .item.date { + width: 145px; + text-align: right; +} +.flux .item > a { + display: block; +} +.flux .item > a { + display: block; + text-decoration: none; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} +.flux .item.share > a { + display: list-item; + list-style-position: inside; + list-style-type: decimal; +} + +/*=== Feed article content */ +.hide_posts > .flux:not(.active) > .flux_content { + display: none; +} +.content { + min-height: 20em; + margin: auto; + line-height: 1.7em; + word-wrap: break-word; +} +.content.large { + max-width: 1000px; +} +.content.medium { + max-width: 800px; +} +.content.thin { + max-width: 550px; +} +.content ul, +.content ol, +.content dd { + margin: 0 0 0 15px; + padding: 0 0 5px 15px; +} +.content pre { + overflow: auto; +} +br { + line-height: 1em; +} +br + br + br { + display: none; +} + +/*=== Notification and actualize notification */ +.notification { + position: absolute; + top: 1em; + left: 25%; right: 25%; + z-index: 10; + background: #fff; + border: 1px solid #aaa; +} +.notification.closed { + display: none; +} +.notification a.close { + position: absolute; + top: 0; bottom: 0; + right: 0; + display: inline-block; +} + +#actualizeProgress { + position: fixed; +} +#actualizeProgress progress { + max-width: 100%; + vertical-align: middle; +} +#actualizeProgress .progress { + vertical-align: middle; +} + +/*=== Navigation menu (for articles) */ +#nav_entries { + position: fixed; + bottom: 0; left: 0; + display: table; + width: 250px; + background: #fff; + table-layout: fixed; +} +#nav_entries .item { + display: table-cell; + width: 30%; +} +#nav_entries a { + display: block; +} + +/*=== "Load more" part */ +#load_more { + min-height: 40px; +} +.loading { + background: url("loader.gif") center center no-repeat; + font-size: 0; +} +#bigMarkAsRead { + display: block; + width: 100%; + padding: 1em 0; + text-align: center; + font-size: 1.4em; +} +.bigTick { + font-size: 4em; +} + +/*=== Statistiques */ +.stat { + margin: 15px 0; +} +.stat.half { + display: inline-block; + width: 46%; + padding: 0 2%; +} +.stat > table { + width: 100%; +} + +/*=== GLOBAL VIEW */ +/*================*/ +/*=== Category boxes */ +#stream.global .box-category { + display: inline-block; + width: 19em; + max-width: 95%; + margin: 20px 10px; + border: 1px solid #ccc; + vertical-align: top; +} +#stream.global .category { + width: 100%; +} +#stream.global .btn { + display: block; +} +#stream.global .box-category .feeds { + display: block; + overflow: auto; +} +#stream.global .box-category .feed { + width: 19em; + max-width: 90%; +} + +/*=== Panel */ +#overlay { + display: none; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + background: rgba(0, 0, 0, 0.9); +} +#panel { + display: none; + position: fixed; + top: 1em; bottom: 1em; + left: 2em; right: 2em; + overflow: auto; + background: #fff; +} +#panel .close { + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; +} +#panel .close img { + display: none; +} + +/*=== DIVERS */ +/*===========*/ +.nav-login, +.nav_menu .search, +.nav_menu .toggle_aside { + display: none; +} + +.aside .toggle_aside { + position: absolute; + right: 0; + display: none; + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; +} + +/*=== MOBILE */ +/*===========*/ +@media(max-width: 840px) { + .header, + .aside .btn-important, + .aside .feeds .dropdown, + .flux_header .item.website span, + .item.date, .day .date, + .dropdown-menu > .no-mobile, + .no-mobile { + display: none; + } + .nav-login { + display: block; + } + .nav_menu .toggle_aside, + .aside .toggle_aside, + .nav_menu .search, + #panel .close img { + display: inline-block; + } + + .aside { + position: fixed; + top: 0; bottom: 0; + left: 0; + width: 0; + overflow: hidden; + z-index: 100; + } + .aside:target { + width: 90%; + overflow: auto; + } + .aside .categories { + margin: 10px 0 75px; + } + + .flux_header .item.website { + width: 40px; + } + + .flux:not(.current):hover .item.title { + position: relative; + width: auto; + white-space: nowrap; + } + + .notification { + top: 0; + left: 0; + right: 0; + } + + #nav_entries { + width: 100%; + } + + #stream.global .box-category { + margin: 10px 0; + } + + #panel { + top: 0; bottom: 0; + left: 0; right: 0; + } + #panel .close { + top: 0; right: 0; + left: auto; bottom: auto; + display: inline-block; + width: 30px; + height: 30px; + } +} + +/*=== PRINTER */ +/*============*/ +@media print { + .header, .aside, + .nav_menu, .day, + .flux_header, + .flux_content .bottom, + .pagination, + #nav_entries { + display: none; + } + html, body { + background: #fff; + color: #000; + font-family: Serif; + } + #global, + .flux_content { + display: block !important; + } + .flux_content .content { + width: 100% !important; + } + .flux_content .content a { + color: #000; + } + .flux_content .content a:after { + content: " [" attr(href) "] "; + font-style: italic; + } +} diff --git a/public/themes/default/fonts/openSans.woff b/p/themes/fonts/openSans.woff Binary files differindex 55b25f867..55b25f867 100644 --- a/public/themes/default/fonts/openSans.woff +++ b/p/themes/fonts/openSans.woff diff --git a/p/themes/icons/add.svg b/p/themes/icons/add.svg new file mode 100644 index 000000000..652491775 --- /dev/null +++ b/p/themes/icons/add.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-60.0002,-726)"> +<path style="color:#666666;" fill="#666" d="m67,729,0,4-4,0,0,2,4,0,0,4,2,0,0-4,4,0,0-2-4,0,0-4-2,0z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/all.svg b/p/themes/icons/all.svg new file mode 100644 index 000000000..aeaa96277 --- /dev/null +++ b/p/themes/icons/all.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-40.0002,-746)" fill="#bebebe"> +<rect style="color:#bebebe;" height="2.0002" width="9.9996" y="749" x="43"/> +<rect style="color:#bebebe;" height="2.0002" width="9.9996" y="753" x="43"/> +<rect style="color:#bebebe;" height="2.0002" width="9.9996" y="757" x="43"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/apple-touch-icon.png b/p/themes/icons/apple-touch-icon.png Binary files differnew file mode 100644 index 000000000..0bbab07ea --- /dev/null +++ b/p/themes/icons/apple-touch-icon.png diff --git a/p/themes/icons/bookmark-add.svg b/p/themes/icons/bookmark-add.svg new file mode 100644 index 000000000..51db9c498 --- /dev/null +++ b/p/themes/icons/bookmark-add.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-141.0002,-807)" fill="#bebebe"> +<path d="m143,807,0,13,4-4,4,4,0-4,0-1-2,0,0-4,2,0,0-4z"/> +<path d="m152,810,0,2-2,0,0,2,2,0,0,2,2,0,0-2,2,0,0-2-2,0,0-2-2,0z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/bookmark.svg b/p/themes/icons/bookmark.svg new file mode 100644 index 000000000..63a44908f --- /dev/null +++ b/p/themes/icons/bookmark.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="enable-background:accumulate;color:#000000;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#f1c40f"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/category-white.svg b/p/themes/icons/category-white.svg new file mode 100644 index 000000000..5bb5cecd9 --- /dev/null +++ b/p/themes/icons/category-white.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-442,-176)"> +<g transform="translate(234.0002,-820)"> +<path d="m208.53,997c-0.28913,0-0.53125,0.24212-0.53125,0.53125v13.938c0,0.2985,0.23264,0.5312,0.53125,0.5312h14.938c0.2986,0,0.53125-0.2326,0.53125-0.5312v-8.9376c0-0.2891-0.24212-0.5312-0.53125-0.5312h-12.469v7.5c0,0.277-0.223,0.5-0.5,0.5s-0.5-0.223-0.5-0.5v-8c0-0.277,0.223-0.5,0.5-0.5h2.9688,8.5312v-1.4062c0-0.3272-0.26666-0.5938-0.59375-0.5938h-7.4062v-1.4688c0-0.39-0.24-0.63-0.53-0.63z" fill="#FFF"/> +</g> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/category.svg b/p/themes/icons/category.svg new file mode 100644 index 000000000..b5470d84a --- /dev/null +++ b/p/themes/icons/category.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-442,-176)"> +<g transform="translate(234.0002,-820)"> +<path d="m208.53,997c-0.28913,0-0.53125,0.24212-0.53125,0.53125v13.938c0,0.2985,0.23264,0.5312,0.53125,0.5312h14.938c0.2986,0,0.53125-0.2326,0.53125-0.5312v-8.9376c0-0.2891-0.24212-0.5312-0.53125-0.5312h-12.469v7.5c0,0.277-0.223,0.5-0.5,0.5s-0.5-0.223-0.5-0.5v-8c0-0.277,0.223-0.5,0.5-0.5h2.9688,8.5312v-1.4062c0-0.3272-0.26666-0.5938-0.59375-0.5938h-7.4062v-1.4688c0-0.39-0.24-0.63-0.53-0.63z" fill="#666"/> +</g> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/close.svg b/p/themes/icons/close.svg new file mode 100644 index 000000000..20d1577ab --- /dev/null +++ b/p/themes/icons/close.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-60,-518)"> +<g transform="translate(19,-242)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m45,764,1,0c0.01037-0.00012,0.02079-0.00046,0.03125,0,0.25495,0.0112,0.50987,0.12858,0.6875,0.3125l2.282,2.28,2.312-2.28c0.266-0.23,0.447-0.3,0.688-0.31h1v1c0,0.28647-0.03434,0.55065-0.25,0.75l-2.2812,2.2812,2.25,2.25c0.188,0.19,0.281,0.45,0.281,0.72v1h-1c-0.2653-0.00001-0.53059-0.0931-0.71875-0.28125l-2.281-2.28-2.281,2.28c-0.188,0.19-0.454,0.28-0.719,0.28h-1v-1c-0.000003-0.26529,0.09306-0.53058,0.28125-0.71875l2.2812-2.25-2.281-2.28c-0.21-0.19-0.303-0.47-0.281-0.75v-1z" fill-rule="nonzero" fill="#bebebe"/> +</g> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/configure.svg b/p/themes/icons/configure.svg new file mode 100644 index 000000000..2957a7abe --- /dev/null +++ b/p/themes/icons/configure.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-441.0002,-400.99999)"> +<path style="color:#666666;enable-background:accumulate;" d="m449,402c-0.22065,0-0.44081,0.0113-0.65625,0.0312l-0.40625,2.0938c-0.33446,0.0733-0.66305,0.17589-0.96875,0.3125l-1.5312-1.4688c-0.38863,0.23011-0.72695,0.51408-1.0625,0.8125l0.90625,1.9062c-0.22242,0.24899-0.42425,0.5225-0.59375,0.8125l-2.0938-0.28125c-0.17772,0.40877-0.30872,0.83637-0.40625,1.2812l1.8438,1c-0.0171,0.16809-0.0312,0.3274-0.0312,0.5s0.0142,0.33191,0.0312,0.5l-1.8438,1c0.0975,0.44488,0.22853,0.87248,0.40625,1.2812l2.0938-0.28125c0.1695,0.29,0.37133,0.56351,0.59375,0.8125l-0.90625,1.9062c0.33555,0.29842,0.67387,0.58239,1.0625,0.8125l1.5312-1.4688c0.3057,0.13661,0.63429,0.23916,0.96875,0.3125l0.40625,2.0938c0.21544,0.02,0.4356,0.0312,0.65625,0.0312s0.44081-0.0113,0.65625-0.0312l0.40625-2.0938c0.33446-0.0733,0.66305-0.17589,0.96875-0.3125l1.5312,1.4688c0.38863-0.23011,0.72695-0.51408,1.0625-0.8125l-0.90625-1.9062c0.22242-0.24899,0.42425-0.5225,0.59375-0.8125l2.0938,0.28125c0.17772-0.40877,0.30872-0.83637,0.40625-1.2812l-1.8438-1c0.0171-0.16809,0.0312-0.3274,0.0312-0.5s-0.0142-0.33191-0.0312-0.5l1.8438-1c-0.0975-0.44488-0.22853-0.87248-0.40625-1.2812l-2.0938,0.28125c-0.1695-0.29-0.37133-0.56351-0.59375-0.8125l0.90625-1.9062c-0.33555-0.29842-0.67387-0.58239-1.0625-0.8125l-1.5312,1.4688c-0.3057-0.13661-0.63429-0.23916-0.96875-0.3125l-0.40625-2.0938c-0.21544-0.02-0.4356-0.0312-0.65625-0.0312zm0,4c1.6568,0,3,1.3432,3,3s-1.3432,3-3,3-3-1.3432-3-3,1.3432-3,3-3z" fill-rule="nonzero" fill="#666"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/down.svg b/p/themes/icons/down.svg new file mode 100644 index 000000000..8a234ee99 --- /dev/null +++ b/p/themes/icons/down.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-181.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m195.03,751,0,1c-0.00091,0.0111,0.00059,0.021-0.00009,0.0312-0.0112,0.25496-0.12835,0.50994-0.31251,0.6875l-5.7188,6.2977-5.7188-6.2977c-0.18821-0.1881-0.28121-0.45346-0.28122-0.71875v-1h1c0.26531,0.00007,0.53059,0.0931,0.71873,0.28131l4.2812,4.829,4.2813-4.829c0.19464-0.21073,0.46925-0.30315,0.74998-0.2813z" fill-rule="nonzero" fill="#bebebe"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/favicon-16-32-48-64.ico b/p/themes/icons/favicon-16-32-48-64.ico Binary files differnew file mode 100644 index 000000000..90f7d5118 --- /dev/null +++ b/p/themes/icons/favicon-16-32-48-64.ico diff --git a/p/themes/icons/favicon-256.png b/p/themes/icons/favicon-256.png Binary files differnew file mode 100644 index 000000000..8b1021e1a --- /dev/null +++ b/p/themes/icons/favicon-256.png diff --git a/p/themes/icons/favicon.svg b/p/themes/icons/favicon.svg new file mode 100644 index 000000000..a252050b9 --- /dev/null +++ b/p/themes/icons/favicon.svg @@ -0,0 +1,13 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <title>Logo FreshRSS</title> + <circle fill="#FFF" cx="128" cy="128" r="128"/> + <circle fill="#0062BE" cx="128" cy="128" r="33"/> + <g fill="none" stroke="#0062BE" stroke-width="24"> + <g stroke-opacity="0.3"> + <path d="M12,128 A116,116 0 1,1 128,244"/> + <path d="M54,128 A74,74 0 1,1 128,202"/> + </g> + <path d="M128,12 A116,116 0 0,1 244,128"/> + <path d="M128,54 A74,74 0 0,1 202,128"/> + </g> +</svg> diff --git a/public/data/grey.gif b/p/themes/icons/grey.gif Binary files differindex c7212bc1f..c7212bc1f 100644 --- a/public/data/grey.gif +++ b/p/themes/icons/grey.gif diff --git a/p/themes/icons/help.svg b/p/themes/icons/help.svg new file mode 100644 index 000000000..9a0022f76 --- /dev/null +++ b/p/themes/icons/help.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-182,-490)" fill="#bebebe"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m190,490c-4.4147,0-8,3.5853-8,8s3.5853,8,8,8,8-3.5853,8-8-3.5853-8-8-8zm0,2c3.3413,0,6,2.6587,6,6s-2.6587,6-6,6-6-2.6587-6-6,2.6587-6,6-6z"/> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="M189.34,495c-1.28,0-2.34,1.06-2.34,2.34v1.3125c0,1.2861,1.0576,2.3438,2.3438,2.3438h1.3125c1.29,0.01,2.35-1.05,2.35-2.33v-1.3125c0-1.29-1.06-2.35-2.34-2.35h-1.3125zm0,1,1.3125,0c0.74942,0,1.3438,0.59433,1.3438,1.3438v1.3125c0.01,0.76-0.58,1.35-1.33,1.35h-1.3125c-0.76,0-1.35-0.59-1.35-1.34v-1.3125c0-0.76,0.59-1.35,1.34-1.35z"/> +<path d="m186.72,491.44c-1.5103,0.6073-2.6811,1.7985-3.2812,3.3125l3.75,1.875c0.25196-0.64029,0.74249-1.1706,1.375-1.4375l-1.8438-3.75zm6.5625,0-1.8438,3.75c0.63251,0.26694,1.123,0.79721,1.375,1.4375l3.75-1.875c-0.60015-1.514-1.7709-2.7052-3.2812-3.3125zm-6.0938,8-3.75,1.875c0.60709,1.4886,1.789,2.65,3.2812,3.25l1.875-3.75c-0.62682-0.25556-1.1433-0.75203-1.4062-1.375zm5.625,0c-0.26291,0.62297-0.77943,1.1194-1.4062,1.375l1.875,3.75c1.4923-0.60005,2.6742-1.7614,3.2812-3.25l-3.75-1.875z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/icon.svg b/p/themes/icons/icon.svg new file mode 100644 index 000000000..caa987dac --- /dev/null +++ b/p/themes/icons/icon.svg @@ -0,0 +1,12 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"> + <title>Logo FreshRSS</title> + <circle fill="#0062BE" cx="128" cy="128" r="33"/> + <g fill="none" stroke="#0062BE" stroke-width="24"> + <g stroke-opacity="0.3"> + <path d="M12,128 A116,116 0 1,1 128,244"/> + <path d="M54,128 A74,74 0 1,1 128,202"/> + </g> + <path d="M128,12 A116,116 0 0,1 244,128"/> + <path d="M128,54 A74,74 0 0,1 202,128"/> + </g> +</svg> diff --git a/p/themes/icons/key.svg b/p/themes/icons/key.svg new file mode 100644 index 000000000..9193f4552 --- /dev/null +++ b/p/themes/icons/key.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg"> +<g transform="translate(-340.99994,-257)" fill="#666666"> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m346,260c-2.7496,0-5,2.2504-5,5s2.2504,5,5,5c1.5862,0,2.9034-0.84459,3.8125-2h4.8438,0.75l0.21875-0.75,1.0312-4,0.3125-1.25h-1.2812-5.875c-0.90914-1.1554-2.2263-2-3.8125-2zm0,2c1.1158,0,2.0379,0.59507,2.5625,1.5l0.3125,0.5h0.5625,4.9688l-0.53125,2h-4.4375-0.5625l-0.3125,0.5c-0.52462,0.90493-1.4466,1.5-2.5625,1.5-1.6687,0-3-1.3313-3-3s1.3313-3,3-3z"/> +<path opacity="0.35" style="enable-background:accumulate;color:#000000;" d="M355.5,265,350,265,349.44,267,355,267z" fill-rule="nonzero"/> +<path style="enable-background:accumulate;color:#000000;" d="m346,265c0,0.55228-0.44772,1-1,1s-1-0.44772-1-1,0.44772-1,1-1,1,0.44772,1,1z" fill-rule="nonzero"/> +</g> +</svg> diff --git a/p/themes/icons/link.svg b/p/themes/icons/link.svg new file mode 100644 index 000000000..e5290af76 --- /dev/null +++ b/p/themes/icons/link.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16.004" width="16"> +<g fill="#bebebe" transform="translate(-183,-529)"> +<path style="enable-background:accumulate;color:#000000;" d="m191,533.85,0,10.38-2.344-2.2882-1.3394,2.7346c-0.32808,0.73962-2.0337,0.14492-1.5487-0.84412l1.3255-2.8393h-2.9579l6.8645-7.1436z" fill-rule="nonzero" display="block"/> +<path d="m190.16,530.06c-3.8266,0.46006-6.5788,3.9578-6.0938,7.7812,0.13127,1.0347,0.29377,1.3818,0.29377,1.3818l1.675-1.6318c-0.33104-2.7534,1.6216-5.2315,4.375-5.5625,2.7534-0.33104,5.2315,1.6216,5.5625,4.375,0.31355,2.608-1.3913,5.0249-3.9688,5.5312l0.0312,2s0.52086-0.1059,0.62354-0.13097c3.4156-0.83385,5.7063-4.1273,5.2827-7.6503-0.46006-3.8266-3.9547-6.5538-7.7812-6.0938z" style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;"/> +<path opacity="0.3" style="enable-background:accumulate;color:#000000;" d="m187.11,536.81,0-0.20574-0.19826,0.0186c0.0165-0.13095,0.0329-0.26167,0.0496-0.3926h-0.11578l-0.11556,0.14959-0.11578,0.0559-0.1653-0.0932-0.0165-0.20575,0.0331-0.22438,0.24798-0.18688h0.19826l0.0329-0.11229,0.24786,0.0559,0.18183,0.2246,0.0331-0.37419,0.31401-0.26167,0.11567-0.28055,0.23133-0.0934,0.1322-0.18688,0.29738-0.0564,0.14885-0.22415h-0.44623l0.28094-0.13095h0.19814l0.28106-0.0937,0.0331-0.11186-0.0992-0.0937-0.11567-0.0375,0.0331-0.11208-0.0826-0.16822-0.19837,0.0746,0.0331-0.14947-0.23134-0.13096-0.18171,0.3177,0.0165,0.11229-0.18171,0.075-0.11578,0.24302-0.0495-0.22438-0.31402-0.13095-0.0496-0.16822,0.41315-0.24325,0.18182-0.16822,0.0165-0.20563-0.0991-0.0562-0.13219-0.0188-0.0826,0.20575s-0.1382,0.0271-0.17373,0.0358c-0.45378,0.41804-1.3707,1.3204-1.5837,3.024,0.008,0.0395,0.15441,0.26854,0.15441,0.26854l0.347,0.20552,0.347,0.0937m3.9661-4.3003-0.4298-0.16833-0.49552,0.0561-0.61161,0.16822-0.11567,0.11229,0.38008,0.26167,0,0.14959-0.14875,0.14959,0.19846,0.39294,0.13188-0.075,0.16561-0.26168c0.2553-0.0789,0.4842-0.16833,0.72686-0.28053l0.19846-0.5048m2.5292,0.34192-0.375,0.0937-0.21875,0.15625,0,0.125-0.375,0.25,0.0937,0.34375,0.21875-0.15625,0.125,0.15625,0.15625,0.0937,0.0937-0.28125-0.0625-0.15625,0.0625-0.0937,0.21875-0.1875,0.0937,0-0.0937,0.21875,0,0.1875c0.0892-0.0242,0.1588-0.051,0.25-0.0625l-0.25,0.1875v0.125l-0.3125,0.21875-0.28125-0.0625v-0.15625l-0.125,0.0625,0.0625,0.15625h-0.21875l-0.125,0.21875-0.15625,0.15625-0.0937,0.0312v0.1875l0.0312,0.15625h-0.0312v0.53125l0.0625-0.0312,0.0937-0.21875,0.1875-0.125,0.0312-0.0937,0.28125-0.0625,0.15625,0.1875,0.1875,0.0937-0.0937,0.1875,0.15625-0.0312,0.0625-0.21875-0.1875-0.21875h0.0625l0.21875,0.15625,0.0312,0.21875,0.15625,0.21875,0.0625-0.3125,0.0937-0.0312c0.0959,0.0996,0.1692,0.23163,0.25,0.34375h0.28125l0.1875,0.125-0.0937,0.0937-0.15625,0.15625h-0.25l-0.34375-0.0937h-0.1875l-0.125,0.15625-0.34375-0.375-0.25-0.0625-0.375,0.0625-0.15625,0.0937v2.4062l0.0312,0.0312,0.25-0.15625,0.0937,0.0937h0.28125l0.125,0.15625-0.0937,0.3125,0.1875,0.1875v0.375l0.125,0.25-0.0937,0.25c-0.009,0.16159,0,0.30714,0,0.46875,0.0795,0.21894,0.14355,0.43575,0.21875,0.65625l0.0625,0.34375v0.1875h0.125l0.21875-0.125h0.25l0.375-0.4375-0.0312-0.15625,0.25-0.21875-0.1875-0.1875,0.21875-0.1875,0.21875-0.125,0.0937-0.125-0.0625-0.25v-0.59375l0.1875-0.375,0.1875-0.25,0.25-0.5625v-0.15625c-0.11654,0.0146-0.22972,0.0231-0.34375,0.0312-0.0722,0.005-0.14446,0-0.21875,0-0.12359-0.25961-0.2183-0.50966-0.3125-0.78125l-0.15625-0.1875-0.0937-0.3125,0.0625-0.0625,0.21875,0.25,0.25,0.5625,0.15625,0.15625-0.0625,0.21875,0.15625,0.15625,0.25-0.25,0.3125-0.21875,0.15625-0.1875v-0.21875c-0.0389-0.0732-0.0547-0.14545-0.0937-0.21875l-0.15625,0.1875-0.125-0.15625-0.1875-0.125v-0.28125l0.21875,0.21875,0.21875-0.0312c0.10166,0.0923,0.19205,0.20751,0.28125,0.3125l0.15625-0.1875c0-0.17463-0.19976-1.0204-0.625-1.75-0.42526-0.72932-1.1562-1.4062-1.1562-1.4062l-0.0625,0.0937-0.21875,0.21875-0.25-0.25h0.25l0.125-0.125-0.46875-0.0937-0.25-0.0937z" fill-rule="nonzero"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/login.svg b/p/themes/icons/login.svg new file mode 100644 index 000000000..954a0b74b --- /dev/null +++ b/p/themes/icons/login.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-181.0002,-237)" fill="#bebebe"> +<path style="color:#bebebe;" d="m184,244c-0.554,0-1,0.446-1,1v0.53125,5.4688h12v-5.4688-0.53c0-0.554-0.446-1-1-1h-10z" fill-rule="nonzero"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m188,238c-1.6447,0-3,1.3553-3,3v7c0,1.6447,1.3553,3,3,3h2c1.6447,0,3-1.3553,3-3v-7c0-1.6447-1.3553-3-3-3h-2zm0,2,2,0c0.5713,0,1,0.4287,1,1v7c0,0.5713-0.4287,1-1,1h-2c-0.5713,0-1-0.4287-1-1v-7c0-0.5713,0.4287-1,1-1z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/logout.svg b/p/themes/icons/logout.svg new file mode 100644 index 000000000..8dd4ef08b --- /dev/null +++ b/p/themes/icons/logout.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-201.0002,-237)" fill="#bebebe"> +<path style="color:#bebebe;" d="m204,246c-0.554,0-1,0.446-1,1v0.53125,5.4688h12v-5.4688-0.53c0-0.554-0.446-1-1-1h-10z" fill-rule="nonzero"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m208,237c-1.6447,0-3,1.3553-3,3v3h2v-3c0-0.57129,0.42873-1,1-1h2c0.57127,0,1,0.42871,1,1v7h2v-7c0-1.6447-1.3553-3-3-3h-2z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/next.svg b/p/themes/icons/next.svg new file mode 100644 index 000000000..d504d096c --- /dev/null +++ b/p/themes/icons/next.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-121.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m125,749,1,0c0.0104-0.00012,0.0208-0.00046,0.0313,0,0.25495,0.0112,0.50987,0.12858,0.6875,0.3125l6.2977,5.7188-6.2977,5.7188c-0.18816,0.18819-0.45346,0.28125-0.71875,0.28125h-1v-1c0-0.26529,0.0931-0.53058,0.28125-0.71875l4.829-4.2812-4.829-4.2812c-0.21074-0.19463-0.30316-0.46925-0.28125-0.75z" fill-rule="nonzero" fill="#bebebe"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/non-starred.svg b/p/themes/icons/non-starred.svg new file mode 100644 index 000000000..4ebcfccbd --- /dev/null +++ b/p/themes/icons/non-starred.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-61.000202,-397)"> +<path style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" fill="#bebebe" d="m69.003,398.01c-0.2659-0.00099-0.49859,0.1006-0.6647,0.2393-0.16611,0.13869-0.27742,0.32137-0.38968,0.50028-0.22453,0.35782-0.39269,0.76873-0.56546,1.2036-0.17277,0.43483-0.34713,0.88766-0.5046,1.2323-0.15747,0.34465-0.3456,0.57871-0.35862,0.58776-0.01287,0.009-0.30722,0.117-0.69576,0.15181-0.38855,0.0348-0.88122,0.0492-1.3632,0.0694-0.48202,0.0202-0.9703,0.0362-1.393,0.12817-0.21138,0.046-0.4154,0.10148-0.60434,0.20956s-0.36689,0.28631-0.45004,0.53002-0.04901,0.50627,0.03636,0.70157c0.08537,0.19531,0.22656,0.36514,0.3682,0.52344,0.2833,0.31663,0.6327,0.60557,1.0079,0.89849s0.77671,0.55926,1.0678,0.81027c0.29104,0.25101,0.45422,0.50796,0.45912,0.52271,0.0048,0.0146,0.03119,0.33498-0.05456,0.70231-0.08575,0.36732-0.2238,0.81174-0.35282,1.2603-0.12902,0.44861-0.25176,0.90196-0.2917,1.3184-0.01997,0.20819-0.04285,0.40729,0.0053,0.61409,0.04814,0.20679,0.1845,0.43007,0.39902,0.58168,0.21451,0.15161,0.44936,0.1881,0.66823,0.1701s0.43535-0.0703,0.63515-0.15132c0.39961-0.16214,0.80177-0.42851,1.2064-0.68231,0.40465-0.2538,0.80822-0.52155,1.1456-0.71107,0.33734-0.18952,0.6484-0.2686,0.66445-0.26854,0.01586,0.00006,0.30338,0.0951,0.63894,0.28732,0.33556,0.19221,0.72532,0.46503,1.1276,0.72205,0.40229,0.25702,0.81996,0.49752,1.218,0.66284,0.19903,0.0827,0.38893,0.15086,0.60762,0.17059,0.2187,0.0197,0.47978-0.031,0.69551-0.18105,0.21572-0.15001,0.33928-0.35235,0.38918-0.55877s0.04291-0.43517,0.02476-0.64358c-0.03632-0.41683-0.15899-0.86394-0.2841-1.3137-0.12511-0.44978-0.26165-0.88661-0.34421-1.2548-0.08256-0.36813-0.07169-0.68662-0.06666-0.70133,0.005-0.0145,0.18746-0.25247,0.4806-0.50101,0.29313-0.24854,0.67599-0.53755,1.0536-0.82731,0.37764-0.28976,0.76716-0.57158,1.0531-0.88579,0.14298-0.1571,0.2418-0.33444,0.32882-0.52904s0.136-0.42874,0.05481-0.67306-0.27108-0.41314-0.45912-0.52272-0.36679-0.18158-0.57782-0.22931c-0.42206-0.0955-0.91359-0.14009-1.3956-0.16426-0.48198-0.0242-0.94584-0.039-1.3342-0.077s-0.71565-0.13122-0.72859-0.14037c-0.01279-0.009-0.18402-0.23636-0.3384-0.58217s-0.32658-0.78819-0.49548-1.2243c-0.1689-0.4361-0.33477-0.86429-0.55609-1.2238-0.11067-0.17977-0.23335-0.35397-0.39826-0.49396s-0.40309-0.25684-0.66899-0.25783zm0.0086,0.99424c0.0422,0.0358,0.10671,0.13602,0.1841,0.26173,0.15477,0.25141,0.33068,0.6272,0.49397,1.0488,0.16329,0.42163,0.31905,0.88645,0.49598,1.2828,0.17693,0.39633,0.31456,0.73379,0.6753,0.98889,0.36072,0.25509,0.77019,0.29763,1.2152,0.34118,0.44506,0.0435,0.92882,0.0532,1.3948,0.0765,0.46599,0.0234,0.91824,0.0697,1.2135,0.13647,0.14763,0.0334,0.25558,0.0572,0.30381,0.0853-0.02227,0.0498-0.07958,0.15478-0.17956,0.26464-0.19997,0.21973-0.53749,0.48746-0.90261,0.7676-0.36511,0.28015-0.77804,0.57219-1.114,0.85704-0.33595,0.28485-0.63992,0.53221-0.77987,0.94209-0.13995,0.40986-0.05396,0.77681,0.04065,1.1987s0.25406,0.87813,0.37502,1.313c0.12096,0.43486,0.22688,0.84692,0.25228,1.1385,0.01156,0.13264-0.01699,0.23485-0.02778,0.29267-0.05993-0.008-0.1764-0.0324-0.30381-0.0853-0.27836-0.11561-0.64649-0.32808-1.0354-0.57657-0.38894-0.24849-0.8039-0.53053-1.1885-0.75081-0.38457-0.22028-0.70791-0.39837-1.1551-0.40015-0.44722-0.002-0.79205,0.17245-1.1786,0.38965-0.38659,0.21719-0.78438,0.49517-1.1756,0.74055-0.39123,0.24538-0.77636,0.4507-1.0559,0.56412-0.13977,0.0567-0.24616,0.0856-0.3023,0.0902-0.01026-0.0578-0.01522-0.16008-0.0025-0.29243,0.02793-0.29118,0.13818-0.70893,0.26291-1.1426,0.12473-0.43372,0.25404-0.89785,0.35232-1.3188,0.09828-0.42099,0.18672-0.78846,0.05027-1.1994-0.13644-0.41097-0.43218-0.64202-0.76571-0.92967-0.33353-0.28766-0.70551-0.58555-1.0683-0.86876-0.36275-0.2832-0.7178-0.56075-0.91597-0.78224-0.09908-0.11075-0.16221-0.21163-0.1841-0.26173,0.04834-0.0276,0.15448-0.058,0.3023-0.0902,0.29562-0.0643,0.74518-0.10714,1.2112-0.1267,0.46603-0.0196,0.94824-0.0298,1.3935-0.0697,0.44531-0.0399,0.84679-0.0499,1.2097-0.30216,0.36289-0.25221,0.5383-0.60511,0.71876-1.0001,0.18046-0.39497,0.33731-0.84115,0.50435-1.2616,0.16704-0.42041,0.31848-0.83556,0.47556-1.0859,0.07853-0.12515,0.16751-0.20007,0.21012-0.23565z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/prev.svg b/p/themes/icons/prev.svg new file mode 100644 index 000000000..ea4372098 --- /dev/null +++ b/p/themes/icons/prev.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-301.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m313.01,749-1,0c-0.0104-0.00012-0.0208-0.00046-0.0313,0-0.25495,0.0112-0.50987,0.12858-0.6875,0.3125l-6.2977,5.7188,6.2977,5.7188c0.18816,0.18819,0.45346,0.28125,0.71875,0.28125h1v-1c0-0.26529-0.0931-0.53058-0.28125-0.71875l-4.829-4.2812,4.829-4.2812c0.21074-0.19463,0.30316-0.46925,0.28125-0.75z" fill-rule="nonzero" fill="#bebebe"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/read.svg b/p/themes/icons/read.svg new file mode 100644 index 000000000..d72c198df --- /dev/null +++ b/p/themes/icons/read.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16.001" width="16"> +<g transform="translate(-60.99995,-296.9989)"> +<path opacity="0.35" style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" fill="#bebebe" d="m68.875,297a1.0001,1.0001,0,0,0,-0.5,0.25l-4.9062,4a1.0001,1.0001,0,0,0,-0.0625,0.0312s-0.32587,0.29728-0.65625,0.75c-0.22334,0.30605-0.3527,0.8316-0.5,1.3125a1.0001,1.0001,0,0,0,-0.03125,0.0312,1.0001,1.0001,0,0,0,-0.21875,0.5625c-0.00051,0.0118,0.00036,0.0195,0,0.0312a1.0001,1.0001,0,0,0,0,0.0312,1.0001,1.0001,0,0,0,0,0.15625v7.8438a1.0001,1.0001,0,0,0,1,1h12a1.0001,1.0001,0,0,0,1,-1v-7.8438a1.0001,1.0001,0,0,0,0,-0.15625,1.0001,1.0001,0,0,0,-0.21875,-0.65625,1.0001,1.0001,0,0,0,-0.03125,-0.0312c-0.32774-1.1879-1.125-2-1.125-2a1.0001,1.0001,0,0,0,-0.0312,-0.0312l-4.969-4.02a1.0001,1.0001,0,0,0,-0.65625,-0.25,1.0001,1.0001,0,0,0,-0.0937,0zm0.125,2.2812,4.3125,3.5312,0.0312,0.0312c0.021,0.0255,0.18032,0.24952,0.34375,0.5l-4.6874,3.5312-4.6875-3.5312c0.0259-0.0394,0.0349-0.0872,0.0625-0.125,0.1908-0.26146,0.31874-0.41421,0.34375-0.4375l0.03125-0.0312,4.25-3.4688zm-5,5.0938,4.6875,3.5312,0.3125,0.21875,0.3125-0.21875,4.6875-3.5312,0,6.625-10,0,0-6.625z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/refresh.svg b/p/themes/icons/refresh.svg new file mode 100644 index 000000000..62fe56c1e --- /dev/null +++ b/p/themes/icons/refresh.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-241.0002,-627)"> +<path style="baseline-shift:baseline;block-progression:tb;color:#666666;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" fill="#666" d="m253.91,628.97a1.0001,1.0001,0,0,0,-0.125,0.0312,1.0001,1.0001,0,0,0,-0.78125,1v1.6875c-0.38225-0.57796-0.84927-1.0822-1.4062-1.5-1.1556-0.86677-2.532-1.2523-3.875-1.1875-0.19186,0.009-0.37223,0.0353-0.5625,0.0625-1.5222,0.21741-2.9782,1.023-3.9688,2.3438-1.9812,2.6414-1.4227,6.425,1.2188,8.4062s6.425,1.4227,8.4062-1.2188a1.0063,1.0063,0,0,0,0.18,-0.59,1.0063,1.0063,0,0,0,0,-0.15625v-0.84375h-0.8125-0.0937a1.0063,1.0063,0,0,0,-0.0937,0,1.0063,1.0063,0,0,0,-0.8125,0.40625c-1.3326,1.7767-3.817,2.1139-5.5938,0.78125-1.7767-1.3326-2.1139-3.817-0.78125-5.5938,1.3326-1.7767,3.817-2.1139,5.5938-0.78125,0.42946,0.32212,0.76954,0.73295,1.0312,1.1875h-1.4375a1.0001,1.0001,0,0,0,-1,1,1.0001,1.0001,0,0,0,0,0.21875v0.78125h0.84375,0.15625,4,1v-1-4a1.0001,1.0001,0,0,0,0,-0.1875v-0.8125h-0.8125a1.0001,1.0001,0,0,0,-0.28125,-0.0312z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/rss.svg b/p/themes/icons/rss.svg new file mode 100644 index 000000000..2a8713be3 --- /dev/null +++ b/p/themes/icons/rss.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g fill-rule="nonzero" transform="translate(-561,-301.00012)" fill="#666"> +<path style="enable-background:new;color:#000000;" d="m325.06,97.188c0,1.7872-0.89543,3.2361-2,3.2361s-2-1.4488-2-3.2361c0-1.7872,0.89543-3.2361,2-3.2361s2,1.4488,2,3.2361z" transform="matrix(1.0000007,0,0,0.61803426,241.93747,252.93479)"/> +<path style="enable-background:new;color:#000000;" d="m563,303,0,1c0,0.55016,0.45347,1,1,1,4.9706,0,9,4.0294,9,9,0,0.55016,0.45347,1,1,1h1v-1c0-6.0751-4.9249-11-11-11h-1zm0,4,0,1c0,0.55016,0.45347,1,1,1,2.7614,0,5,2.2386,5,5,0,0.55016,0.45347,1,1,1h1v-1c0-3.866-3.134-7-7-7h-1z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/search.svg b/p/themes/icons/search.svg new file mode 100644 index 000000000..35a28dfca --- /dev/null +++ b/p/themes/icons/search.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g fill="#666" transform="translate(-441.0004,-195)"> +<path style="baseline-shift:baseline;block-progression:tb;color:#666666;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m447.51,196c-3.0289,0-5.5107,2.479-5.5107,5.5045,0,3.0254,2.4819,5.5045,5.5107,5.5045s5.5107-2.479,5.5107-5.5045c0-3.0254-2.4819-5.5045-5.5107-5.5045zm0,2.0089c1.9474,0,3.4995,1.5504,3.4995,3.4955s-1.5522,3.4955-3.4995,3.4955c-1.9474,0-3.4995-1.5504-3.4995-3.4955,0-1.9452,1.5522-3.4955,3.4995-3.4955z"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#666666;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m450.81,204a1.0001,1.0001,0,0,0,-0.5,1.7188l4,4a1.0055,1.0055,0,1,0,1.4062,-1.4375l-4-4a1.0001,1.0001,0,0,0,-0.91,-0.28z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/share.svg b/p/themes/icons/share.svg new file mode 100644 index 000000000..db7a3649c --- /dev/null +++ b/p/themes/icons/share.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g fill="#bebebe" transform="translate(-581.0002,-196)"> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,148.15963,-64.49107)"/> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,158.12818,-59.49107)"/> +<path style="enable-background:new;color:#000000;" d="m291,178.03c0,1.0873-0.88144,1.9688-1.9688,1.9688-1.0873,0-1.9688-0.88144-1.9688-1.9688,0-1.0873,0.88144-1.9688,1.9688-1.9688,1.0873,0,1.9688,0.88144,1.9688,1.9688z" fill-rule="nonzero" transform="matrix(1.5079365,0,0,1.5079365,158.12818,-69.49107)"/> +<path style="baseline-shift:baseline;block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m593.62,198.16-10.062,4.875-1.8125,0.90625,1.8125,0.90625,10.031,5.0625,0.90625-1.8125-8.2188-4.1562,8.2188-4-0.875-1.7812z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/starred.svg b/p/themes/icons/starred.svg new file mode 100644 index 000000000..63a44908f --- /dev/null +++ b/p/themes/icons/starred.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-41.000202,-397)"> +<path style="enable-background:accumulate;color:#000000;" d="m530.95,186.71c-0.77941,0.55189-3.1576-1.906-4.1125-1.9179-0.95532-0.0119-3.3949,2.3858-4.161,1.8149-0.76573-0.57072,0.83698-3.592,0.55319-4.5039-0.2839-0.91223-3.3182-2.4915-3.0119-3.3965,0.30617-0.90461,3.6749-0.31399,4.4544-0.86567,0.77986-0.5519,1.3442-3.9257,2.2995-3.914,0.95494,0.0116,1.4342,3.398,2.1998,3.9689,0.76588,0.57114,4.1489,0.0653,4.4331,0.97746,0.28402,0.9118-2.7885,2.414-3.0949,3.3186-0.30652,0.90489,1.22,3.966,0.44027,4.5182z" fill-rule="nonzero" transform="matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)" fill="#f1c40f"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/stats.svg b/p/themes/icons/stats.svg new file mode 100644 index 000000000..90a23215f --- /dev/null +++ b/p/themes/icons/stats.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-142,-490)" fill="#bebebe"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="M144.19,490c-1.21,0-2.19,1.02-2.19,2.22v10.562c0,1.2008,0.98238,2.2188,2.1875,2.2188h11.625c1.2051,0,2.1875-1.0179,2.1875-2.2188v-10.562c-0.01-1.2-0.99-2.22-2.2-2.22h-11.625zm0,2,11.625,0c0.12026,0,0.1875,0.08,0.1875,0.21875v10.562c-0.01,0.14-0.08,0.22-0.2,0.22h-11.625c-0.12026,0-0.1875-0.08-0.1875-0.21875v-10.562c0.01-0.14,0.08-0.22,0.2-0.22z"/> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m151.31,493.97a0.50005,0.50005,0,0,0,-0.34375,0.34375l-1.6875,6.0938-0.875-1.1875a0.50005,0.50005,0,0,0,-0.4,-0.21h-2.5-0.5v1h0.5,2.25l1.3125,1.7812a0.50005,0.50005,0,0,0,0.875,-0.15625l1.5312-5.4375,1.0625,3.4688a0.50005,0.50005,0,0,0,0.47,0.35h1.5,0.5v-1h-0.5-1.125l-1.4375-4.6875a0.50005,0.50005,0,0,0,-0.625,-0.34375z"/> +</g> +</svg> diff --git a/p/themes/icons/tag.svg b/p/themes/icons/tag.svg new file mode 100644 index 000000000..0d6db6ed8 --- /dev/null +++ b/p/themes/icons/tag.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-141.0002,-807)"> +<path d="m149,809,0,13,4-4,4,4c0.0525-6.8494-0.0285-10.584,0-13z" fill="#bebebe"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/unread.svg b/p/themes/icons/unread.svg new file mode 100644 index 000000000..dc1cc609c --- /dev/null +++ b/p/themes/icons/unread.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-40.99995,-297)" fill="#e74c3c"> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="m43.781,301a1.0001,1.0001,0,0,0,-0.40625,1.7812l5,4,0.625,0.5,0.625-0.5,5-4a1.0005,1.0005,0,1,0,-1.25,-1.5625l-4.375,3.5-4.375-3.5a1.0001,1.0001,0,0,0,-0.844,-0.22z"/> +<path style="block-progression:tb;color:#000000;direction:ltr;text-indent:0;text-align:start;enable-background:accumulate;text-transform:none;" d="M42.906,300a1.0001,1.0001,0,0,0,-0.906,1v9a1.0001,1.0001,0,0,0,1,1h12a1.0001,1.0001,0,0,0,1,-1v-9a1.0001,1.0001,0,0,0,-1,-1h-12a1.0001,1.0001,0,0,0,-0.09375,0zm1.094,2h10v7h-10v-7z"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/up.svg b/p/themes/icons/up.svg new file mode 100644 index 000000000..4819c9205 --- /dev/null +++ b/p/themes/icons/up.svg @@ -0,0 +1,5 @@ +<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"> +<g transform="translate(-201.0002,-747)"> +<path style="block-progression:tb;color:#bebebe;direction:ltr;text-indent:0;text-align:start;enable-background:new;text-transform:none;" d="m215.03,759,0-1c-0.00091-0.0111,0.00059-0.021-0.00009-0.0312-0.0112-0.25496-0.12835-0.50994-0.31251-0.6875l-5.7188-6.2977-5.7188,6.2977c-0.18821,0.1881-0.28121,0.45346-0.28122,0.71875v1h1c0.26531-0.00007,0.53059-0.0931,0.71873-0.28131l4.2812-4.829,4.2813,4.829c0.19464,0.21073,0.46925,0.30315,0.74998,0.2813z" fill-rule="nonzero" fill="#bebebe"/> +</g> +</svg>
\ No newline at end of file diff --git a/p/themes/icons/view-global.svg b/p/themes/icons/view-global.svg new file mode 100644 index 000000000..d81e79790 --- /dev/null +++ b/p/themes/icons/view-global.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-61-867)" fill="#666" color="#000"><rect height="2" rx=".385" ry=".379" width="2" x="64" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="870"/><rect height="2" rx=".385" ry=".379" width="2" x="64" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="874.02"/><rect height="2" rx=".385" ry=".379" width="2" x="64" y="878"/><rect height="2" rx=".385" ry=".379" width="2" x="68" y="878"/><rect height="2" rx=".385" ry=".379" width="2" x="72" y="878"/></g></svg>
\ No newline at end of file diff --git a/p/themes/icons/view-normal.svg b/p/themes/icons/view-normal.svg new file mode 100644 index 000000000..c35b101df --- /dev/null +++ b/p/themes/icons/view-normal.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-40-746)" fill="#666" color="#666"><path d="m43 749h10v2h-10z"/><path d="m43 753h10v2h-10z"/><path d="m43 757h10v2h-10z"/></g></svg>
\ No newline at end of file diff --git a/p/themes/icons/view-reader.svg b/p/themes/icons/view-reader.svg new file mode 100644 index 000000000..3243aed6e --- /dev/null +++ b/p/themes/icons/view-reader.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g transform="translate(-181-867)" fill="#666" color="#666"><path d="m 181,868 0,1 0,11 0,1 1,0 5,0 c 0.1754,0 0.52538,0.15166 0.8125,0.34375 0.28712,0.19209 0.46875,0.375 0.46875,0.375 L 189,882.4375 l 0.71875,-0.75 c 0,0 0.8963,-0.6875 1.28125,-0.6875 l 5,0 1,0 0,-1 0,-11 0,-1 -1,0 -5,0 c -0.87652,0 -1.56017,0.34756 -2.03125,0.6875 -0.0301,-0.0207 -0.031,-0.0105 -0.0625,-0.0312 C 188.44557,868.35254 187.82811,868 187,868 l -5,0 -1,0 z m 2,2 4,0 c 0.13821,0 0.51476,0.14746 0.8125,0.34375 0.29774,0.19629 0.5,0.375 0.5,0.375 l 0.71875,0.6875 0.6875,-0.71875 c 0,0 0.89975,-0.6875 1.28125,-0.6875 l 4,0 0,9 -4,0 c -0.87693,0 -1.56008,0.34735 -2.03125,0.6875 -0.0196,-0.0135 -0.011,-0.0177 -0.0312,-0.0312 C 188.47725,879.34834 187.83512,879 187,879 l -4,0 0,-9 z"/><g transform="scale(-1 1)"><rect height="2" rx=".375" width="3" x="-187" y="872"/><rect height="2" rx=".375" width="3" x="-187" y="875"/><rect height="2" rx=".375" width="3" x="-194" y="872"/><rect height="2" rx=".375" width="3" x="-194" y="875"/></g></g></svg>
\ No newline at end of file diff --git a/public/data/.gitignore b/public/data/.gitignore deleted file mode 100644 index 315458098..000000000 --- a/public/data/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -favicons -Configuration.array.php -*.sqlite diff --git a/public/favicon.ico b/public/favicon.ico Binary files differdeleted file mode 100644 index f7ae0a5b9..000000000 --- a/public/favicon.ico +++ /dev/null diff --git a/public/favicon.png b/public/favicon.png Binary files differdeleted file mode 100644 index 3038dc3d1..000000000 --- a/public/favicon.png +++ /dev/null diff --git a/public/icons/icon-128.png b/public/icons/icon-128.png Binary files differdeleted file mode 100644 index 41f8eaa0f..000000000 --- a/public/icons/icon-128.png +++ /dev/null diff --git a/public/icons/icon-16.png b/public/icons/icon-16.png Binary files differdeleted file mode 100644 index cc6efb54b..000000000 --- a/public/icons/icon-16.png +++ /dev/null diff --git a/public/icons/icon-256.png b/public/icons/icon-256.png Binary files differdeleted file mode 100644 index 979581a75..000000000 --- a/public/icons/icon-256.png +++ /dev/null diff --git a/public/icons/icon-32.png b/public/icons/icon-32.png Binary files differdeleted file mode 100644 index 00c4ce2a9..000000000 --- a/public/icons/icon-32.png +++ /dev/null diff --git a/public/icons/icon-64.png b/public/icons/icon-64.png Binary files differdeleted file mode 100644 index e2dad000d..000000000 --- a/public/icons/icon-64.png +++ /dev/null diff --git a/public/icons/icon.svg b/public/icons/icon.svg deleted file mode 100644 index 8aefed2ad..000000000 --- a/public/icons/icon.svg +++ /dev/null @@ -1,213 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="256" - height="256" - id="svg2" - version="1.1" - inkscape:version="0.48.4 r9939" - sodipodi:docname="icon.svg" - inkscape:export-filename="/home/cyp/Bureau/FreshRSS.png" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197"> - <title - id="title2991">Logo FreshRSS</title> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0" - inkscape:pageshadow="2" - inkscape:zoom="1.0000001" - inkscape:cx="211.05579" - inkscape:cy="58.65406" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1366" - inkscape:window-height="711" - inkscape:window-x="0" - inkscape:window-y="25" - inkscape:window-maximized="1" - inkscape:snap-global="true" - fit-margin-top="0" - fit-margin-left="0" - fit-margin-right="0" - fit-margin-bottom="0" - inkscape:showpageshadow="false" /> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title>Logo FreshRSS</dc:title> - <dc:date>2013-08-20</dc:date> - <dc:creator> - <cc:Agent> - <dc:title>Cyprien POUZENC</dc:title> - </cc:Agent> - </dc:creator> - <dc:rights> - <cc:Agent> - <dc:title /> - </cc:Agent> - </dc:rights> - <cc:license - rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" /> - </cc:Work> - <cc:License - rdf:about="http://creativecommons.org/licenses/by-sa/3.0/"> - <cc:permits - rdf:resource="http://creativecommons.org/ns#Reproduction" /> - <cc:permits - rdf:resource="http://creativecommons.org/ns#Distribution" /> - <cc:requires - rdf:resource="http://creativecommons.org/ns#Notice" /> - <cc:requires - rdf:resource="http://creativecommons.org/ns#Attribution" /> - <cc:permits - rdf:resource="http://creativecommons.org/ns#DerivativeWorks" /> - <cc:requires - rdf:resource="http://creativecommons.org/ns#ShareAlike" /> - </cc:License> - </rdf:RDF> - </metadata> - <g - inkscape:label="Calque 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(27,-823.3622)"> - <path - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m 101,823.3622 0,23.704 c 57.60125,0 104.2963,46.6944 104.2963,104.296 l 23.7037,0 c 0,-70.6912 -57.30755,-128 -128,-128 z" - id="path2990-22" - inkscape:connector-curvature="0" - inkscape:transform-center-x="-64" - inkscape:transform-center-y="-63.999956" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m 101,866.0294 0,23.704 c 34.0371,0 61.62963,27.592 61.62963,61.6288 l 23.7037,0 c 0,-47.128 -38.20503,-85.3328 -85.33333,-85.3328 z" - id="path2990-3" - inkscape:connector-curvature="0" - inkscape:transform-center-x="-42.666665" - inkscape:transform-center-y="-42.666356" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - sodipodi:type="arc" - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - id="path2990" - sodipodi:cx="-0.088388346" - sodipodi:cy="50" - sodipodi:rx="3.9774756" - sodipodi:ry="3.9774756" - d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z" - transform="matrix(8.3432784,0,0,8.3432808,101.73744,534.199)" - sodipodi:start="4.712389" - sodipodi:end="6.2831853" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" - inkscape:transform-center-x="-16.592584" - inkscape:transform-center-y="-16.591714" /> - <path - style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m 229,951.3622 -23.7037,0 c 0,57.6016 -46.69488,104.296 -104.2963,104.296 l 0,23.704 c 70.6925,0 128,-57.3072 128,-128 z" - id="path2990-22-9" - inkscape:connector-curvature="0" - inkscape:transform-center-x="-64" - inkscape:transform-center-y="64.000044" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m 186.33333,951.3622 -23.7037,0 c 0,34.0368 -27.59253,61.6304 -61.62963,61.6304 l 0,23.704 c 47.12818,0 85.33333,-38.2064 85.33333,-85.3344 z" - id="path2990-3-6" - inkscape:connector-curvature="0" - inkscape:transform-center-x="-42.666665" - inkscape:transform-center-y="42.667244" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m -27,951.3622 23.703712,0 c 0,-57.6016 46.694864,-104.296 104.296288,-104.296 l 0,-23.704 c -70.692496,0 -128,57.3088 -128,128 z" - id="path2990-22-93" - inkscape:connector-curvature="0" - inkscape:transform-center-x="64" - inkscape:transform-center-y="-63.999956" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - d="m 15.666656,951.3622 23.703712,0 c 0,-34.0368 27.592544,-61.6288 61.629632,-61.6288 l 0,-23.704 c -47.128176,0 -85.333344,38.2048 -85.333344,85.3328 z" - id="path2990-3-9" - inkscape:connector-curvature="0" - inkscape:transform-center-x="42.666672" - inkscape:transform-center-y="-42.666356" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" /> - <path - sodipodi:type="arc" - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - id="path2990-1" - sodipodi:cx="-0.088388346" - sodipodi:cy="50" - sodipodi:rx="3.9774756" - sodipodi:ry="3.9774756" - d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z" - transform="matrix(0,-8.3432784,8.3432808,0,-315.97884,950.62475)" - sodipodi:start="4.712389" - sodipodi:end="6.2831853" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" - inkscape:transform-center-x="16.591713" - inkscape:transform-center-y="-16.592581" /> - <path - sodipodi:type="arc" - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - id="path2990-6" - sodipodi:cx="-0.088388346" - sodipodi:cy="50" - sodipodi:rx="3.9774756" - sodipodi:ry="3.9774756" - d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z" - transform="matrix(-8.3432784,0,0,-8.3432808,100.44774,1368.3403)" - sodipodi:start="4.712389" - sodipodi:end="6.2831853" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" - inkscape:transform-center-x="16.592584" - inkscape:transform-center-y="16.591761" /> - <path - sodipodi:type="arc" - style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" - id="path2990-1-6" - sodipodi:cx="-0.088388346" - sodipodi:cy="50" - sodipodi:rx="3.9774756" - sodipodi:ry="3.9774756" - d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z" - transform="matrix(0,8.3432784,-8.3432808,0,518.16402,951.91446)" - sodipodi:start="4.712389" - sodipodi:end="6.2831853" - inkscape:export-xdpi="197" - inkscape:export-ydpi="197" - inkscape:transform-center-x="-16.591713" - inkscape:transform-center-y="16.592578" /> - </g> -</svg> diff --git a/public/index.php b/public/index.php deleted file mode 100755 index 64aabb609..000000000 --- a/public/index.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php -# ***** BEGIN LICENSE BLOCK ***** -# MINZ - A free PHP framework -# Copyright (C) 2011 Marien Fressinaud -# -# 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/>. -# -# ***** END LICENSE BLOCK ***** - -// Constantes de chemins -define ('PUBLIC_PATH', realpath (dirname (__FILE__))); -define ('LIB_PATH', realpath (PUBLIC_PATH . '/../lib')); -define ('APP_PATH', realpath (PUBLIC_PATH . '/../app')); -define ('LOG_PATH', realpath (PUBLIC_PATH . '/../log')); -define ('CACHE_PATH', realpath (PUBLIC_PATH . '/../cache')); - -if (file_exists (PUBLIC_PATH . '/install.php')) { - include ('install.php'); -} else { - set_include_path (get_include_path () - . PATH_SEPARATOR - . LIB_PATH - . PATH_SEPARATOR - . LIB_PATH . '/minz' - . PATH_SEPARATOR - . APP_PATH); - - require (APP_PATH . '/App_FrontController.php'); - - try { - $front_controller = new App_FrontController (); - $front_controller->init (); - $front_controller->run (); - } catch (PDOConnectionException $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - print '### Application problem ###'."\n".'See logs files'; - } -} diff --git a/public/install.php b/public/install.php deleted file mode 100644 index b85dfd8d5..000000000 --- a/public/install.php +++ /dev/null @@ -1,706 +0,0 @@ -<?php - -session_start (); - -if (isset ($_GET['step'])) { - define ('STEP', $_GET['step']); -} else { - define ('STEP', 1); -} - -define ('SQL_REQ_CREATE_DB', 'CREATE DATABASE %s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;'); - -define ('SQL_REQ_CAT', 'CREATE TABLE IF NOT EXISTS `%scategory` ( - `id` varchar(6) NOT NULL, - `name` varchar(255) NOT NULL, - `color` varchar(7) NOT NULL, - PRIMARY KEY (`id`) -);'); - -define ('SQL_REQ_FEED', 'CREATE TABLE IF NOT EXISTS `%sfeed` ( - `id` varchar(6) NOT NULL, - `url` text NOT NULL, - `category` varchar(6) DEFAULT \'000000\', - `name` varchar(255) NOT NULL, - `website` text NOT NULL, - `description` text NOT NULL, - `lastUpdate` int(11) NOT NULL, - `priority` int(2) NOT NULL DEFAULT \'10\', - `pathEntries` varchar(500) DEFAULT NULL, - `httpAuth` varchar(500) DEFAULT NULL, - `error` int(1) NOT NULL DEFAULT \'0\', - `keep_history` int(1) NOT NULL DEFAULT \'0\', - PRIMARY KEY (`id`), - FOREIGN KEY (`category`) REFERENCES %scategory(id) ON DELETE SET NULL ON UPDATE CASCADE -);'); - -define ('SQL_REQ_ENTRY', 'CREATE TABLE IF NOT EXISTS `%sentry` ( - `id` varchar(6) NOT NULL, - `guid` text NOT NULL, - `title` varchar(255) NOT NULL, - `author` varchar(255) NOT NULL, - `content` text NOT NULL, - `link` text NOT NULL, - `date` int(11) NOT NULL, - `is_read` int(11) NOT NULL DEFAULT \'0\', - `is_favorite` int(11) NOT NULL DEFAULT \'0\', - `id_feed` varchar(6) NOT NULL, - `tags` text NOT NULL, - PRIMARY KEY (`id`), - FOREIGN KEY (`id_feed`) REFERENCES %sfeed(id) ON DELETE CASCADE ON UPDATE CASCADE -);'); - - -function writeLine ($f, $line) { - fwrite ($f, $line . "\n"); -} -function writeArray ($f, $array) { - foreach ($array as $key => $val) { - if (is_array ($val)) { - writeLine ($f, '\'' . $key . '\' => array ('); - writeArray ($f, $val); - writeLine ($f, '),'); - } else { - writeLine ($f, '\'' . $key . '\' => \'' . $val . '\','); - } - } -} - -function small_hash ($txt) { - $t = rtrim (base64_encode (hash ('crc32', $txt, true)), '='); - $t = str_replace ('+', '-', $t); // Get rid of characters which need encoding in URLs. - $t = str_replace ('/', '_', $t); - $t = str_replace ('=', '@', $t); - - return $t; -} - -// gestion internationalisation -$translates = array (); -$actual = 'en'; -function initTranslate () { - global $translates; - global $actual; - - $l = getBetterLanguage ('en'); - if (isset ($_SESSION['language'])) { - $l = $_SESSION['language']; - } - $actual = $l; - - $file = APP_PATH . '/i18n/' . $actual . '.php'; - if (file_exists ($file)) { - $translates = include ($file); - } -} -function getBetterLanguage ($fallback) { - $available = availableLanguages (); - $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE']; - $language = strtolower (substr ($accept, 0, 2)); - - if (isset ($available[$language])) { - return $language; - } else { - return $fallback; - } -} -function availableLanguages () { - return array ( - 'en' => 'English', - 'fr' => 'Français' - ); -} -function _t ($key) { - global $translates; - $translate = $key; - if (isset ($translates[$key])) { - $translate = $translates[$key]; - } - - $args = func_get_args (); - unset($args[0]); - - return vsprintf ($translate, $args); -} - -/*** SAUVEGARDES ***/ -function saveLanguage () { - if (!empty ($_POST)) { - if (!isset ($_POST['language'])) { - return false; - } - - $_SESSION['language'] = $_POST['language']; - - header ('Location: index.php?step=1'); - } -} -function saveStep2 () { - if (!empty ($_POST)) { - if (empty ($_POST['sel']) || - empty ($_POST['title']) || - empty ($_POST['old_entries'])) { - return false; - } - - $_SESSION['sel'] = addslashes ($_POST['sel']); - $_SESSION['base_url'] = addslashes ($_POST['base_url']); - $_SESSION['title'] = addslashes ($_POST['title']); - $_SESSION['old_entries'] = $_POST['old_entries']; - if (!is_int (intval ($_SESSION['old_entries'])) || - $_SESSION['old_entries'] < 1) { - $_SESSION['old_entries'] = 3; - } - $_SESSION['mail_login'] = addslashes ($_POST['mail_login']); - - $token = ''; - if ($_SESSION['mail_login']) { - $token = small_hash (time () . $_SESSION['sel']) - . small_hash ($_SESSION['base_url'] . $_SESSION['sel']); - } - - $file_data = PUBLIC_PATH . '/data/Configuration.array.php'; - - $f = fopen ($file_data, 'w'); - writeLine ($f, '<?php'); - writeLine ($f, 'return array ('); - writeArray ($f, array ( - 'language' => $_SESSION['language'], - 'old_entries' => $_SESSION['old_entries'], - 'mail_login' => $_SESSION['mail_login'], - 'token' => $token - )); - writeLine ($f, ');'); - fclose ($f); - - header ('Location: index.php?step=3'); - } -} -function saveStep3 () { - if (!empty ($_POST)) { - if (empty ($_POST['type']) || - empty ($_POST['host']) || - empty ($_POST['user']) || - empty ($_POST['base'])) { - $_SESSION['bd_error'] = true; - } - - $_SESSION['bd_type'] = isset ($_POST['type']) ? $_POST['type'] : 'mysql'; - $_SESSION['bd_host'] = addslashes ($_POST['host']); - $_SESSION['bd_user'] = addslashes ($_POST['user']); - $_SESSION['bd_pass'] = addslashes ($_POST['pass']); - $_SESSION['bd_name'] = addslashes ($_POST['base']); - $_SESSION['bd_prefix'] = addslashes ($_POST['prefix']); - - $file_conf = APP_PATH . '/configuration/application.ini'; - $f = fopen ($file_conf, 'w'); - writeLine ($f, '[general]'); - writeLine ($f, 'environment = "production"'); - writeLine ($f, 'use_url_rewriting = false'); - writeLine ($f, 'sel_application = "' . $_SESSION['sel'] . '"'); - writeLine ($f, 'base_url = "' . $_SESSION['base_url'] . '"'); - writeLine ($f, 'title = "' . $_SESSION['title'] . '"'); - writeLine ($f, '[db]'); - writeLine ($f, 'type = "' . $_SESSION['bd_type'] . '"'); - writeLine ($f, 'host = "' . $_SESSION['bd_host'] . '"'); - writeLine ($f, 'user = "' . $_SESSION['bd_user'] . '"'); - writeLine ($f, 'password = "' . $_SESSION['bd_pass'] . '"'); - writeLine ($f, 'base = "' . $_SESSION['bd_name'] . '"'); - writeLine ($f, 'prefix = "' . $_SESSION['bd_prefix'] . '"'); - fclose ($f); - - $res = checkBD (); - - if ($res) { - $_SESSION['bd_error'] = false; - header ('Location: index.php?step=4'); - } else { - $_SESSION['bd_error'] = true; - } - } -} -function deleteInstall () { - $res = unlink (PUBLIC_PATH . '/install.php'); - if ($res) { - header ('Location: index.php'); - } -} - -/*** VÉRIFICATIONS ***/ -function checkStep () { - $s0 = checkStep0 (); - $s1 = checkStep1 (); - $s2 = checkStep2 (); - $s3 = checkStep3 (); - if (STEP > 0 && $s0['all'] != 'ok') { - header ('Location: index.php?step=0'); - } elseif (STEP > 1 && $s1['all'] != 'ok') { - header ('Location: index.php?step=1'); - } elseif (STEP > 2 && $s2['all'] != 'ok') { - header ('Location: index.php?step=2'); - } elseif (STEP > 3 && $s3['all'] != 'ok') { - header ('Location: index.php?step=3'); - } -} -function checkStep0 () { - $languages = availableLanguages (); - $language = isset ($_SESSION['language']) && - isset ($languages[$_SESSION['language']]); - - return array ( - 'language' => $language ? 'ok' : 'ko', - 'all' => $language ? 'ok' : 'ko' - ); -} -function checkStep1 () { - $php = version_compare (PHP_VERSION, '5.1.0') >= 0; - $minz = file_exists (LIB_PATH . '/minz'); - $curl = extension_loaded ('curl'); - $pdo = extension_loaded ('pdo_mysql'); - $dom = class_exists('DOMDocument'); - $cache = CACHE_PATH && is_writable (CACHE_PATH); - $log = LOG_PATH && is_writable (LOG_PATH); - $conf = APP_PATH && is_writable (APP_PATH . '/configuration'); - $data = is_writable (PUBLIC_PATH . '/data'); - - return array ( - 'php' => $php ? 'ok' : 'ko', - 'minz' => $minz ? 'ok' : 'ko', - 'curl' => $curl ? 'ok' : 'ko', - 'pdo-mysql' => $pdo ? 'ok' : 'ko', - 'dom' => $dom ? 'ok' : 'ko', - 'cache' => $cache ? 'ok' : 'ko', - 'log' => $log ? 'ok' : 'ko', - 'configuration' => $conf ? 'ok' : 'ko', - 'data' => $data ? 'ok' : 'ko', - 'all' => $php && $minz && $curl && $pdo && $dom && $cache && $log && $conf && $data ? 'ok' : 'ko' - ); -} -function checkStep2 () { - $conf = isset ($_SESSION['sel']) && - isset ($_SESSION['base_url']) && - isset ($_SESSION['title']) && - isset ($_SESSION['old_entries']) && - isset ($_SESSION['mail_login']); - $data = file_exists (PUBLIC_PATH . '/data/Configuration.array.php'); - - return array ( - 'conf' => $conf ? 'ok' : 'ko', - 'data' => $data ? 'ok' : 'ko', - 'all' => $conf && $data ? 'ok' : 'ko' - ); -} -function checkStep3 () { - $conf = file_exists (APP_PATH . '/configuration/application.ini'); - $bd = isset ($_SESSION['bd_type']) && - isset ($_SESSION['bd_host']) && - isset ($_SESSION['bd_user']) && - isset ($_SESSION['bd_pass']) && - isset ($_SESSION['bd_name']); - $conn = !isset ($_SESSION['bd_error']) || !$_SESSION['bd_error']; - - return array ( - 'bd' => $bd ? 'ok' : 'ko', - 'conn' => $conn ? 'ok' : 'ko', - 'conf' => $conf ? 'ok' : 'ko', - 'all' => $bd && $conn && $conf ? 'ok' : 'ko' - ); -} -function checkBD () { - $error = false; - - try { - $str = ''; - $driver_options = null; - if($_SESSION['bd_type'] == 'mysql') { - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' - ); - - // on ouvre une connexion juste pour créer la base si elle n'existe pas - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';'; - $c = new PDO ($str, - $_SESSION['bd_user'], - $_SESSION['bd_pass'], - $driver_options); - - $sql = sprintf (SQL_REQ_CREATE_DB, $_SESSION['bd_name']); - $res = $c->query ($sql); - - // on écrase la précédente connexion en sélectionnant la nouvelle BDD - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_name']; - } elseif($_SESSION['bd_type'] == 'sqlite') { - $str = 'sqlite:' . PUBLIC_PATH - . '/data/' . $_SESSION['bd_name'] . '.sqlite'; - } - - $c = new PDO ($str, - $_SESSION['bd_user'], - $_SESSION['bd_pass'], - $driver_options); - - $sql = sprintf (SQL_REQ_CAT, $_SESSION['bd_prefix']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } - - $sql = sprintf (SQL_REQ_FEED, $_SESSION['bd_prefix'], $_SESSION['bd_prefix']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } - - $sql = sprintf (SQL_REQ_ENTRY, $_SESSION['bd_prefix'], $_SESSION['bd_prefix']); - $res = $c->query ($sql); - - if (!$res) { - $error = true; - } - } catch (PDOException $e) { - $error = true; - } - - if ($error && file_exists (APP_PATH . '/configuration/application.ini')) { - unlink (APP_PATH . '/configuration/application.ini'); - } - - return !$error; -} - -/*** AFFICHAGE ***/ -function printStep0 () { - global $actual; -?> - <?php $s0 = checkStep0 (); if ($s0['all'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('language_defined'); ?></p> - <?php } ?> - - <form action="index.php?step=0" method="post"> - <legend><?php echo _t ('choose_language'); ?></legend> - <div class="form-group"> - <label class="group-name" for="language"><?php echo _t ('language'); ?></label> - <div class="group-controls"> - <select name="language" id="language"> - <?php $languages = availableLanguages (); ?> - <?php foreach ($languages as $short => $lib) { ?> - <option value="<?php echo $short; ?>"<?php echo $actual == $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> - <?php } ?> - </select> - </div> - </div> - - <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> - <?php if ($s0['all'] == 'ok') { ?> - <a class="btn btn-important next-step" href="?step=1"><?php echo _t ('next_step'); ?></a> - <?php } ?> - </div> - </div> - </form> -<?php -} - -function printStep1 () { - $res = checkStep1 (); -?> - <noscript><p class="alert alert-warn"><span class="alert-head"><?php echo _t ('attention'); ?></span> <?php echo _t ('javascript_is_better'); ?></p></noscript> - - <?php if ($res['php'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('php_is_ok', PHP_VERSION); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('php_is_nok', PHP_VERSION, '5.1.0'); ?></p> - <?php } ?> - - <?php if ($res['minz'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('minz_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('minz_is_nok', LIB_PATH . '/minz'); ?></p> - <?php } ?> - - <?php if ($res['curl'] == 'ok') { ?> - <?php $version = curl_version(); ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('curl_is_ok', $version['version']); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('curl_is_nok'); ?></p> - <?php } ?> - - <?php if ($res['pdo-mysql'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pdomysql_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pdomysql_is_nok'); ?></p> - <?php } ?> - - <?php if ($res['dom'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('dom_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('dom_is_nok'); ?></p> - <?php } ?> - - <?php if ($res['cache'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('cache_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', PUBLIC_PATH . '/../cache'); ?></p> - <?php } ?> - - <?php if ($res['log'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('log_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', PUBLIC_PATH . '/../log'); ?></p> - <?php } ?> - - <?php if ($res['configuration'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('conf_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', APP_PATH . '/configuration'); ?></p> - <?php } ?> - - <?php if ($res['data'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('data_is_ok'); ?></p> - <?php } else { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', PUBLIC_PATH . '/data'); ?></p> - <?php } ?> - - <?php if ($res['all'] == 'ok') { ?> - <a class="btn btn-important next-step" href="?step=2"><?php echo _t ('next_step'); ?></a> - <?php } else { ?> - <p class="alert alert-error"><?php echo _t ('fix_errors_before'); ?></p> - <?php } ?> -<?php -} - -function printStep2 () { -?> - <?php $s2 = checkStep2 (); if ($s2['all'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('general_conf_is_ok'); ?></p> - <?php } ?> - - <form action="index.php?step=2" method="post"> - <legend><?php echo _t ('general_configuration'); ?></legend> - <div class="form-group"> - <label class="group-name" for="sel"><?php echo _t ('random_string'); ?></label> - <div class="group-controls"> - <input type="text" id="sel" name="sel" value="<?php echo isset ($_SESSION['sel']) ? $_SESSION['sel'] : '123~abcdefghijklmnopqrstuvwxyz~321'; ?>" /> <i class="icon i_help"></i> <?php echo _t ('change_value'); ?> - </div> - </div> - - <?php - $url = substr ($_SERVER['PHP_SELF'], 0, -10); - ?> - <div class="form-group"> - <label class="group-name" for="base_url"><?php echo _t ('base_url'); ?></label> - <div class="group-controls"> - <input type="text" id="base_url" name="base_url" value="<?php echo isset ($_SESSION['base_url']) ? $_SESSION['base_url'] : $url; ?>" /> <i class="icon i_help"></i> <?php echo _t ('do_not_change_if_doubt'); ?> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="title"><?php echo _t ('title'); ?></label> - <div class="group-controls"> - <input type="text" id="title" name="title" value="<?php echo isset ($_SESSION['title']) ? $_SESSION['title'] : _t ('freshrss'); ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="old_entries"><?php echo _t ('delete_articles_every'); ?></label> - <div class="group-controls"> - <input type="number" id="old_entries" name="old_entries" value="<?php echo isset ($_SESSION['old_entries']) ? $_SESSION['old_entries'] : '3'; ?>" /> <?php echo _t ('month'); ?> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="mail_login"><?php echo _t ('persona_connection_email'); ?></label> - <div class="group-controls"> - <input type="email" id="mail_login" name="mail_login" value="<?php echo isset ($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="<?php echo _t ('blank_to_disable'); ?>" /> - <noscript><b><?php echo _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 _t ('save'); ?></button> - <button type="reset" class="btn"><?php echo _t ('cancel'); ?></button> - <?php if ($s2['all'] == 'ok') { ?> - <a class="btn btn-important next-step" href="?step=3"><?php echo _t ('next_step'); ?></a> - <?php } ?> - </div> - </div> - </form> -<?php -} - -function printStep3 () { -?> - <?php $s3 = checkStep3 (); if ($s3['all'] == 'ok') { ?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('bdd_conf_is_ok'); ?></p> - <?php } elseif ($s3['conn'] == 'ko') { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('bdd_conf_is_ko'); ?></p> - <?php } ?> - - <form action="index.php?step=3" method="post"> - <legend><?php echo _t ('bdd_configuration'); ?></legend> - <!-- - TODO : l'utilisation de SQLite n'est pas encore possible. Pour tester tout de même, décommentez ce bloc - <div class="form-group"> - <label class="group-name" for="type"><?php echo _t ('bdd_type'); ?></label> - <div class="group-controls"> - <select name="type" id="type"> - <option value="mysql" - <?php echo $_SESSION['bd_type'] && $_SESSION['bd_type'] == 'mysql' ? 'selected="selected"' : ''; ?>> - MySQL - </option> - <option value="sqlite" - <?php echo $_SESSION['bd_type'] && $_SESSION['bd_type'] == 'sqlite' ? 'selected="selected"' : ''; ?>> - SQLite - </option> - </select> - </div> - </div> - --> - - <div class="form-group"> - <label class="group-name" for="host"><?php echo _t ('host'); ?></label> - <div class="group-controls"> - <input type="text" id="host" name="host" value="<?php echo isset ($_SESSION['bd_host']) ? $_SESSION['bd_host'] : 'localhost'; ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="user"><?php echo _t ('username'); ?></label> - <div class="group-controls"> - <input type="text" id="user" name="user" value="<?php echo isset ($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="pass"><?php echo _t ('password'); ?></label> - <div class="group-controls"> - <input type="password" id="pass" name="pass" value="<?php echo isset ($_SESSION['bd_pass']) ? $_SESSION['bd_pass'] : ''; ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="base"><?php echo _t ('bdd'); ?></label> - <div class="group-controls"> - <input type="text" id="base" name="base" value="<?php echo isset ($_SESSION['bd_name']) ? $_SESSION['bd_name'] : ''; ?>" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="prefix"><?php echo _t ('prefix'); ?></label> - <div class="group-controls"> - <input type="text" id="prefix" name="prefix" value="<?php echo isset ($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" /> - </div> - </div> - - <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> - <?php if ($s3['all'] == 'ok') { ?> - <a class="btn btn-important next-step" href="?step=4"><?php echo _t ('next_step'); ?></a> - <?php } ?> - </div> - </div> - </form> -<?php -} - -function printStep4 () { -?> - <p class="alert alert-success"><span class="alert-head"><?php echo _t ('congratulations'); ?></span> <?php echo _t ('installation_is_ok'); ?></p> - <a class="btn btn-important next-step" href="?step=5"><?php echo _t ('finish_installation'); ?></a> -<?php -} - -function printStep5 () { -?> - <p class="alert alert-error"><span class="alert-head"><?php echo _t ('oops'); ?></span> <?php echo _t ('install_not_deleted', PUBLIC_PATH . '/install.php'); ?></p> -<?php -} - -initTranslate (); - -checkStep (); - -switch (STEP) { -case 0: -default: - saveLanguage (); - break; -case 1: - break; -case 2: - saveStep2 (); - break; -case 3: - saveStep3 (); - break; -case 4: - break; -case 5: - deleteInstall (); - break; -} -?> -<!DOCTYPE html> -<html lang="fr"> - <head> - <meta charset="utf-8"> - <meta name="viewport" content="initial-scale=1.0"> - <title><?php echo _t ('freshrss_installation'); ?></title> - <link rel="stylesheet" type="text/css" media="all" href="themes/default/global.css" /> - <link rel="stylesheet" type="text/css" media="all" href="themes/default/freshrss.css" /> - </head> - <body> - -<div class="header"> - <div class="item title"> - <h1><a href="index.php"><?php echo _t ('freshrss'); ?></a></h1> - <h2><?php echo _t ('installation_step', STEP); ?></h2> - </div> -</div> - -<div id="global"> - <ul class="nav nav-list aside"> - <li class="nav-header"><?php echo _t ('steps'); ?></li> - <li class="item<?php echo STEP == 0 ? ' active' : ''; ?>"><a href="?step=0"><?php echo _t ('language'); ?></a></li> - <li class="item<?php echo STEP == 1 ? ' active' : ''; ?>"><a href="?step=1"><?php echo _t ('checks'); ?></a></li> - <li class="item<?php echo STEP == 2 ? ' active' : ''; ?>"><a href="?step=2"><?php echo _t ('general_configuration'); ?></a></li> - <li class="item<?php echo STEP == 3 ? ' active' : ''; ?>"><a href="?step=3"><?php echo _t ('bdd_configuration'); ?></a></li> - <li class="item<?php echo STEP == 4 ? ' active' : ''; ?>"><a href="?step=4"><?php echo _t ('this_is_the_end'); ?></a></li> - </ul> - - <div class="post"> - <?php - switch (STEP) { - case 0: - default: - printStep0 (); - break; - case 1: - printStep1 (); - break; - case 2: - printStep2 (); - break; - case 3: - printStep3 (); - break; - case 4: - printStep4 (); - break; - case 5: - printStep5 (); - break; - } - ?> - </div> -</div> - </body> -</html> - diff --git a/public/scripts/endless_mode.js b/public/scripts/endless_mode.js deleted file mode 100644 index b56614a55..000000000 --- a/public/scripts/endless_mode.js +++ /dev/null @@ -1,44 +0,0 @@ -var url_load_more = ""; -var load_more = false; -var container = null; - -function init_load_more(block) { - var next_link = $("a#load_more") - if (!next_link.length) { - // no more article to load - url_load_more = ""; - return; - } - - url_load_more = next_link.attr("href"); - container = block; - - $("#load_more").click (function () { - load_more_posts (); - - return false; - }); -} - -function load_more_posts () { - if(load_more == true || url_load_more == "") { - return; - } - - load_more = true; - $("#load_more").addClass("loading"); - $.get (url_load_more, function (data) { - container.children(".flux:last").after($("#stream .flux", data)); - $(".pagination").html($(".pagination", data).html()); - - init_load_more(container); - init_posts(); - - $("#load_more").removeClass("loading"); - load_more = false; - }); -} - -$(document).ready (function () { - init_load_more($("#stream")); -});
\ No newline at end of file diff --git a/public/scripts/global_view.js b/public/scripts/global_view.js deleted file mode 100644 index bc2254011..000000000 --- a/public/scripts/global_view.js +++ /dev/null @@ -1,58 +0,0 @@ -var panel_loading = false; - -function load_panel(link) { - if(panel_loading) { - return; - } - - panel_loading = true; - - $.get (link, function (data) { - $("#panel").append($(".nav_menu, #stream .day, #stream .flux, #stream .pagination", data)); - - $("#panel .nav_menu").children().not("#nav_menu_read_all").remove(); - - init_load_more($("#panel")); - init_posts(); - - $("#overlay").fadeIn(); - $("#panel").slideToggle(); - - // force le démarrage du scroll en haut. - // Sans ça, si l'on scroll en lisant une catégorie par exemple, - // en en ouvrant une autre ensuite, on se retrouve au même point de scroll - $("#panel").scrollTop (0); - - panel_loading = false; - }); -} - -function init_close_panel() { - $("#panel .close").click(function() { - $("#panel").html('<a class="close" href="#"><i class="icon i_close"></i></a>'); - - init_close_panel(); - $("#panel").slideToggle(); - $("#overlay").fadeOut(); - }); -} - -function init_global_view() { - $("#stream .category a").click(function() { - var link = $(this).attr("href"); - - load_panel(link); - - return false; - }); - - $(".nav_menu #nav_menu_read_all, .nav_menu .toggle_aside").remove(); - - init_stream_delegates($("#panel")); -} - - -$(document).ready (function () { - init_global_view(); - init_close_panel(); -});
\ No newline at end of file diff --git a/public/scripts/jquery.lazyload.min.js b/public/scripts/jquery.lazyload.min.js deleted file mode 100644 index 8dd097dc3..000000000 --- a/public/scripts/jquery.lazyload.min.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Lazy Load - jQuery plugin for lazy loading images - * - * Copyright (c) 2007-2013 Mika Tuupola - * - * Licensed under the MIT license: - * http://www.opensource.org/licenses/mit-license.php - * - * Project home: - * http://www.appelsiini.net/projects/lazyload - * - * Version: 1.9.0 - * - */ -!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!0,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("<img />").bind("load",function(){var d=c.data(j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.data(j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/iphone|ipod|ipad.*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);
\ No newline at end of file diff --git a/public/scripts/jquery.min.js b/public/scripts/jquery.min.js deleted file mode 100644 index da4170647..000000000 --- a/public/scripts/jquery.min.js +++ /dev/null @@ -1,6 +0,0 @@ -/*! jQuery v1.10.2 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license -//@ sourceMappingURL=jquery-1.10.2.min.map -*/ -(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.2",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=st(),k=st(),E=st(),S=!1,A=function(e,t){return e===t?(S=!0,0):0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=mt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+yt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,n,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function lt(e){return e[b]=!0,e}function ut(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function ct(e,t){var n=e.split("|"),r=e.length;while(r--)o.attrHandle[n[r]]=t}function pt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function dt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return lt(function(t){return t=+t,lt(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.defaultView;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.attachEvent&&i!==i.top&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),r.getElementsByTagName=ut(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ut(function(e){return e.innerHTML="<div class='a'></div><div class='a i'></div>",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ut(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=K.test(n.querySelectorAll))&&(ut(function(e){e.innerHTML="<select><option selected=''></option></select>",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ut(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=K.test(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ut(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=K.test(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return pt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?pt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:lt,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=mt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?lt(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:lt(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?lt(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:lt(function(e){return function(t){return at(e,t).length>0}}),contains:lt(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:lt(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},o.pseudos.nth=o.pseudos.eq;for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=ft(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=dt(n);function gt(){}gt.prototype=o.filters=o.pseudos,o.setFilters=new gt;function mt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function yt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function vt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function bt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function wt(e,t,n,r,i,o){return r&&!r[b]&&(r=wt(r)),i&&!i[b]&&(i=wt(i,o)),lt(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||Nt(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:xt(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=xt(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=xt(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function Tt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=vt(function(e){return e===t},s,!0),p=vt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[vt(bt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return wt(l>1&&bt(f),l>1&&yt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&Tt(e.slice(l,r)),i>r&&Tt(e=e.slice(r)),i>r&&yt(e))}f.push(n)}return bt(f)}function Ct(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=xt(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?lt(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=mt(e)),n=t.length;while(n--)o=Tt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Ct(i,r))}return o};function Nt(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function kt(e,t,n,i){var a,s,u,c,p,f=mt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&yt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}r.sortStable=b.split("").sort(A).join("")===b,r.detectDuplicates=S,p(),r.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(f.createElement("div"))}),ut(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||ct("type|href|height|width",function(e,n,r){return r?t:e.getAttribute(n,"type"===n.toLowerCase()?1:2)}),r.attributes&&ut(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||ct("value",function(e,n,r){return r||"input"!==e.nodeName.toLowerCase()?t:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||ct(B,function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&i.specified?i.value:e[n]===!0?n.toLowerCase():null}),x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!l||i&&!u||(t=t||[],t=[e,t.slice?t.slice():t],n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav></:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="<div></div>",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null)}),n=s=l=u=r=o=null,t -}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,r=0,o=x(this),a=e.match(T)||[];while(t=a[r++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/<tbody/i,wt=/<|&#?\w+;/,Tt=/<(?:script|style|link)/i,Ct=/^(?:checkbox|radio)$/i,Nt=/checked\s*(?:[^=]|=\s*.checked.)/i,kt=/^$|\/(?:java|ecma)script/i,Et=/^true\/(.*)/,St=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,At={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],area:[1,"<map>","</map>"],param:[1,"<object>","</object>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X<div>","</div>"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1></$2>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1></$2>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?"<table>"!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); -u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){nn(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("<iframe frameborder='0' width='0' height='0'/>").css("cssText","display:block !important")).appendTo(t.documentElement),t=(Pt[0].contentWindow||Pt[0].contentDocument).document,t.write("<!doctype html><html><body>"),t.close(),n=un(e,t),Pt.detach()),Gt[e]=n),n}function un(e,t){var n=x(t.createElement(e)).appendTo(t.body),r=x.css(n[0],"display");return n.remove(),r}x.each(["height","width"],function(e,n){x.cssHooks[n]={get:function(e,r,i){return r?0===e.offsetWidth&&Xt.test(x.css(e,"display"))?x.swap(e,Qt,function(){return sn(e,n,i)}):sn(e,n,i):t},set:function(e,t,r){var i=r&&Rt(e);return on(e,t,r?an(e,n,r,x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,i),i):0)}}}),x.support.opacity||(x.cssHooks.opacity={get:function(e,t){return It.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=x.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===x.trim(o.replace($t,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=$t.test(o)?o.replace($t,i):o+" "+i)}}),x(function(){x.support.reliableMarginRight||(x.cssHooks.marginRight={get:function(e,n){return n?x.swap(e,{display:"inline-block"},Wt,[e,"marginRight"]):t}}),!x.support.pixelPosition&&x.fn.position&&x.each(["top","left"],function(e,n){x.cssHooks[n]={get:function(e,r){return r?(r=Wt(e,n),Yt.test(r)?x(e).position()[n]+"px":r):t}}})}),x.expr&&x.expr.filters&&(x.expr.filters.hidden=function(e){return 0>=e.offsetWidth&&0>=e.offsetHeight||!x.support.reliableHiddenOffsets&&"none"===(e.style&&e.style.display||x.css(e,"display"))},x.expr.filters.visible=function(e){return!x.expr.filters.hidden(e)}),x.each({margin:"",padding:"",border:"Width"},function(e,t){x.cssHooks[e+t]={expand:function(n){var r=0,i={},o="string"==typeof n?n.split(" "):[n];for(;4>r;r++)i[e+Zt[r]+t]=o[r]||o[r-2]||o[0];return i}},Ut.test(e)||(x.cssHooks[e+t].set=on)});var cn=/%20/g,pn=/\[\]$/,fn=/\r?\n/g,dn=/^(?:submit|button|image|reset|file)$/i,hn=/^(?:input|select|textarea|keygen)/i;x.fn.extend({serialize:function(){return x.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=x.prop(this,"elements");return e?x.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!x(this).is(":disabled")&&hn.test(this.nodeName)&&!dn.test(e)&&(this.checked||!Ct.test(e))}).map(function(e,t){var n=x(this).val();return null==n?null:x.isArray(n)?x.map(n,function(e){return{name:t.name,value:e.replace(fn,"\r\n")}}):{name:t.name,value:n.replace(fn,"\r\n")}}).get()}}),x.param=function(e,n){var r,i=[],o=function(e,t){t=x.isFunction(t)?t():null==t?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(n===t&&(n=x.ajaxSettings&&x.ajaxSettings.traditional),x.isArray(e)||e.jquery&&!x.isPlainObject(e))x.each(e,function(){o(this.name,this.value)});else for(r in e)gn(r,e[r],n,o);return i.join("&").replace(cn,"+")};function gn(e,t,n,r){var i;if(x.isArray(t))x.each(t,function(t,i){n||pn.test(e)?r(e,i):gn(e+"["+("object"==typeof i?t:"")+"]",i,n,r)});else if(n||"object"!==x.type(t))r(e,t);else for(i in t)gn(e+"["+i+"]",t[i],n,r)}x.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){x.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),x.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)},bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)}});var mn,yn,vn=x.now(),bn=/\?/,xn=/#.*$/,wn=/([?&])_=[^&]*/,Tn=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Cn=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Nn=/^(?:GET|HEAD)$/,kn=/^\/\//,En=/^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,Sn=x.fn.load,An={},jn={},Dn="*/".concat("*");try{yn=o.href}catch(Ln){yn=a.createElement("a"),yn.href="",yn=yn.href}mn=En.exec(yn.toLowerCase())||[];function Hn(e){return function(t,n){"string"!=typeof t&&(n=t,t="*");var r,i=0,o=t.toLowerCase().match(T)||[];if(x.isFunction(n))while(r=o[i++])"+"===r[0]?(r=r.slice(1)||"*",(e[r]=e[r]||[]).unshift(n)):(e[r]=e[r]||[]).push(n)}}function qn(e,n,r,i){var o={},a=e===jn;function s(l){var u;return o[l]=!0,x.each(e[l]||[],function(e,l){var c=l(n,r,i);return"string"!=typeof c||a||o[c]?a?!(u=c):t:(n.dataTypes.unshift(c),s(c),!1)}),u}return s(n.dataTypes[0])||!o["*"]&&s("*")}function _n(e,n){var r,i,o=x.ajaxSettings.flatOptions||{};for(i in n)n[i]!==t&&((o[i]?e:r||(r={}))[i]=n[i]);return r&&x.extend(!0,e,r),e}x.fn.load=function(e,n,r){if("string"!=typeof e&&Sn)return Sn.apply(this,arguments);var i,o,a,s=this,l=e.indexOf(" ");return l>=0&&(i=e.slice(l,e.length),e=e.slice(0,l)),x.isFunction(n)?(r=n,n=t):n&&"object"==typeof n&&(a="POST"),s.length>0&&x.ajax({url:e,type:a,dataType:"html",data:n}).done(function(e){o=arguments,s.html(i?x("<div>").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window); diff --git a/public/scripts/notification.js b/public/scripts/notification.js deleted file mode 100644 index e4f9fb649..000000000 --- a/public/scripts/notification.js +++ /dev/null @@ -1,17 +0,0 @@ -function closeNotification () { - $(".notification").slideUp (200, function () { - $(".notification").remove (); - }); -} - -$(document).ready (function () { - notif = $(".notification"); - if (notif[0] !== undefined) { - timer = setInterval('closeNotification()', 5000); - - notif.find ("a.close").click (function () { - closeNotification (); - return false; - }); - } -}); diff --git a/public/themes/default/freshrss.css b/public/themes/default/freshrss.css deleted file mode 100644 index e464b9fe4..000000000 --- a/public/themes/default/freshrss.css +++ /dev/null @@ -1,811 +0,0 @@ -/* STRUCTURE */ -.header { - display: table; - width: 100%; - background: #f4f4f4; - table-layout: fixed; -} - .header > .item { - display: table-cell; - padding: 10px 0; - border-bottom: 1px solid #aaa; - vertical-align: middle; - text-align: center; - } - .header > .item.title { - width: 250px; - } - .header > .item.title .logo { - display: inline-block; - padding: 10px; - } - .header > .item.title h1 { - display: inline-block; - margin: 0; - text-shadow: 1px -1px 0 #ccc; - vertical-align: middle; - } - .header > .item.title a:hover { - text-decoration: none; - } - .header > .item.search input { - width: 200px; - transition: width 200ms linear; - } - .header .item.search input:focus { - width: 300px; - } - .header > .item.configure { - width: 100px; - } - -#global { - display: table; - width: 100%; - height: 100%; - background: #fafafa; - table-layout: fixed; -} - .aside { - display: table-cell; - height: 100%; - width: 250px; - vertical-align: top; - border-right: 1px solid #aaa; - background: #fff; - } - .aside .nav-form input { - width: 180px; - } - .aside.aside_flux { - padding: 10px 0 40px; - } - .aside.aside_feed .nav-form input { - width: 140px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu { - right: -20px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { - right: 33px; - } - - .nav-login { - display: none; - } - - .nav_menu { - width: 100%; - background: #fafafa; - border-bottom: 1px solid #aaa; - text-align: center; - padding: 5px 0; - } - -.favicon { - height: 16px; - width: 16px; -} - -.categories { - margin: 0; - padding: 0; - text-align: center; - list-style: none; -} - .categories .all, - .categories .favorites, - .categories .category { - display: block; - padding: 5px 0; - width: 220px; - margin: 5px auto; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .categories .all .btn, - .categories .favorites .btn, - .categories .category .btn:first-child { - width: 195px; - position: relative; - } - .categories .feeds { - width: 100%; - margin: 0; - list-style: none; - } - .categories .feeds .item.active { - background: #0062BE; - } - .categories .feeds .item.active .feed { - color: #fff; - } - .categories .feeds .item.empty .feed { - color: #e67e22; - } - .categories .feeds .item.empty.active { - background: #e67e22; - } - .categories .feeds .item.empty.active .feed { - color: #fff; - } - .categories .feeds .item.error .feed { - color: #BD362F; - } - .categories .feeds .item .feed { - display: inline-block; - margin: 0; - width: 165px; - line-height: 35px; - font-size: 90%; - vertical-align: middle; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .categories .feeds .dropdown .dropdown-menu { - left: 0; - } - .categories .feeds .dropdown .dropdown-menu:after { - left: 2px; - } - .categories .feeds .item .dropdown-toggle i { - background-image: none; - } - .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle i, - .categories .feeds .item:hover .dropdown-toggle i, - .categories .feeds .item.active .dropdown-toggle i { - background-image: url("icons/configure.png"); - background-image: url("icons/configure.svg"); - background-color: #fff; - border-radius: 3px; - } - .categories .notRead { - position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; - background: #ccc; - color: #fff; - font-size: 90%; - border: 1px solid #bbb; - border-radius: 5px; - box-shadow: 1px 3px 3px #aaa inset; - text-shadow: 0 0 1px #aaa; - } - -.post { - padding: 10px 50px; -} - .post form { - margin: 10px 0; - } - -.day { - min-height: 50px; - padding: 0 10px; - font-size: 130%; - font-weight: bold; - line-height: 50px; - background: #fff; - border-top: 1px solid #aaa; - border-bottom: 1px solid #aaa; -} - .day:first-child { - border-top: none; - } - .day .name { - position: absolute; - right: 0; - width: 50%; - height: 1.5em; - padding: 0 10px 0 0; - overflow: hidden; - color: #aab; - font-size: 1.8em; - opacity: .3; - text-shadow: 0px -1px 0px #333; - font-style: italic; - white-space: nowrap; - text-overflow: ellipsis; - text-align: right; - } - -.flux { - border-left: 3px solid #aaa; - background: #fafafa; -} - .flux:hover { - background: #fff; - } - .flux.active { - border-left: 3px solid #0062BE; - background: #fff; - } - .flux.not_read { - border-left: 3px solid #FF5300; - background: #FFF3ED; - } - .flux.favorite { - border-left: 3px solid #FFC300; - background: #FFF6DA; - } - - .flux_header { - height: 25px; - font-size: 12px; - line-height: 25px; - border-top: 1px solid #ddd; - } - .item.manage { - width: 80px; - white-space: nowrap; - font-size: 0px; - text-align: center; - } - .item.manage .read { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/read.png") center center no-repeat; - background: url("icons/read.svg") center center no-repeat; - vertical-align: middle; - } - .item.manage .read:hover { - text-decoration: none; - } - .flux.not_read .item.manage .read { - background: url("icons/unread.png") center center no-repeat; - background: url("icons/unread.svg") center center no-repeat; - } - .item.manage .bookmark { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/non-starred.png") center center no-repeat; - background: url("icons/non-starred.svg") center center no-repeat; - vertical-align: middle; - } - .item.manage .bookmark:hover { - text-decoration: none; - } - .flux.favorite .item.manage .bookmark { - background: url("icons/starred.png") center center no-repeat; - background: url("icons/starred.svg") center center no-repeat; - } - .flux_header .item.website { - width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 40px; - } - .flux_header .item.website .favicon { - padding: 5px; - } - .flux_header .item.website a { - display: block; - height: 40px; - } - .flux_header .item.title { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - } - .flux_header .item.title a { - color: #000; - outline: none; - } - .flux_header .item.title a:hover { - text-decoration: none - } - .flux.not_read .flux_header .item.title { - font-weight: bold; - } - .item.date { - width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - text-align: right; - font-size: 10px; - color: #666; - cursor: pointer; - } - .item.link { - width: 40px; - text-align: center; - } - .item.link a { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/link.png") center center no-repeat; - background: url("icons/link.svg") center center no-repeat; - vertical-align: middle; - } - .item.link a:hover { - text-decoration: none; - } - -#stream.reader .flux { - padding: 0 0 30px; - border: none; - background: #f0f0f0; - color: #333; -} - #stream.reader .flux .author { - margin: 0 0 10px; - font-size: 90%; - color: #666; - } - -#stream.global { - text-align: center; -} - #stream.global .category { - display: inline-block; - width: 280px; - margin: 20px 10px; - vertical-align: top; - background: #fff; - border: 1px solid #aaa; - border-radius: 5px; - text-align: left; - box-shadow: 0 0 5px #bbb; - } - #stream.global .cat_header { - height: 35px; - padding: 0 10px; - background: #eee; - border-bottom: 1px solid #aaa; - border-radius: 5px 5px 0 0; - line-height: 35px; - font-size: 120%; - } - #stream.global .cat_header a { - color: #333; - text-shadow: 0 -1px 0px #aaa; - } - #stream.global .category .feeds { - max-height: 250px; - margin: 0; - list-style: none; - overflow: auto; - } - #stream.global .category .feeds .item { - padding: 2px 10px; - font-size: 90%; - } - -.content { - min-height: 300px; - max-width: 550px; - margin: 0 auto; - padding: 20px 10px; - line-height: 170%; - word-wrap: break-word; -} - .content .title { - margin: 0 0 5px; - } - .content h1, .content h2, .content h3 { - margin: 20px 0 5px; - } - - .content p { - margin: 0 0 20px; - } - .content img.big { - display: block; - margin: 10px auto; - } - .content figure img.big { - margin: 0; - } - .content hr { - margin: 30px 0; - height: 1px; - background: #ddd; - border: 0; - } - .content pre { - margin: 10px auto; - padding: 10px; - overflow: auto; - background: #000; - color: #fff; - font-size: 110%; - } - .content q, .content blockquote { - display: block; - margin: 5px 0; - padding: 5px 20px; - font-style: italic; - border-left: 4px solid #ccc; - color: #666; - } - .content blockquote p { - margin: 0; - } - -#panel { - display: none; - position: fixed; - top: 10px; bottom: 10px; - left: 100px; right: 100px; - overflow: auto; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 5px; -} - #panel .close { - position: fixed; - top: 10px; right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - background: #fff; - } - -#overlay { - display: none; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - background: rgba(0, 0, 0, 0.9); -} - -.flux_content .bottom { - font-size: 90%; - text-align: center; -} - -/*** PAGINATION ***/ -.pagination { - display: table; - width: 100%; - margin: 0; - background: #fafafa; - text-align: center; - color: #333; - font-size: 80%; - line-height: 200%; - table-layout: fixed; -} - .pagination .item { - display: table-cell; - line-height: 40px; - } - .pagination .item.pager-current { - font-weight: bold; - font-size: 140%; - } - .pagination .item.pager-first, - .pagination .item.pager-previous, - .pagination .item.pager-next, - .pagination .item.pager-last { - width: 100px; - } - .pagination .item a { - display: block; - color: #333; - font-style: italic; - } - .pagination:first-child .item { - border-bottom: 1px solid #aaa; - } - .pagination:last-child .item { - border-top: 1px solid #aaa; - } - -.nav_entries { - display: table; - width: 250px; - height: 40px; - position: fixed; - bottom: 0; - left: 0; - margin: 0; - background: #fff; - border-top: 1px solid #ddd; - text-align: center; - line-height: 40px; - table-layout: fixed; -} - .nav_entries .item { - display: table-cell; - width: 30%; - } - .nav_entries .item a { - display: block; - } - .nav_entries .item .icon.i_up { - margin: 5px 0 0; - vertical-align: top; - } - -.loading { - background: url("loader.gif") center center no-repeat; - font-size: 0; -} - -.bigMarkAsRead { - cursor: pointer; - height: 300px; - text-shadow: 0 -1px 0 #aaa; -} -.bigMarkAsRead:hover { - background: #333; - color: #fff; -} -.bigTick { - font-size: 72pt; - margin: 75px 0 10px 0; -} - -/*** NOTIFICATION ***/ -.notification { - position: fixed; - top: 10px; - left: 25%; right: 25%; - min-height: 30px; - padding: 10px; - line-height: 30px; - text-align: center; - border-radius: 5px; - box-shadow: 0 0 5px #666; - background: #ddd; - color: #666; - font-weight: bold; -} - .notification.good { - background: #f4f899; - } - .notification.bad { - background: #f4a899; - } - .notification a.close { - display: inline-block; - width: 16px; - height: 16px; - float: right; - margin: -20px -20px 0 0; - padding: 5px; - background: #fff; - border-radius: 50px; - border: 1px solid #aaa; - line-height: 16px; - } - -.toggle_aside, .btn.toggle_aside { - display: none; -} - -.actualizeProgress { - position: fixed; - top: 10px; - left: 25%; right: 25%; - padding: 5px; - background: #fff; - text-align: center; - border: 1px solid #ddd; - border-radius: 5px; -} - .actualizeProgress progress { - max-width: 100%; - vertical-align: middle; - } - .actualizeProgress .progress { - color: #999; - font-size: 90%; - vertical-align: middle; - } - -.logs { - border: 1px solid #aaa; -} - .logs .log { - padding: 5px 2%; - overflow: auto; - background: #fafafa; - border-bottom: 1px solid #999; - color: #333; - font-size: 90%; - } - .logs .log .date { - display: block; - } - .logs .log.error { - background: #fdd; - color: #844; - } - .logs .log.warning { - background: #ffe; - color: #c95; - } - .logs .log.notice { - background: #f4f4f4; - color: #aaa; - } - -@media(max-width: 840px) { - .header, - .aside .btn-important, - .aside .feeds .dropdown, - .flux_header .item.website span, - .item.date { - display: none; - } - .flux_header .item.website { - width: 40px; - text-align: center; - } - .flux_header .item.website .favicon { - padding: 12px; - } - - .nav-login { - display: block; - } - - .content { - font-size: 120%; - } - - .pagination { - margin: 0 0 40px; - } - .pagination .pager-previous, .pagination .pager-next { - width: 100px; - } - - .toggle_aside, .btn.toggle_aside { - display: inline-block; - } - .aside { - position: fixed; - top: 0; left: 0; - width: 0; - overflow: hidden; - border-right: none; - z-index: 10; - transition: width 200ms linear; - } - .aside.aside_flux { - padding: 10px 0 0; - } - .aside:target { - width: 80%; - border-right: 1px solid #aaa; - overflow: auto; - } - .aside .toggle_aside { - position: absolute; - right: 0; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border: 1px solid #ccc; - border-radius: 20px; - text-align: center; - line-height: 26px; - } - .aside .categories { - margin: 30px 0; - } - - .nav_entries { - width: 100%; - } - - .nav_menu .btn { - margin: 5px 10px; - } - .nav_menu .stick { - margin: 0 10px; - } - .nav_menu .stick .btn { - margin: 5px 0; - } - - #panel { - left: 5px; right: 5px; - } - - .day .date { - display: none; - } - .day .name { - height: 2.6em; - font-size: 1em; - text-shadow: none; - } - - .notification, - .actualizeProgress { - left: 10px; - right: 10px; - } -} - -/*** FALLBACK ***/ -.btn { - background: #fff; - background: -moz-linear-gradient(top, #fff 0%, #eee 100%); - background: -webkit-linear-gradient(top, #fff 0%, #eee 100%); - background: -o-linear-gradient(top, #fff 0%, #eee 100%); - background: -ms-linear-gradient(top, #fff 0%, #eee 100%); -} - .btn:hover { - background: #f0f0f0; - background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%); - } - .btn.btn-important { - background: #0084CC; - background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%); - } - .btn.btn-important:hover { - background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%); - background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%); - } - .btn.btn-attention { - background: #E95B57; - background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%); - } - .btn.btn-attention:hover { - background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%); - background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%); - } - -.dropdown .dropdown-menu:after { - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); -} - -.nav-head { - background: #fff; - background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); - background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); -} - -.header > .item.search input { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; -} - -@media(max-width: 840px) { - .aside { - -moz-transition: width 200ms linear; - -webkit-transition: width 200ms linear; - -o-transition: width 200ms linear; - -ms-transition: width 200ms linear; - } -} diff --git a/public/themes/default/global.css b/public/themes/default/global.css deleted file mode 100644 index 8685b4e92..000000000 --- a/public/themes/default/global.css +++ /dev/null @@ -1,584 +0,0 @@ -/* FONTS */ -@font-face { - font-family: "OpenSans"; - src: url("fonts/openSans.woff") format("woff"); -} - - -* { - margin: 0; - padding: 0; -} -html, body { - height: 100%; - font-size: 95%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "sans-serif"; -} - -/* LIENS */ -a { - color: #0062BE; - text-decoration: none; -} - a:hover { - text-decoration: underline; - } - -/* LISTES */ -ul, ol, dl { - margin: 10px 0 10px 30px; - line-height: 190%; -} - dd { - margin: 0 0 10px 30px; - } - -/* TITRES */ -h1, h2, h3 { - min-height: 40px; - margin: 15px 0 5px; - line-height: 40px; -} - -/* IMG */ -figure { - margin: 5px 0 10px; - text-align: center; -} - figcaption { - display: inline-block; - padding: 3px 20px; - color: #999; - font-style: italic; - border-bottom: 1px solid #ccc; - } -img { - height: auto; - max-width: 100%; - vertical-align: middle; -} - a img { - border: none; - } - -/* VIDEOS */ -iframe, embed, object, video { - max-width: 100%; -} - -/* FORMULAIRES */ -legend { - display: block; - width: 100%; - margin: 20px 0 5px; - padding: 5px 0; - border-bottom: 1px solid #ddd; - font-size: 150%; - clear: both; -} -label { - display: block; - min-height: 25px; - padding: 5px 0; - font-size: 14px; - line-height: 25px; - cursor: pointer; -} -input, select, textarea { - display: inline-block; - max-width: 100%; - min-height: 25px; - padding: 5px; - background: #fdfdfd; - border: 1px solid #bbb; - border-radius: 3px; - color: #666; - line-height: 25px; - vertical-align: middle; - box-shadow: 0 2px 2px #eee inset; -} - input[type="radio"], - input[type="checkbox"] { - width: 15px !important; - min-height: 15px !important; - } - input:focus, select:focus, textarea:focus { - color: #0062BE; - border-color: #33BBFF; - box-shadow: 0 2px 2px #DDDDFF inset; - } - -.form-group { - margin: 0; -} - .form-group:after { - content: ""; - display: block; - clear: both; - } - .form-group.form-actions { - min-width: 250px; - padding: 5px 0; - background: #f4f4f4; - border-top: 1px solid #ddd; - } - .form-group.form-actions .btn { - margin: 0 10px; - } - .form-group .group-name { - display: block; - float: left; - width: 200px; - padding: 10px 0; - text-align: right; - } - .form-group .group-controls { - min-width: 250px; - min-height: 25px; - margin: 0 0 0 220px; - padding: 5px 0; - } - .form-group .group-controls .control { - display: block; - min-height: 30px; - padding: 5px 0; - line-height: 25px; - font-size: 14px; - } - -.stick { - display: inline-block; - white-space: nowrap; - font-size: 0px; - vertical-align: middle; -} - .stick input, - .stick .btn { - border-radius: 0; - font-size: 14px; - } - .stick .btn:first-child, - .stick input:first-child { - border-radius: 3px 0 0 3px; - } - .stick .btn.btn-important:first-child { - border-right: 1px solid #06f; - } - .stick .btn:last-child, - .stick input:last-child { - border-radius: 0 3px 3px 0; - } - .stick .btn + .btn, - .stick .btn + input, - .stick input + .btn, - .stick input + input { - border-left: none; - } - .stick input + .btn { - border-top: 1px solid #bbb; - } - .stick .btn + .dropdown > .btn { - border-left: none; - border-radius: 0 3px 3px 0; - } - .stick .btn + .dropdown a { - font-size: 12px; - } - -.btn { - display: inline-block; - min-height: 37px; - min-width: 15px; - padding: 5px 10px; - background: linear-gradient(to bottom, #fff 0%, #eee 100%); - border-radius: 3px; - border: 1px solid #ddd; - border-bottom: 1px solid #aaa; - border-right: 1px solid #aaa; - color: #666; - text-shadow: 0px -1px 0 #ddd; - line-height: 20px; - vertical-align: middle; - cursor: pointer; - overflow: hidden; -} - a.btn { - min-height: 25px; - line-height: 25px; - } - .btn:hover { - background: linear-gradient(to bottom, #f8f8f8, #f0f0f0); - text-decoration: none; - } - .btn.active, - .btn:active, - .dropdown-target:target ~ .btn.dropdown-toggle { - box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa; - background: #eee; - } - - .btn.btn-important { - background: linear-gradient(to bottom, #0084CC, #0045CC); - color: #fff; - border: 1px solid #0062B7; - text-shadow: 0px -1px 0 #aaa; - } - .btn.btn-important:hover { - background: linear-gradient(to bottom, #0066CC, #0045CC); - } - .btn.btn-important:active { - background: #0044CB; - box-shadow: none; - } - - .btn.btn-attention { - background: linear-gradient(to bottom, #E95B57, #BD362F); - color: #fff; - border: 1px solid #C44742; - text-shadow: 0px -1px 0px #666; - } - .btn.btn-attention:hover { - background: linear-gradient(to bottom, #D14641, #BD362F); - } - .btn.btn-attention:active { - background: #BD362F; - box-shadow: none; - } - -/* NAVIGATION */ -.nav.nav-list .nav-header, -.nav.nav-list .item { - display: block; - height: 35px; - line-height: 35px; -} - .nav.nav-list .item:hover { - background: #fafafa; - } - .nav.nav-list .item:hover a { - color: #003388; - } - .nav.nav-list .item.active { - background: #0062BE; - color: #fff; - } - .nav.nav-list .item.active a { - color: #fff; - } - .nav.nav-list .disable { - color: #aaa; - background: #fafafa; - text-align: center; - } - .nav.nav-list .item > * { - display: block; - padding: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .nav.nav-list a:hover { - text-decoration: none; - } - .nav.nav-list .item.error a { - color: #BD362F; - } - .nav.nav-list .item.active.error a { - color: #fff; - background: #BD362F; - } - .nav.nav-list .item.empty a { - color: #f39c12; - } - .nav.nav-list .item.active.empty a { - color: #fff; - background: #f39c12; - } - - .nav.nav-list .nav-header { - padding: 0 10px; - color: #888; - background: #f4f4f4; - border-bottom: 1px solid #ddd; - font-weight: bold; - text-shadow: 0 0 1px #ddd; - } - .nav.nav-list .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - - .nav.nav-list .nav-form { - padding: 3px; - text-align: center; - } - -.nav-head { - display: block; - margin: 0; - background: linear-gradient(to bottom, #fff, #f0f0f0); - border-bottom: 1px solid #ddd; - text-align: right; -} - .nav-head .item { - display: inline-block; - padding: 5px 10px; - } - -/* HORIZONTAL-LIST */ -.horizontal-list { - display: table; - table-layout: fixed; - margin: 0; - padding: 0; - width: 100%; -} - .horizontal-list .item { - display: table-cell; - vertical-align: middle; - } - -/* DROPDOWN */ -.dropdown { - position: relative; - display: inline-block; -} - .dropdown .dropdown-target { - display: none; - } - - .dropdown .dropdown-menu { - display: none; - min-width: 200px; - margin: 5px 0 0; - padding: 5px 0; - position: absolute; - right: 0px; - background: #fff; - border: 1px solid #ddd; - border-radius: 5px; - text-align: left; - box-shadow: 3px 3px 3px #ddd; - } - .dropdown .dropdown-menu:after { - content: ""; - position: absolute; - top: -6px; - right: 13px; - width: 10px; - height: 10px; - background: #fff; - border-top: 1px solid #ddd; - border-left: 1px solid #ddd; - z-index: -10; - transform: rotate(45deg); - } - .dropdown .dropdown-menu .dropdown-header { - display: block; - padding: 0 5px; - color: #888; - font-weight: bold; - font-size: 14px; - line-height: 30px; - } - .dropdown .dropdown-menu .item { - display: block; - height: 30px; - font-size: 90%; - line-height: 30px; - } - .dropdown .dropdown-menu .item > * { - display: block; - padding: 0 25px; - line-height: 30px; - } - .dropdown .dropdown-menu .item:hover { - background: #0062BE; - color: #fff; - } - .dropdown .dropdown-menu .item:hover > * { - color: #fff; - text-decoration: none; - } - .dropdown .dropdown-menu .input { - display: block; - height: 40px; - font-size: 90%; - line-height: 30px; - } - .dropdown .dropdown-menu .input select, - .dropdown .dropdown-menu .input input { - display: block; - height: 20px; - width: 95%; - margin: auto; - padding: 2px 5px; - border-radius: 3px; - } - .dropdown .dropdown-menu .input select { - width: 70%; - height: auto; - } - .dropdown .dropdown-menu .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - .dropdown .dropdown-target:target ~ .dropdown-menu { - display: block; - z-index: 10; - } - .dropdown .dropdown-close { - display: inline; - } - .dropdown .dropdown-close a { - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - display: block; - z-index: -10; - } - -/* ALERTS */ -.alert { - display: block; - width: 90%; - margin: 15px auto; - padding: 10px 15px; - background: #f4f4f4; - border: 1px solid #ccc; - border-right: 1px solid #aaa; - border-bottom: 1px solid #aaa; - border-radius: 5px; - color: #aaa; - text-shadow: 0 0 1px #eee; -} - .alert .alert-head { - margin: 0; - font-weight: bold; - font-size: 110%; - } - .alert.alert-warn { - background: #ffe; - border: 1px solid #eeb; - color: #c95; - } - .alert.alert-success { - background: #dfd; - border: 1px solid #cec; - color: #484; - } - .alert.alert-error { - background: #fdd; - border: 1px solid #ecc; - color: #844; - } - -/* ICONES */ -.icon { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - line-height: 16px; - background: center center no-repeat; -} - .icon.i_refresh { - background-image: url("icons/refresh.png"); - background-image: url("icons/refresh.svg"); - } - .icon.i_bookmark { - background-image: url("icons/starred.png"); - background-image: url("icons/starred.svg"); - } - .icon.i_not_bookmark { - background-image: url("icons/unstarred.png"); - background-image: url("icons/unstarred.svg"); - } - .icon.i_read { - background-image: url("icons/read.png"); - background-image: url("icons/read.svg"); - } - .icon.i_unread { - background-image: url("icons/unread.png"); - background-image: url("icons/unread.svg"); - } - .icon.i_all { - background-image: url("icons/all.png"); - background-image: url("icons/all.svg"); - } - .icon.i_close { - background-image: url("icons/close.png"); - background-image: url("icons/close.svg"); - } - .icon.i_search { - background-image: url("icons/search.png"); - background-image: url("icons/search.svg"); - } - .icon.i_configure { - background-image: url("icons/configure.png"); - background-image: url("icons/configure.svg"); - } - .icon.i_login { - background-image: url("icons/login.png"); - background-image: url("icons/login.svg"); - } - .icon.i_logout { - background-image: url("icons/logout.png"); - background-image: url("icons/logout.svg"); - } - .icon.i_add { - background-image: url("icons/add.png"); - background-image: url("icons/add.svg"); - } - .icon.i_link { - background-image: url("icons/link.png"); - background-image: url("icons/link.svg"); - } - .icon.i_down { - background-image: url("icons/down.png"); - background-image: url("icons/down.svg"); - } - .icon.i_up { - background-image: url("icons/up.png"); - background-image: url("icons/up.svg"); - } - .icon.i_next { - background-image: url("icons/next.png"); - background-image: url("icons/next.svg"); - } - .icon.i_prev { - background-image: url("icons/previous.png"); - background-image: url("icons/previous.svg"); - } - .icon.i_help { - background-image: url("icons/help.png"); - background-image: url("icons/help.svg"); - } - .icon.i_note { - background-image: url("icons/note.png"); - background-image: url("icons/note.svg"); - } - .icon.i_note_empty { - background-image: url("icons/note_empty.png"); - background-image: url("icons/note_empty.svg"); - } - .icon.i_category { - background-image: url("icons/category.png"); - background-image: url("icons/category.svg"); - } - .icon.i_rss { - background-image: url("icons/rss.png"); - background-image: url("icons/rss.svg"); - } - .icon.i_share { - background-image: url("icons/share.png"); - background-image: url("icons/share.svg"); - } - .icon.i_tag { - background-image: url("icons/tag.png"); - background-image: url("icons/tag.svg"); - } diff --git a/public/themes/default/icons/add.png b/public/themes/default/icons/add.png Binary files differdeleted file mode 100644 index 90cdf4830..000000000 --- a/public/themes/default/icons/add.png +++ /dev/null diff --git a/public/themes/default/icons/add.svg b/public/themes/default/icons/add.svg deleted file mode 100644 index 425c9b4aa..000000000 --- a/public/themes/default/icons/add.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='list-add-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='true' inkscape:cy='17.83946' inkscape:current-layer='layer12' inkscape:window-width='1920' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='true' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='0' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='1' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='1021' borderopacity='1' guidetolerance='10' inkscape:snap-bbox-midpoints='false' inkscape:cx='-2.56708' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'> - - <path d='m 67.0002,729 0,4 -4,0 0,2 4,0 0,4 2,0 0,-4 4,0 0,-2 -4,0 0,-4 -2,0 z' id='rect31992' style='color:#666666;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible'/> - </g> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-60.0002,-726)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/all.png b/public/themes/default/icons/all.png Binary files differdeleted file mode 100644 index b0dbe5483..000000000 --- a/public/themes/default/icons/all.png +++ /dev/null diff --git a/public/themes/default/icons/all.svg b/public/themes/default/icons/all.svg deleted file mode 100644 index 2180f7a4b..000000000 --- a/public/themes/default/icons/all.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='view-list-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='71.95215' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1198' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='1582' inkscape:snap-global='true' inkscape:window-y='85' gridtolerance='10' inkscape:window-height='1079' inkscape:snap-to-guides='true' inkscape:current-layer='layer12' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='244.57499' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer9' style='display:inline'/> - <g inkscape:label='devices' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer12'> - - <rect inkscape:label='a' x='43.000397' y='748.99976' id='rect7356' height='2.0002136' width='9.9996014' style='color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible'/> - <rect inkscape:label='a' x='43.000397' y='752.99976' id='rect7358' height='2.0002136' width='9.9996014' style='color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible'/> - <rect inkscape:label='a' x='43.000397' y='756.99976' id='rect7360' height='2.0002136' width='9.9996014' style='color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible'/> - </g> - <g inkscape:label='places' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-40.0002,-746)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/category.png b/public/themes/default/icons/category.png Binary files differdeleted file mode 100644 index e3a9bf34b..000000000 --- a/public/themes/default/icons/category.png +++ /dev/null diff --git a/public/themes/default/icons/category.svg b/public/themes/default/icons/category.svg deleted file mode 100644 index 71f95490e..000000000 --- a/public/themes/default/icons/category.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='folder-symbolic.svg' version='1.1' inkscape:version='0.48.0 r9654' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='-173.07332' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:window-width='1310' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='true' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='52' inkscape:snap-global='true' inkscape:window-y='24' gridtolerance='10' inkscape:window-height='690' inkscape:snap-to-guides='true' inkscape:current-layer='layer13' inkscape:zoom='1' inkscape:cx='-157.67647' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer9' style='display:inline'/> - <g inkscape:label='devices' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer13'> - <g transform='translate(234.0002,-820)' id='g14154'> - <path inkscape:connector-curvature='0' d='m 208.53105,997 c -0.28913,0 -0.53125,0.24212 -0.53125,0.53125 l 0,13.93755 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 l 14.9375,0 c 0.2986,0 0.53125,-0.2326 0.53125,-0.5312 l 0,-8.9376 c 0,-0.2891 -0.24212,-0.5312 -0.53125,-0.5312 l -12.46875,0 0,7.5 c 0,0.277 -0.223,0.5 -0.5,0.5 -0.277,0 -0.5,-0.223 -0.5,-0.5 l 0,-8 c 0,-0.277 0.223,-0.5 0.5,-0.5 l 2.96875,0 8.53125,0 0,-1.4062 c 0,-0.3272 -0.26666,-0.5938 -0.59375,-0.5938 l -7.40625,0 0,-1.46875 C 213.9998,997.2421 213.75768,997 213.46855,997 z' id='rect3845' sodipodi:nodetypes='ccccccccccsccccccccccc' style='fill:#666666;fill-opacity:1;stroke:none;display:inline'/> - - </g> - </g> - <g inkscape:label='mimetypes' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/close.png b/public/themes/default/icons/close.png Binary files differdeleted file mode 100644 index 452f1d1cf..000000000 --- a/public/themes/default/icons/close.png +++ /dev/null diff --git a/public/themes/default/icons/close.svg b/public/themes/default/icons/close.svg deleted file mode 100644 index c0c786fd6..000000000 --- a/public/themes/default/icons/close.svg +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' version='1.1' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <defs id='defs7386'/> - <g transform='translate(-60,-518)' id='layer9' style='display:inline'/> - <g transform='translate(-60,-518)' id='layer10'/> - <g transform='translate(-60,-518)' id='layer11'/> - <g transform='translate(-60,-518)' id='layer12'> - <g transform='translate(19,-242)' id='layer4-4-1' style='display:inline'> - - <path inkscape:connector-curvature='0' d='m 45,764 1,0 c 0.01037,-1.2e-4 0.02079,-4.6e-4 0.03125,0 0.254951,0.0112 0.50987,0.12858 0.6875,0.3125 L 49,766.59375 51.3125,764.3125 C 51.578125,764.082 51.759172,764.007 52,764 l 1,0 0,1 c 0,0.28647 -0.03434,0.55065 -0.25,0.75 l -2.28125,2.28125 2.25,2.25 C 52.906938,770.46942 52.999992,770.7347 53,771 l 0,1 -1,0 c -0.265301,-10e-6 -0.530586,-0.0931 -0.71875,-0.28125 L 49,769.4375 46.71875,771.71875 C 46.530586,771.90694 46.26529,772 46,772 l -1,0 0,-1 c -3e-6,-0.26529 0.09306,-0.53058 0.28125,-0.71875 l 2.28125,-2.25 L 45.28125,765.75 C 45.070508,765.55537 44.97809,765.28075 45,765 l 0,-1 z' id='path10839-9' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Andale Mono;-inkscape-font-specification:Andale Mono'/> - </g> - </g> - <g transform='translate(-60,-518)' id='layer13'/> - <g transform='translate(-60,-518)' id='layer14'/> - <g transform='translate(-60,-518)' id='layer15'/> -</svg> diff --git a/public/themes/default/icons/configure.png b/public/themes/default/icons/configure.png Binary files differdeleted file mode 100644 index 8c6fb531c..000000000 --- a/public/themes/default/icons/configure.png +++ /dev/null diff --git a/public/themes/default/icons/configure.svg b/public/themes/default/icons/configure.svg deleted file mode 100644 index 52df8bca7..000000000 --- a/public/themes/default/icons/configure.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns='http://www.w3.org/2000/svg' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' width='16' sodipodi:docname='emblem-system-symbolic.svg' height='16' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' version='1.1' inkscape:version='0.48.1 r9760'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:window-height='967' inkscape:snap-bbox-midpoints='false' inkscape:snap-grids='true' inkscape:object-paths='false' pagecolor='#555753' inkscape:zoom='1' inkscape:current-layer='layer15' borderopacity='1' inkscape:snap-others='false' inkscape:window-x='2259' inkscape:window-y='356' inkscape:snap-to-guides='true' gridtolerance='10' id='namedview88' inkscape:snap-global='true' inkscape:cx='9.30971' inkscape:cy='-0.23089111' bordercolor='#666666' inkscape:window-width='1226' inkscape:snap-bbox='true' guidetolerance='10' showguides='true' showborder='false' inkscape:guide-bbox='true' inkscape:object-nodes='false' inkscape:pageopacity='1' inkscape:window-maximized='0' objecttolerance='10' showgrid='false' inkscape:snap-nodes='true' inkscape:bbox-paths='false' inkscape:pageshadow='2'> - <inkscape:grid visible='true' spacingx='1px' spacingy='1px' id='grid4866' type='xygrid' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' id='layer9' style='display:inline' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='devices' id='layer10' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='apps' id='layer11' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='places' id='layer13' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='mimetypes' id='layer14' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='emblems' id='layer15' style='display:inline' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'> - - <path id='path35543-6-4' d='m 449.0004,402.00002 c -0.22065,0 -0.44081,0.0113 -0.65625,0.0312 l -0.40625,2.09375 c -0.33446,0.0733 -0.66305,0.17589 -0.96875,0.3125 l -1.53125,-1.46875 c -0.38863,0.23011 -0.72695,0.51408 -1.0625,0.8125 l 0.90625,1.90625 c -0.22242,0.24899 -0.42425,0.5225 -0.59375,0.8125 l -2.09375,-0.28125 c -0.17772,0.40877 -0.30872,0.83637 -0.40625,1.28125 l 1.84375,1 c -0.0171,0.16809 -0.0312,0.3274 -0.0312,0.5 0,0.1726 0.0142,0.33191 0.0312,0.5 l -1.84375,1 c 0.0975,0.44488 0.22853,0.87248 0.40625,1.28125 l 2.09375,-0.28125 c 0.1695,0.29 0.37133,0.56351 0.59375,0.8125 l -0.90625,1.90625 c 0.33555,0.29842 0.67387,0.58239 1.0625,0.8125 l 1.53125,-1.46875 c 0.3057,0.13661 0.63429,0.23916 0.96875,0.3125 l 0.40625,2.09375 c 0.21544,0.02 0.4356,0.0312 0.65625,0.0312 0.22065,0 0.44081,-0.0113 0.65625,-0.0312 l 0.40625,-2.09375 c 0.33446,-0.0733 0.66305,-0.17589 0.96875,-0.3125 l 1.53125,1.46875 c 0.38863,-0.23011 0.72695,-0.51408 1.0625,-0.8125 l -0.90625,-1.90625 c 0.22242,-0.24899 0.42425,-0.5225 0.59375,-0.8125 l 2.09375,0.28125 c 0.17772,-0.40877 0.30872,-0.83637 0.40625,-1.28125 l -1.84375,-1 c 0.0171,-0.16809 0.0312,-0.3274 0.0312,-0.5 0,-0.1726 -0.0142,-0.33191 -0.0312,-0.5 l 1.84375,-1 c -0.0975,-0.44488 -0.22853,-0.87248 -0.40625,-1.28125 l -2.09375,0.28125 c -0.1695,-0.29 -0.37133,-0.56351 -0.59375,-0.8125 l 0.90625,-1.90625 c -0.33555,-0.29842 -0.67387,-0.58239 -1.0625,-0.8125 l -1.53125,1.46875 c -0.3057,-0.13661 -0.63429,-0.23916 -0.96875,-0.3125 l -0.40625,-2.09375 c -0.21544,-0.02 -0.4356,-0.0312 -0.65625,-0.0312 z m 0,4 c 1.65685,0 3,1.34315 3,3 0,1.65685 -1.34315,3 -3,3 -1.65685,0 -3,-1.34315 -3,-3 0,-1.65685 1.34315,-3 3,-3 z' style='color:#666666;fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate' inkscape:connector-curvature='0' sodipodi:nodetypes='sccccccccccsccccccccccsccccccccccsccccccccccssssss'/> - </g> - <g inkscape:label='emotes' id='g71291' style='display:inline' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='categories' id='g4953' style='display:inline' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> - <g inkscape:label='actions' id='layer12' style='display:inline' transform='translate(-441.0002,-400.99999)' inkscape:groupmode='layer'/> -</svg> diff --git a/public/themes/default/icons/down.png b/public/themes/default/icons/down.png Binary files differdeleted file mode 100644 index 5a647245f..000000000 --- a/public/themes/default/icons/down.png +++ /dev/null diff --git a/public/themes/default/icons/down.svg b/public/themes/default/icons/down.svg deleted file mode 100644 index 06d031fae..000000000 --- a/public/themes/default/icons/down.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='go-down-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.4 r9939' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-nodes='false' inkscape:bbox-paths='true' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer12' inkscape:cx='18.648774' inkscape:cy='9.99302' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1408' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='0' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='60px' originy='530px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-181.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-181.0002,-747)'> - - <path inkscape:connector-curvature='0' d='m 195.03152,751.00323 0,1.00001 c -9.1e-4,0.0111 5.9e-4,0.021 -9e-5,0.0312 -0.0112,0.25496 -0.12835,0.50994 -0.31251,0.6875 l -5.71875,6.29767 -5.71875,-6.29773 c -0.18821,-0.1881 -0.28121,-0.45346 -0.28122,-0.71875 l 0,-1.00001 1,0 c 0.26531,7e-5 0.53059,0.0931 0.71873,0.28131 l 4.28125,4.82896 4.28127,-4.82896 c 0.19464,-0.21073 0.46925,-0.30315 0.74998,-0.2813 z' id='path10839-9-9-5-9-1' sodipodi:nodetypes='ccsccccccccccc' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Andale Mono;-inkscape-font-specification:Andale Mono'/> - </g> -</svg> diff --git a/public/themes/default/icons/help.png b/public/themes/default/icons/help.png Binary files differdeleted file mode 100644 index aa63c5411..000000000 --- a/public/themes/default/icons/help.png +++ /dev/null diff --git a/public/themes/default/icons/help.svg b/public/themes/default/icons/help.svg deleted file mode 100644 index b103ec46d..000000000 --- a/public/themes/default/icons/help.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='help-browser-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='true' inkscape:cy='-218.16054' inkscape:current-layer='layer11' inkscape:window-width='1920' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='true' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='0' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='1' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='1021' borderopacity='1' guidetolerance='10' inkscape:snap-bbox-midpoints='false' inkscape:cx='-124.56688' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'> - <path d='m 190,490 c -4.4147,0 -8,3.5853 -8,8 0,4.4147 3.5853,8 8,8 4.4147,0 8,-3.5853 8,-8 0,-4.4147 -3.5853,-8 -8,-8 z m 0,2 c 3.3413,0 6,2.6587 6,6 0,3.3413 -2.6587,6 -6,6 -3.3413,0 -6,-2.6587 -6,-6 0,-3.3413 2.6587,-6 6,-6 z' id='rect11749-5-0-3' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - - <path d='M 189.34375,495 C 188.05763,495 187,496.05763 187,497.34375 l 0,1.3125 c 0,1.28612 1.05763,2.34375 2.34375,2.34375 l 1.3125,0 C 191.94237,501 193,499.94237 193,498.65625 l 0,-1.3125 C 193,496.05763 191.94237,495 190.65625,495 l -1.3125,0 z m 0,1 1.3125,0 c 0.74942,0 1.34375,0.59433 1.34375,1.34375 l 0,1.3125 C 192,499.40567 191.40567,500 190.65625,500 l -1.3125,0 C 188.59433,500 188,499.40567 188,498.65625 l 0,-1.3125 C 188,496.59433 188.59433,496 189.34375,496 z' id='rect11749-5-0-3-3' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - <path inkscape:connector-curvature='0' d='m 186.71875,491.4375 c -1.51031,0.6073 -2.6811,1.7985 -3.28125,3.3125 l 3.75,1.875 c 0.25196,-0.64029 0.74249,-1.17056 1.375,-1.4375 l -1.84375,-3.75 z m 6.5625,0 -1.84375,3.75 c 0.63251,0.26694 1.12304,0.79721 1.375,1.4375 l 3.75,-1.875 c -0.60015,-1.514 -1.77094,-2.7052 -3.28125,-3.3125 z m -6.09375,8 -3.75,1.875 c 0.60709,1.48862 1.78896,2.64995 3.28125,3.25 l 1.875,-3.75 c -0.62682,-0.25556 -1.14334,-0.75203 -1.40625,-1.375 z m 5.625,0 c -0.26291,0.62297 -0.77943,1.11944 -1.40625,1.375 l 1.875,3.75 c 1.49229,-0.60005 2.67416,-1.76138 3.28125,-3.25 l -3.75,-1.875 z' id='path4624' style='fill:#bebebe;fill-opacity:1;stroke:none'/> - </g> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/link.png b/public/themes/default/icons/link.png Binary files differdeleted file mode 100644 index de2b187d7..000000000 --- a/public/themes/default/icons/link.png +++ /dev/null diff --git a/public/themes/default/icons/link.svg b/public/themes/default/icons/link.svg deleted file mode 100644 index ddc8e0706..000000000 --- a/public/themes/default/icons/link.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' id='svg7384' sodipodi:docname='web-browser-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' width='16' xmlns='http://www.w3.org/2000/svg' height='16.00366'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='false' inkscape:current-layer='layer11' inkscape:bbox-paths='false' inkscape:pageshadow='2' inkscape:snap-bbox='true' inkscape:pageopacity='1' inkscape:guide-bbox='true' pagecolor='#555753' bordercolor='#666666' showguides='true' inkscape:snap-bbox-midpoints='false' inkscape:window-maximized='0' inkscape:snap-grids='true' inkscape:window-width='1595' id='namedview88' inkscape:window-x='2191' inkscape:window-y='111' gridtolerance='10' borderopacity='1' showgrid='false' inkscape:cx='21.026784' inkscape:cy='2.9913602' inkscape:snap-nodes='false' inkscape:window-height='1162' inkscape:snap-global='true' inkscape:object-nodes='false' objecttolerance='10' inkscape:snap-others='false' inkscape:zoom='1' guidetolerance='10' inkscape:snap-to-guides='true' showborder='false'> - <inkscape:grid enabled='true' type='xygrid' id='grid4866' visible='true' snapvisiblegridlinesonly='true' empspacing='2' spacingx='1px' spacingy='1px'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer9' inkscape:label='status' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-183,-529)'> - - <path style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible;enable-background:accumulate' sodipodi:nodetypes='cccccccc' id='path6242' inkscape:connector-curvature='0' d='m 191.0002,533.84553 0,10.38049 -2.34399,-2.28818 -1.33941,2.73465 c -0.32808,0.73962 -2.03368,0.14492 -1.5487,-0.84412 l 1.32547,-2.83928 -2.95789,0 6.86452,-7.14356 z'/> - <path style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans' sodipodi:nodetypes='csccssccsssc' id='path23405' inkscape:connector-curvature='0' d='m 190.15645,530.0625 c -3.82659,0.46006 -6.57883,3.95775 -6.09375,7.78125 0.13127,1.03473 0.29377,1.38184 0.29377,1.38184 l 1.67498,-1.63184 c -0.33104,-2.75343 1.62156,-5.23146 4.375,-5.5625 2.75344,-0.33104 5.23146,1.62156 5.5625,4.375 0.31355,2.60795 -1.39127,5.02493 -3.96875,5.53125 l 0.0312,2 c 0,0 0.52086,-0.1059 0.62354,-0.13097 3.41561,-0.83385 5.70627,-4.1273 5.28271,-7.65028 -0.46006,-3.8266 -3.95466,-6.55381 -7.78125,-6.09375 z'/> - <path style='opacity:0.3;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate' id='path11289' inkscape:connector-curvature='0' d='m 187.11293,536.81497 0,-0.20574 -0.19826,0.0186 c 0.0165,-0.13095 0.0329,-0.26167 0.0496,-0.3926 l -0.11578,0 -0.11556,0.14959 -0.11578,0.0559 -0.1653,-0.0932 -0.0165,-0.20575 0.0331,-0.22438 0.24798,-0.18688 0.19826,0 0.0329,-0.11229 0.24786,0.0559 0.18183,0.2246 0.0331,-0.37419 0.31401,-0.26167 0.11567,-0.28055 0.23133,-0.0934 0.1322,-0.18688 0.29738,-0.0564 0.14885,-0.22415 c -0.14874,0 -0.29749,0 -0.44623,0 l 0.28094,-0.13095 0.19814,0 0.28106,-0.0937 0.0331,-0.11186 -0.0992,-0.0937 -0.11567,-0.0375 0.0331,-0.11208 -0.0826,-0.16822 -0.19837,0.0746 0.0331,-0.14947 -0.23134,-0.13096 -0.18171,0.3177 0.0165,0.11229 -0.18171,0.075 -0.11578,0.24302 -0.0495,-0.22438 -0.31402,-0.13095 -0.0496,-0.16822 0.41315,-0.24325 0.18182,-0.16822 0.0165,-0.20563 -0.0991,-0.0562 -0.13219,-0.0188 -0.0826,0.20575 c 0,0 -0.1382,0.0271 -0.17373,0.0358 -0.45378,0.41804 -1.37066,1.32044 -1.58368,3.02405 0.008,0.0395 0.15441,0.26854 0.15441,0.26854 l 0.347,0.20552 0.347,0.0937 m 3.96609,-4.30034 -0.4298,-0.16833 -0.49552,0.0561 -0.61161,0.16822 -0.11567,0.11229 0.38008,0.26167 0,0.14959 -0.14875,0.14959 0.19846,0.39294 0.13188,-0.075 0.16561,-0.26168 c 0.2553,-0.0789 0.4842,-0.16833 0.72686,-0.28053 l 0.19846,-0.5048 m 2.52925,0.34192 -0.375,0.0937 -0.21875,0.15625 0,0.125 -0.375,0.25 0.0937,0.34375 0.21875,-0.15625 0.125,0.15625 0.15625,0.0937 0.0937,-0.28125 -0.0625,-0.15625 0.0625,-0.0937 0.21875,-0.1875 0.0937,0 -0.0937,0.21875 0,0.1875 c 0.0892,-0.0242 0.1588,-0.051 0.25,-0.0625 l -0.25,0.1875 0,0.125 -0.3125,0.21875 -0.28125,-0.0625 0,-0.15625 -0.125,0.0625 0.0625,0.15625 -0.21875,0 -0.125,0.21875 -0.15625,0.15625 -0.0937,0.0312 0,0.1875 0.0312,0.15625 -0.0312,0 0,0.53125 0.0625,-0.0312 0.0937,-0.21875 0.1875,-0.125 0.0312,-0.0937 0.28125,-0.0625 0.15625,0.1875 0.1875,0.0937 -0.0937,0.1875 0.15625,-0.0312 0.0625,-0.21875 -0.1875,-0.21875 0.0625,0 0.21875,0.15625 0.0312,0.21875 0.15625,0.21875 0.0625,-0.3125 0.0937,-0.0312 c 0.0959,0.0996 0.1692,0.23163 0.25,0.34375 l 0.28125,0 0.1875,0.125 -0.0937,0.0937 -0.15625,0.15625 -0.25,0 -0.34375,-0.0937 -0.1875,0 -0.125,0.15625 -0.34375,-0.375 -0.25,-0.0625 -0.375,0.0625 -0.15625,0.0937 0,2.40625 0.0312,0.0312 0.25,-0.15625 0.0937,0.0937 0.28125,0 0.125,0.15625 -0.0937,0.3125 0.1875,0.1875 0,0.375 0.125,0.25 -0.0937,0.25 c -0.009,0.16159 0,0.30714 0,0.46875 0.0795,0.21894 0.14355,0.43575 0.21875,0.65625 l 0.0625,0.34375 0,0.1875 0.125,0 0.21875,-0.125 0.25,0 0.375,-0.4375 -0.0312,-0.15625 0.25,-0.21875 -0.1875,-0.1875 0.21875,-0.1875 0.21875,-0.125 0.0937,-0.125 -0.0625,-0.25 0,-0.59375 0.1875,-0.375 0.1875,-0.25 0.25,-0.5625 0,-0.15625 c -0.11654,0.0146 -0.22972,0.0231 -0.34375,0.0312 -0.0722,0.005 -0.14446,0 -0.21875,0 -0.12359,-0.25961 -0.2183,-0.50966 -0.3125,-0.78125 l -0.15625,-0.1875 -0.0937,-0.3125 0.0625,-0.0625 0.21875,0.25 0.25,0.5625 0.15625,0.15625 -0.0625,0.21875 0.15625,0.15625 0.25,-0.25 0.3125,-0.21875 0.15625,-0.1875 0,-0.21875 c -0.0389,-0.0732 -0.0547,-0.14545 -0.0937,-0.21875 l -0.15625,0.1875 -0.125,-0.15625 -0.1875,-0.125 0,-0.28125 0.21875,0.21875 0.21875,-0.0312 c 0.10166,0.0923 0.19205,0.20751 0.28125,0.3125 l 0.15625,-0.1875 c 0,-0.17463 -0.19976,-1.02044 -0.625,-1.75 -0.42526,-0.72932 -1.15625,-1.40625 -1.15625,-1.40625 l -0.0625,0.0937 -0.21875,0.21875 -0.25,-0.25 0.25,0 0.125,-0.125 -0.46875,-0.0937 -0.25,-0.0937 z'/> - </g> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer12' inkscape:label='actions' transform='translate(-183,-529)'/> -</svg> diff --git a/public/themes/default/icons/login.png b/public/themes/default/icons/login.png Binary files differdeleted file mode 100644 index cebe0cf7d..000000000 --- a/public/themes/default/icons/login.png +++ /dev/null diff --git a/public/themes/default/icons/login.svg b/public/themes/default/icons/login.svg deleted file mode 100644 index 0a8af162f..000000000 --- a/public/themes/default/icons/login.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='changes-prevent-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='9.416826' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1296' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='1941' inkscape:snap-global='true' inkscape:window-y='81' gridtolerance='10' inkscape:window-height='1013' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='52.563336' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='lock' transform='translate(161.0002,-39)' id='g4053' style='fill:#bebebe;fill-opacity:1'> - - </g> - <path inkscape:connector-curvature='0' d='m 184,244 c -0.554,0 -1,0.446 -1,1 l 0,0.53125 0,5.46875 12,0 0,-5.46875 L 195,245 c 0,-0.554 -0.446,-1 -1,-1 l -10,0 z' id='rect4063' sodipodi:nodetypes='csccccscc' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible'/> - <path inkscape:connector-curvature='0' d='m 188,238 c -1.6447,0 -3,1.3553 -3,3 l 0,7 c 0,1.6447 1.3553,3 3,3 l 2,0 c 1.6447,0 3,-1.3553 3,-3 l 0,-7 c 0,-1.6447 -1.3553,-3 -3,-3 l -2,0 z m 0,2 2,0 c 0.5713,0 1,0.4287 1,1 l 0,7 c 0,0.5713 -0.4287,1 -1,1 l -2,0 c -0.5713,0 -1,-0.4287 -1,-1 l 0,-7 c 0,-0.5713 0.4287,-1 1,-1 z' id='rect4291' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/logout.png b/public/themes/default/icons/logout.png Binary files differdeleted file mode 100644 index 49255fccd..000000000 --- a/public/themes/default/icons/logout.png +++ /dev/null diff --git a/public/themes/default/icons/logout.svg b/public/themes/default/icons/logout.svg deleted file mode 100644 index 284a67578..000000000 --- a/public/themes/default/icons/logout.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='changes-allow-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='9.416826' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1296' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='1941' inkscape:snap-global='true' inkscape:window-y='81' gridtolerance='10' inkscape:window-height='1013' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='32.563336' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='lock' transform='translate(181.0002,-39)' id='g4053-3' style='fill:#bebebe;fill-opacity:1'> - - </g> - <path inkscape:connector-curvature='0' d='m 204,246 c -0.554,0 -1,0.446 -1,1 l 0,0.53125 0,5.46875 12,0 0,-5.46875 L 215,247 c 0,-0.554 -0.446,-1 -1,-1 l -10,0 z' id='rect4063-6' sodipodi:nodetypes='csccccscc' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible'/> - <path inkscape:connector-curvature='0' d='m 208.0002,237 c -1.64469,0 -3,1.35529 -3,3 l 0,3 2,0 0,-3 c 0,-0.57129 0.42873,-1 1,-1 l 2,0 c 0.57127,0 1,0.42871 1,1 l 0,7 2,0 0,-7 c 0,-1.64471 -1.35531,-3 -3,-3 l -2,0 z' id='rect4291-4' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/next.png b/public/themes/default/icons/next.png Binary files differdeleted file mode 100644 index ab3490c3b..000000000 --- a/public/themes/default/icons/next.png +++ /dev/null diff --git a/public/themes/default/icons/next.svg b/public/themes/default/icons/next.svg deleted file mode 100644 index 72637b4e6..000000000 --- a/public/themes/default/icons/next.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='go-next-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.4 r9939' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-nodes='false' inkscape:bbox-paths='true' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer12' inkscape:cx='78.648774' inkscape:cy='9.99302' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1408' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='0' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='120px' originy='530px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-121.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-121.0002,-747)'> - - <path inkscape:connector-curvature='0' d='m 125.0004,749 1,0 c 0.0104,-1.2e-4 0.0208,-4.6e-4 0.0313,0 0.25495,0.0112 0.50987,0.12858 0.6875,0.3125 l 6.29767,5.71875 -6.29772,5.71875 c -0.18816,0.18819 -0.45346,0.28125 -0.71875,0.28125 l -1,0 0,-1 c 0,-0.26529 0.0931,-0.53058 0.28125,-0.71875 l 4.82897,-4.28125 -4.82897,-4.28125 c -0.21074,-0.19463 -0.30316,-0.46925 -0.28125,-0.75 z' id='path10839-9-9-5-9' sodipodi:nodetypes='ccsccccccccccc' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Andale Mono;-inkscape-font-specification:Andale Mono'/> - </g> -</svg> diff --git a/public/themes/default/icons/non-starred.png b/public/themes/default/icons/non-starred.png Binary files differdeleted file mode 100644 index 4aafb6d8a..000000000 --- a/public/themes/default/icons/non-starred.png +++ /dev/null diff --git a/public/themes/default/icons/non-starred.svg b/public/themes/default/icons/non-starred.svg deleted file mode 100644 index 346717f78..000000000 --- a/public/themes/default/icons/non-starred.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='non-starred-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='0.50817' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1457' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='86' inkscape:snap-global='true' inkscape:window-y='51' gridtolerance='10' inkscape:window-height='1093' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='-0.97094164' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='folder-remote' transform='translate(-166.9996,-599)' id='g5516'> - - </g> - <path inkscape:connector-curvature='0' d='m 69.002994,398.0051 c -0.2659,-9.9e-4 -0.498591,0.1006 -0.664702,0.2393 -0.166111,0.13869 -0.277415,0.32137 -0.389683,0.50028 -0.224526,0.35782 -0.392692,0.76873 -0.565462,1.20357 -0.17277,0.43483 -0.347131,0.88766 -0.504599,1.23232 -0.157468,0.34465 -0.345596,0.57871 -0.35862,0.58776 -0.01287,0.009 -0.307217,0.117 -0.695765,0.15181 -0.388547,0.0348 -0.881219,0.0492 -1.36324,0.0694 -0.482021,0.0202 -0.970296,0.0362 -1.393042,0.12817 -0.211377,0.046 -0.4154,0.10148 -0.604343,0.20956 -0.188943,0.10808 -0.366894,0.28631 -0.450041,0.53002 -0.08315,0.24371 -0.04901,0.50627 0.03636,0.70157 0.08537,0.19531 0.226562,0.36514 0.368205,0.52344 0.283296,0.31663 0.632701,0.60557 1.007899,0.89849 0.375198,0.29292 0.776711,0.55926 1.067753,0.81027 0.291042,0.25101 0.454223,0.50796 0.459122,0.52271 0.0048,0.0146 0.03119,0.33498 -0.05456,0.70231 -0.08575,0.36732 -0.223801,0.81174 -0.352819,1.26034 -0.129019,0.44861 -0.25176,0.90196 -0.291704,1.31835 -0.01997,0.20819 -0.04285,0.40729 0.0053,0.61409 0.04814,0.20679 0.184498,0.43007 0.399016,0.58168 0.214508,0.15161 0.449359,0.1881 0.668233,0.1701 0.218874,-0.018 0.435347,-0.0703 0.635153,-0.15132 0.399612,-0.16214 0.801767,-0.42851 1.206417,-0.68231 0.404649,-0.2538 0.808218,-0.52155 1.145553,-0.71107 0.337335,-0.18952 0.648398,-0.2686 0.66445,-0.26854 0.01586,6e-5 0.303381,0.0951 0.638936,0.28732 0.335555,0.19221 0.725321,0.46503 1.127607,0.72205 0.402287,0.25702 0.819957,0.49752 1.218019,0.66284 0.199027,0.0827 0.388928,0.15086 0.607623,0.17059 0.218695,0.0197 0.479784,-0.031 0.695512,-0.18105 0.215717,-0.15001 0.339276,-0.35235 0.389178,-0.55877 0.0499,-0.20642 0.04291,-0.43517 0.02476,-0.64358 -0.03632,-0.41683 -0.158989,-0.86394 -0.284099,-1.31372 -0.12511,-0.44978 -0.261649,-0.88661 -0.344206,-1.25475 -0.08256,-0.36813 -0.07169,-0.68662 -0.06666,-0.70133 0.005,-0.0145 0.187465,-0.25247 0.480599,-0.50101 0.293134,-0.24854 0.675989,-0.53755 1.053628,-0.82731 0.37764,-0.28976 0.767161,-0.57158 1.053124,-0.88579 0.142977,-0.1571 0.241796,-0.33444 0.32882,-0.52904 0.08702,-0.1946 0.135998,-0.42874 0.05481,-0.67306 -0.08119,-0.24432 -0.271084,-0.41314 -0.459122,-0.52272 -0.188037,-0.10958 -0.366789,-0.18158 -0.577821,-0.22931 -0.422064,-0.0955 -0.913586,-0.14009 -1.395564,-0.16426 -0.481977,-0.0242 -0.945835,-0.039 -1.334196,-0.077 -0.388361,-0.038 -0.715653,-0.13122 -0.728592,-0.14037 -0.01279,-0.009 -0.184019,-0.23636 -0.338404,-0.58217 -0.154385,-0.34581 -0.326584,-0.78819 -0.495481,-1.22429 -0.168896,-0.4361 -0.334766,-0.86429 -0.556091,-1.2238 -0.110668,-0.17977 -0.233352,-0.35397 -0.398259,-0.49396 -0.164907,-0.13999 -0.40309,-0.25684 -0.66899,-0.25783 z m 0.0086,0.99424 c 0.0422,0.0358 0.106711,0.13602 0.184103,0.26173 0.154773,0.25141 0.330675,0.6272 0.493967,1.04883 0.163292,0.42163 0.319052,0.88645 0.495985,1.28277 0.176933,0.39633 0.314564,0.73379 0.675296,0.98889 0.360721,0.25509 0.770187,0.29763 1.215244,0.34118 0.445058,0.0435 0.92882,0.0532 1.394807,0.0765 0.465988,0.0234 0.918236,0.0697 1.213479,0.13647 0.147627,0.0334 0.255581,0.0572 0.303811,0.0853 -0.02227,0.0498 -0.07958,0.15478 -0.179562,0.26464 -0.199968,0.21973 -0.537491,0.48746 -0.902606,0.7676 -0.365114,0.28015 -0.778037,0.57219 -1.113986,0.85704 -0.33595,0.28485 -0.639918,0.53221 -0.779871,0.94209 -0.139952,0.40986 -0.05396,0.77681 0.04065,1.19869 0.09461,0.42189 0.25406,0.87813 0.375016,1.31299 0.120956,0.43486 0.226877,0.84692 0.25228,1.13851 0.01156,0.13264 -0.01699,0.23485 -0.02778,0.29267 -0.05993,-0.008 -0.176402,-0.0324 -0.303811,-0.0853 -0.278362,-0.11561 -0.646494,-0.32808 -1.03543,-0.57657 -0.388935,-0.24849 -0.803899,-0.53053 -1.18847,-0.75081 -0.38457,-0.22028 -0.707909,-0.39837 -1.155138,-0.40015 -0.447219,-0.002 -0.792046,0.17245 -1.178633,0.38965 -0.386587,0.21719 -0.784375,0.49517 -1.175607,0.74055 -0.391232,0.24538 -0.776359,0.4507 -1.055898,0.56412 -0.139769,0.0567 -0.246155,0.0856 -0.302298,0.0902 -0.01026,-0.0578 -0.01522,-0.16008 -0.0025,-0.29243 0.02793,-0.29118 0.138178,-0.70893 0.262912,-1.14264 0.124734,-0.43372 0.254035,-0.89785 0.352315,-1.31883 0.09828,-0.42099 0.186721,-0.78846 0.05027,-1.19943 -0.136442,-0.41097 -0.43218,-0.64202 -0.765707,-0.92967 -0.333527,-0.28766 -0.705509,-0.58555 -1.068257,-0.86876 -0.362749,-0.2832 -0.717797,-0.56075 -0.915974,-0.78224 -0.09908,-0.11075 -0.162208,-0.21163 -0.184103,-0.26173 0.04834,-0.0276 0.154481,-0.058 0.302298,-0.0902 0.295625,-0.0643 0.745175,-0.10714 1.211209,-0.1267 0.466034,-0.0196 0.948238,-0.0298 1.393546,-0.0697 0.445308,-0.0399 0.846793,-0.0499 1.209695,-0.30216 0.362893,-0.25221 0.5383,-0.60511 0.718755,-1.00009 0.180456,-0.39497 0.33731,-0.84115 0.504347,-1.26156 0.167037,-0.42041 0.318485,-0.83556 0.475555,-1.08586 0.07853,-0.12515 0.167511,-0.20007 0.21012,-0.23565 z' id='path5520' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.04970646;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/note.png b/public/themes/default/icons/note.png Binary files differdeleted file mode 100644 index d819ebd02..000000000 --- a/public/themes/default/icons/note.png +++ /dev/null diff --git a/public/themes/default/icons/note.svg b/public/themes/default/icons/note.svg deleted file mode 100644 index 117b999f7..000000000 --- a/public/themes/default/icons/note.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='user-available-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='-71.46449' inkscape:current-layer='layer9' inkscape:window-width='1310' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='false' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='54' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='0' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='690' borderopacity='1' guidetolerance='10' inkscape:cx='83.606112' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path inkscape:connector-curvature='0' d='m 44.6875,358.0625 c -1.5235,0 -2.75,1.2265 -2.75,2.75 l 0,4.4375 c 0,1.5235 1.2265,2.75 2.75,2.75 l 6.305187,0 3.053347,3.98495 -0.07728,-4.0787 c 1.204532,-0.29284 2.09375,-1.35911 2.09375,-2.65625 l 0,-4.4375 c 0,-1.5235 -1.2265,-2.75 -2.75,-2.75 z' id='rect11261' sodipodi:nodetypes='sssscccssss' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.4000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate'/> - </g> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/note_empty.png b/public/themes/default/icons/note_empty.png Binary files differdeleted file mode 100644 index c26ad2b16..000000000 --- a/public/themes/default/icons/note_empty.png +++ /dev/null diff --git a/public/themes/default/icons/note_empty.svg b/public/themes/default/icons/note_empty.svg deleted file mode 100644 index 93092f4a4..000000000 --- a/public/themes/default/icons/note_empty.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='user-invisible-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16.000198'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='true' inkscape:cy='6.91348' inkscape:current-layer='layer9' inkscape:window-width='1920' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='true' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='0' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='1' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='32' inkscape:window-height='1021' borderopacity='1' guidetolerance='10' inkscape:snap-bbox-midpoints='false' inkscape:cx='11.38402' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path d='m 104.75,357.0625 c -2.0602,0 -3.75,1.6898 -3.75,3.75 l 0,4.4375 c 0,2.0602 1.6898,3.75 3.75,3.75 l 4.9375,0 3.75,2.65625 1.59375,1.125 0,-1.96875 -0.0313,-2.5 c 1.11055,-0.59715 1.96875,-1.65265 1.96875,-3.0625 l 0,-4.4375 c 0,-2.0602 -1.6898,-3.75 -3.75,-3.75 l -8.46875,0 z m 0,2 8.46875,0 c 0.9868,0 1.75,0.7632 1.75,1.75 l 0,4.4375 c 0,0.86273 -0.63508,1.54099 -1.125,1.625 L 113,367 l 0,0.84375 0,1.03125 -2.40625,-1.6875 -0.25,-0.1875 -0.3125,0 -5.28125,0 c -0.9868,0 -1.75,-0.7632 -1.75,-1.75 l 0,-4.4375 c 0,-0.9868 0.7632,-1.75 1.75,-1.75 z' id='path12148' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - </g> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/previous.png b/public/themes/default/icons/previous.png Binary files differdeleted file mode 100644 index 10e40669e..000000000 --- a/public/themes/default/icons/previous.png +++ /dev/null diff --git a/public/themes/default/icons/previous.svg b/public/themes/default/icons/previous.svg deleted file mode 100644 index 67685c50c..000000000 --- a/public/themes/default/icons/previous.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='go-next-rtl-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.4 r9939' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-nodes='false' inkscape:bbox-paths='true' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer12' inkscape:cx='-101.35123' inkscape:cy='9.99302' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1408' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='0' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='-60px' originy='530px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-301.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-301.0002,-747)'> - - <path inkscape:connector-curvature='0' d='m 313.01372,749 -1,0 c -0.0104,-1.2e-4 -0.0208,-4.6e-4 -0.0313,0 -0.25495,0.0112 -0.50987,0.12858 -0.6875,0.3125 l -6.29767,5.71875 6.29772,5.71875 c 0.18816,0.18819 0.45346,0.28125 0.71875,0.28125 l 1,0 0,-1 c 0,-0.26529 -0.0931,-0.53058 -0.28125,-0.71875 l -4.82897,-4.28125 4.82897,-4.28125 c 0.21074,-0.19463 0.30316,-0.46925 0.28125,-0.75 z' id='path5441' sodipodi:nodetypes='ccsccccccccccc' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Andale Mono;-inkscape-font-specification:Andale Mono'/> - </g> -</svg> diff --git a/public/themes/default/icons/read.png b/public/themes/default/icons/read.png Binary files differdeleted file mode 100644 index a402689c7..000000000 --- a/public/themes/default/icons/read.png +++ /dev/null diff --git a/public/themes/default/icons/read.svg b/public/themes/default/icons/read.svg deleted file mode 100644 index 932bfd860..000000000 --- a/public/themes/default/icons/read.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns='http://www.w3.org/2000/svg' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' width='16' xmlns:cc='http://creativecommons.org/ns#' height='16.001099' sodipodi:docname='mail-read-symbolic.svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.1 r9760' version='1.1'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:window-width='1226' id='namedview88' showgrid='false' objecttolerance='10' inkscape:pageopacity='1' inkscape:cx='12.92257' inkscape:cy='-1.422716' inkscape:snap-bbox='true' borderopacity='1' inkscape:object-nodes='false' inkscape:current-layer='layer9' inkscape:snap-to-guides='true' inkscape:snap-others='false' inkscape:snap-grids='true' bordercolor='#666666' inkscape:window-height='967' inkscape:snap-bbox-midpoints='false' showborder='false' inkscape:bbox-paths='false' inkscape:guide-bbox='true' inkscape:window-x='2657' inkscape:window-y='338' inkscape:snap-global='true' inkscape:snap-nodes='true' pagecolor='#555753' inkscape:object-paths='false' inkscape:pageshadow='2' inkscape:zoom='1' inkscape:window-maximized='0' gridtolerance='10' guidetolerance='10' showguides='true'> - <inkscape:grid id='grid4866' empspacing='2' type='xygrid' visible='true' snapvisiblegridlinesonly='true' enabled='true' spacingx='1px' spacingy='1px'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g id='layer9' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='status'> - - <path id='path35631' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;opacity:0.35;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans' d='m 68.8752,297 a 1.0001,1.0001 0 0 0 -0.5,0.25 l -4.90625,4 a 1.0001,1.0001 0 0 0 -0.0625,0.0312 c 0,0 -0.32587,0.29728 -0.65625,0.75 -0.22334,0.30605 -0.3527,0.8316 -0.5,1.3125 a 1.0001,1.0001 0 0 0 -0.03125,0.0312 1.0001,1.0001 0 0 0 -0.21875,0.5625 c -5.1e-4,0.0118 3.6e-4,0.0195 0,0.0312 a 1.0001,1.0001 0 0 0 0,0.0312 1.0001,1.0001 0 0 0 0,0.15625 l 0,7.84375 a 1.0001,1.0001 0 0 0 1,1 l 12,0 a 1.0001,1.0001 0 0 0 1,-1 l 0,-7.84375 a 1.0001,1.0001 0 0 0 0,-0.15625 1.0001,1.0001 0 0 0 -0.21875,-0.65625 1.0001,1.0001 0 0 0 -0.03125,-0.0312 c -0.32774,-1.18787 -1.125,-2 -1.125,-2 a 1.0001,1.0001 0 0 0 -0.0312,-0.0312 L 69.6252,297.25 a 1.0001,1.0001 0 0 0 -0.65625,-0.25 1.0001,1.0001 0 0 0 -0.0937,0 z m 0.125,2.28125 4.3125,3.53125 0.0312,0.0312 c 0.021,0.0255 0.18032,0.24952 0.34375,0.5 l -4.68745,3.53125 -4.6875,-3.53125 c 0.0259,-0.0394 0.0349,-0.0872 0.0625,-0.125 0.1908,-0.26146 0.31874,-0.41421 0.34375,-0.4375 l 0.03125,-0.0312 4.25,-3.46875 z m -5,5.09375 4.6875,3.53125 0.3125,0.21875 0.3125,-0.21875 4.6875,-3.53125 0,6.625 -10,0 0,-6.625 z' inkscape:connector-curvature='0'/> - </g> - <g id='layer10' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='devices'/> - <g id='layer11' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='apps'/> - <g id='layer13' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='places'/> - <g id='layer14' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='mimetypes'/> - <g id='layer15' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='emblems'/> - <g id='g71291' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='emotes'/> - <g id='g4953' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='categories'/> - <g id='layer12' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='actions'/> -</svg> diff --git a/public/themes/default/icons/refresh.png b/public/themes/default/icons/refresh.png Binary files differdeleted file mode 100644 index dba399981..000000000 --- a/public/themes/default/icons/refresh.png +++ /dev/null diff --git a/public/themes/default/icons/refresh.svg b/public/themes/default/icons/refresh.svg deleted file mode 100644 index 11728fc5d..000000000 --- a/public/themes/default/icons/refresh.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' xmlns:cc='http://creativecommons.org/ns#' id='svg7384' sodipodi:docname='view-refresh-symbolic.svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns='http://www.w3.org/2000/svg' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' inkscape:version='0.48.1 r9760' version='1.1' width='16' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:zoom='1' inkscape:bbox-paths='false' inkscape:pageopacity='1' guidetolerance='10' inkscape:snap-bbox='true' inkscape:window-height='1162' objecttolerance='10' id='namedview88' showborder='false' inkscape:window-x='2191' inkscape:window-y='111' inkscape:cx='9.3351214' inkscape:cy='10.6119' gridtolerance='10' inkscape:snap-bbox-midpoints='false' showguides='true' inkscape:pageshadow='2' pagecolor='#555753' bordercolor='#666666' inkscape:snap-global='true' inkscape:window-maximized='0' inkscape:object-nodes='false' inkscape:snap-nodes='false' inkscape:guide-bbox='true' inkscape:window-width='1595' inkscape:snap-others='false' borderopacity='1' showgrid='false' inkscape:snap-to-guides='true' inkscape:object-paths='false' inkscape:snap-grids='true' inkscape:current-layer='layer12'> - <inkscape:grid enabled='true' spacingx='1px' spacingy='1px' id='grid4866' visible='true' snapvisiblegridlinesonly='true' empspacing='2' type='xygrid'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g id='layer9' transform='translate(-241.0002,-627)' inkscape:label='status' style='display:inline' inkscape:groupmode='layer'/> - <g id='layer10' transform='translate(-241.0002,-627)' inkscape:label='devices' inkscape:groupmode='layer'/> - <g id='layer11' transform='translate(-241.0002,-627)' inkscape:label='apps' inkscape:groupmode='layer'/> - <g id='layer13' transform='translate(-241.0002,-627)' inkscape:label='places' inkscape:groupmode='layer'/> - <g id='layer14' transform='translate(-241.0002,-627)' inkscape:label='mimetypes' inkscape:groupmode='layer'/> - <g id='layer15' transform='translate(-241.0002,-627)' inkscape:label='emblems' style='display:inline' inkscape:groupmode='layer'/> - <g id='g71291' transform='translate(-241.0002,-627)' inkscape:label='emotes' style='display:inline' inkscape:groupmode='layer'/> - <g id='g4953' transform='translate(-241.0002,-627)' inkscape:label='categories' style='display:inline' inkscape:groupmode='layer'/> - <g id='layer12' transform='translate(-241.0002,-627)' inkscape:label='actions' style='display:inline' inkscape:groupmode='layer'> - - <path id='path4597' d='m 253.90645,628.96875 a 1.0001,1.0001 0 0 0 -0.125,0.0312 1.0001,1.0001 0 0 0 -0.78125,1 l 0,1.6875 c -0.38225,-0.57796 -0.84927,-1.08223 -1.40625,-1.5 -1.15563,-0.86677 -2.53199,-1.25227 -3.875,-1.1875 -0.19186,0.009 -0.37223,0.0353 -0.5625,0.0625 -1.52218,0.21741 -2.97815,1.02304 -3.96875,2.34375 -1.98119,2.64144 -1.42267,6.42505 1.21875,8.40625 2.64147,1.98122 6.42504,1.42267 8.40625,-1.21875 A 1.0063276,1.0063276 0 0 0 253.0002,638 a 1.0063276,1.0063276 0 0 0 0,-0.15625 l 0,-0.84375 -0.8125,0 -0.0937,0 a 1.0063276,1.0063276 0 0 0 -0.0937,0 1.0063276,1.0063276 0 0 0 -0.8125,0.40625 c -1.33265,1.77674 -3.81702,2.11387 -5.59375,0.78125 -1.77674,-1.33264 -2.1139,-3.81699 -0.78125,-5.59375 1.33265,-1.77674 3.817,-2.11389 5.59375,-0.78125 0.42946,0.32212 0.76954,0.73295 1.03125,1.1875 l -1.4375,0 a 1.0001,1.0001 0 0 0 -1,1 1.0001,1.0001 0 0 0 0,0.21875 l 0,0.78125 0.84375,0 0.15625,0 4,0 1,0 0,-1 0,-4 a 1.0001,1.0001 0 0 0 0,-0.1875 l 0,-0.8125 -0.8125,0 a 1.0001,1.0001 0 0 0 -0.28125,-0.0312 z' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#666666;fill:#666666;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans' inkscape:connector-curvature='0'/> - </g> -</svg> diff --git a/public/themes/default/icons/rss.png b/public/themes/default/icons/rss.png Binary files differdeleted file mode 100644 index c20455af0..000000000 --- a/public/themes/default/icons/rss.png +++ /dev/null diff --git a/public/themes/default/icons/rss.svg b/public/themes/default/icons/rss.svg deleted file mode 100644 index ceaddceee..000000000 --- a/public/themes/default/icons/rss.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='application-rss+xml-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.3.1 r9886' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-paths='false' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer14' inkscape:cx='27.01134' inkscape:cy='1.367038' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='709' inkscape:window-maximized='1' inkscape:window-width='1366' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='-319.9998px' originy='84.00012px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-561,-301.00012)'> - - <path sodipodi:cx='323.0625' sodipodi:cy='97.1875' d='m 325.0625,97.1875 a 2,3.236068 0 1 1 -4,0 2,3.236068 0 1 1 4,0 z' id='path4983' sodipodi:rx='2' sodipodi:ry='3.236068' style='color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.69602728;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.0000007,0,0,0.61803426,241.93747,252.93479)' sodipodi:type='arc'/> - <path inkscape:connector-curvature='0' d='m 563.0002,303 0,1 c 0,0.55016 0.45347,1 1,1 4.97056,0 9,4.02944 9,9 0,0.55016 0.45347,1 1,1 l 1,0 0,-1 c 0,-6.07513 -4.92487,-11 -11,-11 l -1,0 z m 0,4 0,1 c 0,0.55016 0.45347,1 1,1 2.76143,0 5,2.23857 5,5 0,0.55016 0.45347,1 1,1 l 1,0 0,-1 c 0,-3.866 -3.134,-7 -7,-7 l -1,0 z' id='path5814' style='color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33333492;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new'/> - </g> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-561,-301.00012)'/> -</svg> diff --git a/public/themes/default/icons/search.png b/public/themes/default/icons/search.png Binary files differdeleted file mode 100644 index 48e7373c4..000000000 --- a/public/themes/default/icons/search.png +++ /dev/null diff --git a/public/themes/default/icons/search.svg b/public/themes/default/icons/search.svg deleted file mode 100644 index acfb364cc..000000000 --- a/public/themes/default/icons/search.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='folder-saved-search-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.2 r9819' version='1.1' width='16.000002' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-paths='false' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer13' inkscape:cx='-151.02446' inkscape:cy='0.353386' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#555753' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1168' inkscape:window-maximized='1' inkscape:window-width='1600' inkscape:window-x='0' inkscape:window-y='0' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-441.0004,-195)'> - <path inkscape:connector-curvature='0' d='m 447.50781,195.99554 c -3.02886,0 -5.51073,2.47905 -5.51073,5.50447 0,3.02541 2.48187,5.50446 5.51073,5.50446 3.02885,0 5.51072,-2.47905 5.51072,-5.50446 0,-3.02542 -2.48187,-5.50447 -5.51072,-5.50447 z m 0,2.00893 c 1.94735,0 3.49951,1.55039 3.49951,3.49554 0,1.94514 -1.55216,3.49553 -3.49951,3.49553 -1.94736,0 -3.49952,-1.55039 -3.49952,-3.49553 0,-1.94515 1.55216,-3.49554 3.49952,-3.49554 z' id='path5079' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#666666;fill:#666666;fill-opacity:1;stroke:none;stroke-width:1.55467153;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Sans;-inkscape-font-specification:Sans'/> - <path inkscape:connector-curvature='0' d='m 450.8125,204 a 1.0001,1.0001 0 0 0 -0.5,1.71875 l 4,4 a 1.0054782,1.0054782 0 1 0 1.40625,-1.4375 l -4,-4 A 1.0001,1.0001 0 0 0 450.8125,204 z' id='path5081' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#666666;fill:#666666;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Sans;-inkscape-font-specification:Sans'/> - - </g> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-441.0004,-195)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-441.0004,-195)'/> -</svg> diff --git a/public/themes/default/icons/share.png b/public/themes/default/icons/share.png Binary files differdeleted file mode 100644 index 74c4c5dda..000000000 --- a/public/themes/default/icons/share.png +++ /dev/null diff --git a/public/themes/default/icons/share.svg b/public/themes/default/icons/share.svg deleted file mode 100644 index 443321882..000000000 --- a/public/themes/default/icons/share.svg +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='folder-publicshare-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.3.1 r9886' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-paths='false' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer13' inkscape:cx='-55.50224' inkscape:cy='-178.38158' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1381' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='1600' inkscape:window-y='27' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='-340px' originy='-20.999999px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-581.0002,-196)'> - - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8192' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,148.15963,-64.49107)' sodipodi:type='arc'/> - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8194' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,158.12818,-59.49107)' sodipodi:type='arc'/> - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8196' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,158.12818,-69.49107)' sodipodi:type='arc'/> - <path inkscape:connector-curvature='0' d='m 593.625,198.15625 -10.0625,4.875 -1.8125,0.90625 1.8125,0.90625 10.03125,5.0625 0.90625,-1.8125 -8.21875,-4.15625 8.21875,-4 -0.875,-1.78125 z' id='path8198' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-581.0002,-196)'/> -</svg> diff --git a/public/themes/default/icons/starred.png b/public/themes/default/icons/starred.png Binary files differdeleted file mode 100644 index a6c076358..000000000 --- a/public/themes/default/icons/starred.png +++ /dev/null diff --git a/public/themes/default/icons/starred.svg b/public/themes/default/icons/starred.svg deleted file mode 100644 index 0944a7726..000000000 --- a/public/themes/default/icons/starred.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='starred-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='0.50817' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1457' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='86' inkscape:snap-global='true' inkscape:window-y='51' gridtolerance='10' inkscape:window-height='1093' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='19.029058' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='folder-remote' transform='translate(-186.9996,-599)' id='g11910-1'> - - </g> - <path sodipodi:cy='180.96373' sodipodi:r2='3.8276224' transform='matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)' inkscape:rounded='0.2104596' inkscape:flatsided='false' inkscape:transform-center-x='-0.0094346789' inkscape:transform-center-y='-0.69491065' d='m 530.9488,186.70897 c -0.77941,0.55189 -3.15759,-1.90601 -4.11253,-1.9179 -0.95532,-0.0119 -3.39494,2.38585 -4.16096,1.8149 -0.76573,-0.57072 0.83698,-3.59203 0.55319,-4.50391 -0.2839,-0.91223 -3.31818,-2.49151 -3.01189,-3.39647 0.30617,-0.90461 3.67487,-0.31399 4.45442,-0.86567 0.77986,-0.5519 1.3442,-3.92569 2.29952,-3.91404 0.95494,0.0116 1.43421,3.39798 2.19979,3.9689 0.76588,0.57114 4.14893,0.0653 4.43307,0.97746 0.28402,0.9118 -2.78848,2.41405 -3.09488,3.31858 -0.30652,0.90489 1.21999,3.96605 0.44027,4.51815 z' id='path11922-0' sodipodi:type='star' sodipodi:arg1='0.95492637' sodipodi:r1='7.0383992' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate' sodipodi:arg2='1.5829876' inkscape:randomized='0' sodipodi:cx='526.88293' sodipodi:sides='5'/> - </g> - <g inkscape:label='devices' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/tag.png b/public/themes/default/icons/tag.png Binary files differdeleted file mode 100644 index cb1a13833..000000000 --- a/public/themes/default/icons/tag.png +++ /dev/null diff --git a/public/themes/default/icons/tag.svg b/public/themes/default/icons/tag.svg deleted file mode 100644 index 688aca7a4..000000000 --- a/public/themes/default/icons/tag.svg +++ /dev/null @@ -1,134 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - sodipodi:docname="tag.svg" - height="16" - id="svg7384" - inkscape:version="0.48.4 r9939" - version="1.1" - width="16"> - <metadata - id="metadata90"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview - inkscape:bbox-paths="false" - bordercolor="#666666" - borderopacity="1" - inkscape:current-layer="layer12" - inkscape:cx="63.620645" - inkscape:cy="27.761176" - gridtolerance="10" - inkscape:guide-bbox="true" - guidetolerance="10" - id="namedview88" - inkscape:object-nodes="false" - inkscape:object-paths="false" - objecttolerance="10" - pagecolor="#555753" - inkscape:pageopacity="1" - inkscape:pageshadow="2" - showborder="false" - showgrid="false" - showguides="true" - inkscape:snap-bbox="true" - inkscape:snap-bbox-midpoints="false" - inkscape:snap-global="true" - inkscape:snap-grids="true" - inkscape:snap-nodes="false" - inkscape:snap-others="false" - inkscape:snap-to-guides="true" - inkscape:window-height="709" - inkscape:window-maximized="1" - inkscape:window-width="1366" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:zoom="4"> - <inkscape:grid - empspacing="2" - enabled="true" - id="grid4866" - snapvisiblegridlinesonly="true" - spacingx="1px" - spacingy="1px" - type="xygrid" - visible="true" /> - </sodipodi:namedview> - <title - id="title9167">Gnome Symbolic Icon Theme</title> - <defs - id="defs7386" /> - <g - inkscape:groupmode="layer" - id="layer9" - inkscape:label="status" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer10" - inkscape:label="devices" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer11" - inkscape:label="apps" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer13" - inkscape:label="places" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer14" - inkscape:label="mimetypes" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer15" - inkscape:label="emblems" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="g71291" - inkscape:label="emotes" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="g4953" - inkscape:label="categories" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer12" - inkscape:label="actions" - style="display:inline" - transform="translate(-141.0002,-807)"> - <path - inkscape:connector-curvature="0" - d="m 149.0002,809 0,13 4,-4 4,4 c 0.0525,-6.84943 -0.0285,-10.58353 0,-13 z" - id="path12292" - sodipodi:nodetypes="cccccc" - style="fill:#bebebe;fill-opacity:1;stroke:none" /> - </g> -</svg> diff --git a/public/themes/default/icons/unread.png b/public/themes/default/icons/unread.png Binary files differdeleted file mode 100644 index ab6e3fb39..000000000 --- a/public/themes/default/icons/unread.png +++ /dev/null diff --git a/public/themes/default/icons/unread.svg b/public/themes/default/icons/unread.svg deleted file mode 100644 index 9219b5083..000000000 --- a/public/themes/default/icons/unread.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='mail-unread-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='-166.8292' inkscape:current-layer='layer9' inkscape:window-width='1310' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='true' objecttolerance='10' showgrid='false' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='54' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='0' inkscape:snap-global='true' inkscape:window-y='25' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='690' borderopacity='1' guidetolerance='10' inkscape:cx='315.82499' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path d='m 43.78125,301 a 1.0000999,1.0000999 0 0 0 -0.40625,1.78125 l 5,4 0.625,0.5 0.625,-0.5 5,-4 a 1.0004882,1.0004882 0 1 0 -1.25,-1.5625 l -4.375,3.5 -4.375,-3.5 A 1.0000999,1.0000999 0 0 0 43.78125,301 z' id='path35600' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.99999976;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - <path d='M 42.90625,300 A 1.0001,1.0001 0 0 0 42,301 l 0,9 a 1.0001,1.0001 0 0 0 1,1 l 12,0 a 1.0001,1.0001 0 0 0 1,-1 l 0,-9 a 1.0001,1.0001 0 0 0 -1,-1 l -12,0 a 1.0001,1.0001 0 0 0 -0.09375,0 z M 44,302 l 10,0 0,7 -10,0 0,-7 z' id='rect35604' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - </g> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> -</svg> diff --git a/public/themes/default/icons/up.png b/public/themes/default/icons/up.png Binary files differdeleted file mode 100644 index 8bfc2fb13..000000000 --- a/public/themes/default/icons/up.png +++ /dev/null diff --git a/public/themes/default/icons/up.svg b/public/themes/default/icons/up.svg deleted file mode 100644 index dab31a20d..000000000 --- a/public/themes/default/icons/up.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='go-up-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.4 r9939' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-nodes='false' inkscape:bbox-paths='true' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer12' inkscape:cx='-1.351226' inkscape:cy='9.99302' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1408' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='0' inkscape:window-y='0' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='40px' originy='530px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-201.0002,-747)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-201.0002,-747)'> - - <path inkscape:connector-curvature='0' d='m 215.03152,758.99677 0,-1.00001 c -9.1e-4,-0.0111 5.9e-4,-0.021 -9e-5,-0.0312 -0.0112,-0.25496 -0.12835,-0.50994 -0.31251,-0.6875 l -5.71875,-6.29767 -5.71875,6.29773 c -0.18821,0.1881 -0.28121,0.45346 -0.28122,0.71875 l 0,1.00001 1,0 c 0.26531,-7e-5 0.53059,-0.0931 0.71873,-0.28131 l 4.28125,-4.82896 4.28127,4.82896 c 0.19464,0.21073 0.46925,0.30315 0.74998,0.2813 z' id='path10839-9-9-5-9-1-0' sodipodi:nodetypes='ccsccccccccccc' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.78124988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new;font-family:Andale Mono;-inkscape-font-specification:Andale Mono'/> - </g> -</svg> diff --git a/public/themes/flat-design/fonts/openSans.woff b/public/themes/flat-design/fonts/openSans.woff Binary files differdeleted file mode 100644 index 55b25f867..000000000 --- a/public/themes/flat-design/fonts/openSans.woff +++ /dev/null diff --git a/public/themes/flat-design/freshrss.css b/public/themes/flat-design/freshrss.css deleted file mode 100644 index 49533f017..000000000 --- a/public/themes/flat-design/freshrss.css +++ /dev/null @@ -1,754 +0,0 @@ -/* STRUCTURE */ -body { - background: #fafafa; -} - -.header { - display: table; - width: 100%; - table-layout: fixed; - background: #ecf0f1; -} - .header > .item { - display: table-cell; - padding: 10px 0; - vertical-align: middle; - text-align: center; - } - .header > .item.title { - width: 250px; - } - .header > .item.title .logo { - display: inline-block; - width: 32px; - padding: 10px; - } - .header > .item.title h1 { - display: inline-block; - margin: 0; - vertical-align: middle; - } - .header > .item.title a:hover { - text-decoration: none; - } - .header > .item.search input { - width: 200px; - transition: width 200ms linear; - } - .header .item.search input:focus { - width: 300px; - } - .header > .item.configure { - width: 100px; - } - -#global { - display: table; - width: 100%; - height: 100%; - table-layout: fixed; -} - .aside { - display: table-cell; - height: 100%; - width: 250px; - vertical-align: top; - background: #ecf0f1; - } - .aside .nav-form input { - width: 180px; - } - .aside.aside_flux { - padding: 10px 0 40px; - } - .aside.aside_feed .nav-form input { - width: 140px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu { - right: -20px; - } - .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { - right: 33px; - } - - .nav-login { - display: none; - } - - .nav_menu { - width: 100%; - text-align: center; - padding: 5px 0; - } - -.favicon { - height: 16px; - width: 16px; -} - -.categories { - margin: 0; - padding: 0; - text-align: center; - list-style: none; -} - .categories .all, - .categories .favorites, - .categories .category { - display: block; - padding: 5px 0; - width: 220px; - margin: 5px auto; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .categories .all .btn, - .categories .favorites .btn, - .categories .category .btn:first-child { - width: 195px; - position: relative; - } - .categories .feeds { - width: 220px; - margin: 0 auto; - list-style: none; - } - .categories .feeds .item.active:after { - content: "⇢"; - line-height: 35px; - float: right; - } - .categories .feeds .item.empty .feed { - color: #e67e22; - } - .categories .feeds .item.error .feed { - color: #BD362F; - } - .categories .feeds .item .feed { - display: inline-block; - margin: 0; - width: 165px; - line-height: 35px; - font-size: 90%; - vertical-align: middle; - text-align: left; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .categories .feeds .dropdown .dropdown-menu { - left: 0; - } - .categories .feeds .dropdown .dropdown-menu:after { - left: 2px; - } - .categories .feeds .item .dropdown-toggle i { - background-image: none; - } - .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle i, - .categories .feeds .item:hover .dropdown-toggle i, - .categories .feeds .item.active .dropdown-toggle i { - background-image: url("icons/configure.png"); - background-image: url("icons/configure.svg"); - background-color: #95a5a6; - border-radius: 3px; - } - .categories .notRead { - position: absolute; - top: 5px; right: 0px; - padding: 0 5px; - color: #fff; - font-size: 90%; - background: #3498DB; - border-left: 3px solid #2980B9; - border-radius: 5px 0 0 5px; - } - .categories .btn:hover .notRead, - .categories .btn.active .notRead { - background: #2980B9; - border-left: 3px solid #3498DB; - } - -.post { - padding: 10px 50px; -} - .post form { - margin: 10px 0; - } - -.day { - display: inline; - min-height: 50px; - padding: 5px 15px; - font-size: 130%; - font-weight: bold; - line-height: 50px; - background: #ecf0f1; - border-radius: 0 20px 20px 0; -} - .day .name { - position: absolute; - right: 0; - width: 50%; - height: 1.5em; - padding: 0 10px 0 0; - overflow: hidden; - color: #aab; - font-size: 1.8em; - opacity: .3; - font-style: italic; - text-align: right; - white-space: nowrap; - text-overflow: ellipsis; - z-index: -10; - } - -.flux { -} - .flux.active { - border-left: 3px solid #3498db; - background: #fff; - } - .flux.not_read { - border-left: 3px solid #FF5300; - background: #FFF3ED; - } - .flux.favorite { - border-left: 3px solid #FFC300; - background: #FFF6DA; - } - - .flux_header { - height: 25px; - font-size: 12px; - line-height: 25px; - border-top: 1px solid #ecf0f1; - } - .item.manage { - width: 80px; - white-space: nowrap; - font-size: 0px; - text-align: center; - } - .item.manage .read { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/read.png") center center no-repeat; - background: url("icons/read.svg") center center no-repeat; - vertical-align: middle; - } - .item.manage .read:hover { - text-decoration: none; - } - .flux.not_read .item.manage .read { - background: url("icons/unread.png") center center no-repeat; - background: url("icons/unread.svg") center center no-repeat; - } - .item.manage .bookmark { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/non-starred.png") center center no-repeat; - background: url("icons/non-starred.svg") center center no-repeat; - vertical-align: middle; - } - .item.manage .bookmark:hover { - text-decoration: none; - } - .flux.favorite .item.manage .bookmark { - background: url("icons/starred.png") center center no-repeat; - background: url("icons/starred.svg") center center no-repeat; - } - .flux_header .item.website { - width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 40px; - } - .flux_header .item.website .favicon { - padding: 5px; - } - .flux_header .item.website a { - display: block; - height: 40px; - } - .flux_header .item.title { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - cursor: pointer; - } - .flux_header .item.title a { - color: #333; - outline: none; - } - .flux_header .item.title a:hover { - text-decoration: none - } - .flux.not_read .flux_header .item.title { - font-weight: bold; - } - .item.date { - width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - text-align: right; - font-size: 10px; - color: #666; - cursor: pointer; - } - .item.link { - width: 40px; - text-align: center; - } - .item.link a { - display: inline-block; - width: 40px; - height: 40px; - background: url("icons/link.png") center center no-repeat; - background: url("icons/link.svg") center center no-repeat; - vertical-align: middle; - } - .item.link a:hover { - text-decoration: none; - } - -#stream.reader .flux { - position: relative; - padding: 0 0 30px; - border: none; - background: #ecf0f1; - color: #34495e; - font-size: 120%; -} - #stream.reader .flux a { - } - #stream.reader .flux .author { - margin: 0 0 10px; - font-size: 90%; - color: #aaa; - } - -#stream.global { - text-align: center; -} - #stream.global .category { - display: inline-block; - width: 280px; - margin: 20px 10px; - vertical-align: top; - border: 1px solid #aaa; - border-radius: 5px; - text-align: left; - box-shadow: 0 0 5px #bbb; - } - #stream.global .cat_header { - height: 35px; - padding: 0 10px; - background: #eee; - border-bottom: 1px solid #aaa; - border-radius: 5px 5px 0 0; - line-height: 35px; - font-size: 120%; - } - #stream.global .cat_header a { - color: #333; - text-shadow: 0 -1px 0px #aaa; - } - #stream.global .category .feeds { - max-height: 250px; - margin: 0; - list-style: none; - overflow: auto; - } - #stream.global .category .feeds .item { - padding: 2px 10px; - font-size: 90%; - } - -.content { - min-height: 300px; - max-width: 550px; - margin: 0 auto; - padding: 20px 10px; - line-height: 170%; - word-wrap: break-word; -} - .content .title { - margin: 0 0 5px; - } - .content h1, .content h2, .content h3 { - margin: 20px 0 5px; - } - - .content p { - margin: 0 0 20px; - } - .content img.big { - display: block; - margin: 10px auto; - } - .content figure img.big { - margin: 0; - } - .content hr { - margin: 30px 0; - height: 1px; - background: #ddd; - border: 0; - } - .content pre { - margin: 10px auto; - padding: 10px; - overflow: auto; - background: #000; - color: #fff; - font-size: 110%; - } - .content q, .content blockquote { - display: block; - margin: 5px 0; - padding: 5px 20px; - font-style: italic; - border-left: 4px solid #ccc; - color: #666; - } - .content blockquote p { - margin: 0; - } - -#panel { - display: none; - position: fixed; - top: 10px; bottom: 10px; - left: 100px; right: 100px; - overflow: auto; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 5px; -} - #panel .close { - position: fixed; - top: 10px; right: 0px; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border-radius: 3px; - text-align: center; - line-height: 24px; - background: #95a5a6; - } - #panel .close:hover { - background: #7f8c8d; - } - -#overlay { - display: none; - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - background: rgba(0, 0, 0, 0.9); -} - -.flux_content .bottom { - font-size: 90%; - text-align: center; -} - -/*** PAGINATION ***/ -.pagination { - display: table; - width: 100%; - margin: 0; - background: #ecf0f1; - text-align: center; - color: #000; - font-size: 80%; - line-height: 200%; - table-layout: fixed; - font-weight: bold; -} - .pagination .item { - display: table-cell; - line-height: 40px; - vertical-align: top; - } - .pagination .item.pager-current { - font-weight: bold; - font-size: 140%; - color: #ecf0f1; - background: #34495e; - } - .pagination .item.pager-first, - .pagination .item.pager-previous, - .pagination .item.pager-next, - .pagination .item.pager-last { - width: 100px; - } - .pagination .item a { - display: block; - color: #000; - font-weight: bold; - line-height: 40px; - } - .pagination .item a:hover { - color: #ecf0f1; - background: #34495e; - text-decoration: none; - } - -.nav_entries { - display: table; - width: 250px; - height: 40px; - position: fixed; - bottom: 0; - left: 0; - margin: 0; - background: #34495e; - text-align: center; - line-height: 40px; - table-layout: fixed; -} - .nav_entries .item { - display: table-cell; - width: 30%; - } - .nav_entries .item a { - display: block; - } - .nav_entries .item .icon.i_up { - margin: 5px 0 0; - vertical-align: top; - } - -.pagination .loading, -.pagination a:hover.loading { - background: url("loader.gif") center center no-repeat #34495e; - font-size: 0; -} - -.bigMarkAsRead { - background: #ecf0f1; - cursor: pointer; - height: 300px; - text-shadow: 0 -1px 0 #aaa; -} -.bigMarkAsRead:hover { - background: #34495e; - color: #fff; -} -.bigTick { - font-size: 72pt; - margin: 75px 0 10px 0; -} - -/*** NOTIFICATION ***/ -.notification { - position: fixed; - top: 10px; - left: 25%; right: 25%; - min-height: 30px; - padding: 10px; - line-height: 30px; - text-align: center; - border-radius: 3px; - background: #ddd; - color: #666; - font-weight: bold; -} - .notification.good { - background: #1abc9c; - color: #fff; - } - .notification.bad { - background: #e74c3c; - color: #fff; - } - .notification a.close { - display: inline-block; - width: 16px; - height: 16px; - float: right; - margin: -16px -16px 0 0; - padding: 5px; - border-radius: 3px; - line-height: 16px; - } - .notification.good a.close { - background: #1abc9c; - } - .notification.bad a.close { - background: #e74c3c; - } - -.toggle_aside, .btn.toggle_aside { - display: none; -} - -.actualizeProgress { - position: fixed; - top: 10px; - left: 25%; right: 25%; - padding: 5px; - background: #3498db; - color: #fff; - text-align: center; - border-radius: 3px; - font-weight: bold; -} - .actualizeProgress progress { - max-width: 100%; - width: 250px; - height: 15px; - vertical-align: middle; - background: #fff; - border: none; - } - .actualizeProgress .progress { - color: #ecf0f1; - } - -.logs { - border: 1px solid #34495e; -} - .logs .log { - margin: 10px 0; - padding: 5px 2%; - overflow: auto; - background: #fafafa; - color: #666; - font-size: 90%; - } - .logs .log .date { - margin: 0 10px 0 0; - padding: 5px 10px; - border-radius: 20px; - } - .logs .log.error .date { - background: #e74c3c; - color: #fff; - } - .logs .log.warning .date { - background: #f39c12; - } - .logs .log.notice .date { - background: #ecf0f1; - } - -@media(max-width: 840px) { - .header, - .aside .btn-important, - .aside .feeds .dropdown, - .flux_header .item.website span, - .item.date { - display: none; - } - .flux_header .item.website { - width: 40px; - text-align: center; - } - .flux_header .item.website .favicon { - padding: 12px; - } - - .nav-login { - display: block; - } - - .content { - font-size: 120%; - } - - .pagination { - margin: 0 0 40px; - } - .pagination .pager-previous, .pagination .pager-next { - width: 100px; - } - - .toggle_aside, .btn.toggle_aside { - display: inline-block; - } - .aside { - position: fixed; - top: 0; left: 0; - width: 0; - overflow: hidden; - z-index: 10; - transition: width 200ms linear; - background: #ecf0f1; - } - .aside.aside_flux { - padding: 10px 0 0; - } - .aside:target { - width: 80%; - border-right: 1px solid #aaa; - overflow: auto; - } - .aside .toggle_aside { - position: absolute; - right: 10px; - display: inline-block; - width: 26px; - height: 26px; - margin: 0 10px 0 0; - border-radius: 3px; - text-align: center; - line-height: 24px; - background: #95a5a6; - } - .aside .toggle_aside:hover { - background: #7f8c8d; - } - .aside .categories { - margin: 30px 0; - } - - .nav_entries { - width: 100%; - } - - .nav_menu .btn { - margin: 5px 10px; - } - .nav_menu .stick { - margin: 0 10px; - } - .nav_menu .stick .btn { - margin: 5px 0; - } - - #panel { - left: 5px; right: 5px; - } - - .day .date { - display: none; - } - .day .name { - height: 2.6em; - font-size: 1em; - text-shadow: none; - } - - .notification, - .actualizeProgress { - left: 10px; - right: 10px; - } -} - -/*** FALLBACK ***/ -.dropdown .dropdown-menu:after { - -moz-transform: rotate(45deg); - -webkit-transform: rotate(45deg); - -ms-transform: rotate(45deg); -} diff --git a/public/themes/flat-design/global.css b/public/themes/flat-design/global.css deleted file mode 100644 index 0824d1bb3..000000000 --- a/public/themes/flat-design/global.css +++ /dev/null @@ -1,576 +0,0 @@ -/* FONTS */ -@font-face { - font-family: "OpenSans"; - src: url("fonts/openSans.woff") format("woff"); -} - - -* { - margin: 0; - padding: 0; -} -html, body { - height: 100%; - font-size: 95%; - font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", "sans-serif"; -} - -/* LIENS */ -a { - color: #2980b9; - text-decoration: none; -} - a:hover { - text-decoration: underline; - } - -/* LISTES */ -ul, ol, dl { - margin: 10px 0 10px 30px; - line-height: 190%; -} - dd { - margin: 0 0 10px 30px; - } - -/* TITRES */ -h1, h2, h3 { - min-height: 40px; - margin: 15px 0 5px; - line-height: 40px; -} - -/* IMG */ -figure { - margin: 5px 0 10px; - text-align: center; -} - figcaption { - display: inline-block; - padding: 3px 20px; - color: #999; - font-style: italic; - border-bottom: 1px solid #ccc; - } -img { - height: auto; - max-width: 100%; - vertical-align: middle; -} - a img { - border: none; - } - -/* VIDEOS */ -iframe, embed, object, video { - max-width: 100%; -} - -/* FORMULAIRES */ -legend { - display: inline-block; - margin: 20px 0 5px; - padding: 5px 20px; - font-size: 150%; - clear: both; - background: #ecf0f1; - border-radius: 20px; -} -label { - display: block; - min-height: 25px; - padding: 5px 0; - font-size: 12px; - line-height: 25px; - cursor: pointer; - font-weight: bold; - color: #444; -} -input, select, textarea { - display: inline-block; - max-width: 100%; - min-height: 25px; - padding: 5px; - background: #FFF; - border: none; - border-bottom: 3px solid #ddd; - color: #666; - line-height: 25px; - vertical-align: middle; - border-radius: 5px; -} - input[type="radio"], - input[type="checkbox"] { - width: 15px !important; - min-height: 15px !important; - } - input:focus, select:focus, textarea:focus { - color: #333; - border-color: #2980b9; - } - -.form-group { - margin: 5px 0; - border: 1px solid transparent; -} - .form-group:after { - content: ""; - display: block; - clear: both; - } - .form-group:hover { - background: #fff; - border: 1px solid #eee; - border-radius: 3px; - } - .form-group.form-actions { - min-width: 250px; - margin: 20px 0; - padding: 5px 0; - background: #ecf0f1; - border-top: 3px solid #bdc3c7; - border-radius: 5px 5px 0 0; - } - .form-group.form-actions .btn { - margin: 0 10px; - } - .form-group .group-name { - display: block; - float: left; - width: 200px; - padding: 10px 0; - text-align: right; - } - .form-group .group-controls { - min-width: 250px; - min-height: 25px; - margin: 0 0 0 220px; - padding: 5px 0; - } - .form-group .group-controls label { - font-weight: normal; - font-size: 14px; - color: #000; - } - .form-group .group-controls .control { - display: block; - min-height: 30px; - padding: 5px 0; - line-height: 25px; - font-size: 14px; - } - -.stick { - display: inline-block; - white-space: nowrap; - font-size: 0px; - vertical-align: middle; -} - .stick .btn, - .stick input { - font-size: 14px; - border-radius: 0; - } - .stick .btn:first-child, - .stick input:first-child { - border-radius: 5px 0 0 5px; - } - .stick .btn:last-child, - .stick input:last-child, - .stick .btn + .dropdown > .btn { - border-radius: 0 5px 5px 0; - } - .stick .btn + .dropdown a { - font-size: 12px; - } - -.btn { - display: inline-block; - min-height: 38px; - min-width: 18px; - padding: 5px 10px; - background: #3498db; - border-radius: 5px; - border: none; - border-bottom: 3px solid #2980b9; - color: #fff; - line-height: 20px; - vertical-align: middle; - cursor: pointer; - overflow: hidden; -} - a.btn { - min-height: 25px; - line-height: 25px; - } - .btn.active, - .btn:active, - .btn:hover, - .dropdown-target:target ~ .btn.dropdown-toggle { - background: #2980b9; - text-decoration: none; - } - - .btn.btn-important { - background: #e67e22; - color: #fff; - border-bottom: 3px solid #d35400; - } - .btn.btn-important:active, - .btn.btn-important:hover { - background: #d35400; - } - - .btn.btn-attention { - background: #e74c3c; - color: #fff; - border-bottom: 3px solid #c0392b; - } - .btn.btn-attention:hover, - .btn.btn-attention:active { - background: #c0392b; - } - -/* NAVIGATION */ -.nav.nav-list { - border-right: 1px solid #ecf0f1; -} -.nav.nav-list .nav-header, -.nav.nav-list .item { - display: block; - height: 35px; - line-height: 35px; - margin: 5px 0; -} - .nav.nav-list .item:hover, - .nav.nav-list .item.active { - background: #2980b9; - color: #fff; - } - .nav.nav-list .item:hover a, - .nav.nav-list .item.active a { - color: #fff; - } - .nav.nav-list .disable { - color: #aaa; - background: #fafafa; - text-align: center; - } - .nav.nav-list .item > * { - display: block; - padding: 0 10px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - .nav.nav-list a:hover { - text-decoration: none; - } - .nav.nav-list .item.error a { - color: #BD362F; - } - .nav.nav-list .item:hover.error a, - .nav.nav-list .item.active.error a { - color: #fff; - background: #BD362F; - } - .nav.nav-list .item.empty a { - color: #f39c12; - } - .nav.nav-list .item:hover.empty a, - .nav.nav-list .item.active.empty a { - color: #fff; - background: #f39c12; - } - - .nav.nav-list .nav-header { - padding: 0 10px; - margin: 0; - color: #fff; - background: #34495e; - font-weight: bold; - } - .nav.nav-list .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #ddd; - } - - .nav.nav-list .nav-form { - padding: 3px; - text-align: center; - } - -.nav-head { - display: block; - margin: 0; - background: #34495e; - color: #fff; - text-align: right; -} - .nav-head a { - color: #fff; - } - .nav-head .item { - display: inline-block; - padding: 5px 10px; - } - -/* HORIZONTAL-LIST */ -.horizontal-list { - display: table; - table-layout: fixed; - margin: 0; - padding: 0; - width: 100%; -} - .horizontal-list .item { - display: table-cell; - vertical-align: middle; - } - -/* DROPDOWN */ -.dropdown { - position: relative; - display: inline-block; -} - .dropdown .dropdown-target { - display: none; - } - - .dropdown .dropdown-menu { - display: none; - min-width: 200px; - margin: 5px 0 0; - padding: 5px 0; - position: absolute; - right: 0px; - background: #fff; - border: 1px solid #95a5a6; - border-radius: 3px; - text-align: left; - } - .dropdown .dropdown-menu:after { - content: ""; - position: absolute; - top: -6px; - right: 13px; - width: 10px; - height: 10px; - background: #fff; - border-top: 1px solid #95a5a6; - border-left: 1px solid #95a5a6; - z-index: -10; - transform: rotate(45deg); - } - .dropdown .dropdown-menu .dropdown-header { - display: block; - padding: 0 5px; - color: #34495e; - font-weight: bold; - font-size: 14px; - line-height: 30px; - } - .dropdown .dropdown-menu .item { - display: block; - height: 30px; - font-size: 90%; - line-height: 30px; - } - .dropdown .dropdown-menu .item > * { - display: block; - padding: 0 25px; - line-height: 30px; - } - .dropdown .dropdown-menu .item:hover { - background: #2980b9; - color: #fff; - } - .dropdown .dropdown-menu .item:hover > * { - color: #fff; - text-decoration: none; - } - .dropdown .dropdown-menu .input { - display: block; - height: 40px; - font-size: 90%; - line-height: 30px; - } - .dropdown .dropdown-menu label { - font-weight: normal; - } - .dropdown .dropdown-menu .input select, - .dropdown .dropdown-menu .input input { - display: block; - height: 20px; - width: 95%; - margin: auto; - padding: 2px 5px; - border-radius: 3px; - } - .dropdown .dropdown-menu .input select { - width: 70%; - height: auto; - } - .dropdown .dropdown-menu .separator { - display: block; - height: 0; - margin: 5px 0; - border-bottom: 1px solid #95a5a6; - } - .dropdown .dropdown-target:target ~ .dropdown-menu { - display: block; - z-index: 10; - } - .dropdown .dropdown-close { - display: inline; - } - .dropdown .dropdown-close a { - position: fixed; - top: 0; bottom: 0; - left: 0; right: 0; - display: block; - z-index: -10; - } - -/* ALERTS */ -.alert { - display: block; - width: 90%; - margin: 15px auto; - padding: 10px 15px; - background: #f4f4f4; - border: 1px solid #ccc; - border-right: 1px solid #aaa; - border-bottom: 1px solid #aaa; - border-radius: 5px; - color: #aaa; - text-shadow: 0 0 1px #eee; -} - .alert .alert-head { - margin: 0; - font-weight: bold; - font-size: 110%; - } - .alert.alert-warn { - background: #ffe; - border: 1px solid #eeb; - color: #c95; - } - .alert.alert-success { - background: #dfd; - border: 1px solid #cec; - color: #484; - } - .alert.alert-error { - background: #fdd; - border: 1px solid #ecc; - color: #844; - } - -/* ICONES */ -.icon { - display: inline-block; - width: 16px; - height: 16px; - vertical-align: middle; - line-height: 16px; - background: center center no-repeat; -} - .icon.i_refresh { - background-image: url("icons/refresh.png"); - background-image: url("icons/refresh.svg"); - } - .icon.i_bookmark { - background-image: url("icons/starred.png"); - background-image: url("icons/starred.svg"); - } - .icon.i_not_bookmark { - background-image: url("icons/unstarred.png"); - background-image: url("icons/unstarred.svg"); - } - .icon.i_read { - background-image: url("icons/read.png"); - background-image: url("icons/read.svg"); - } - .icon.i_unread { - background-image: url("icons/unread.png"); - background-image: url("icons/unread.svg"); - } - .icon.i_all { - background-image: url("icons/all.png"); - background-image: url("icons/all.svg"); - } - .icon.i_close { - background-image: url("icons/close.png"); - background-image: url("icons/close.svg"); - } - .icon.i_search { - background-image: url("icons/search.png"); - background-image: url("icons/search.svg"); - } - .icon.i_configure { - background-image: url("icons/configure.png"); - background-image: url("icons/configure.svg"); - } - .icon.i_login { - background-image: url("icons/login.png"); - background-image: url("icons/login.svg"); - } - .icon.i_logout { - background-image: url("icons/logout.png"); - background-image: url("icons/logout.svg"); - } - .icon.i_add { - background-image: url("icons/add.png"); - background-image: url("icons/add.svg"); - } - .icon.i_link { - background-image: url("icons/link.png"); - background-image: url("icons/link.svg"); - } - .icon.i_down { - background-image: url("icons/down.png"); - background-image: url("icons/down.svg"); - } - .icon.i_up { - background-image: url("icons/up.png"); - background-image: url("icons/up.svg"); - } - .icon.i_next { - background-image: url("icons/next.png"); - background-image: url("icons/next.svg"); - } - .icon.i_prev { - background-image: url("icons/previous.png"); - background-image: url("icons/previous.svg"); - } - .icon.i_help { - background-image: url("icons/help.png"); - background-image: url("icons/help.svg"); - } - .icon.i_note { - background-image: url("icons/note.png"); - background-image: url("icons/note.svg"); - } - .icon.i_note_empty { - background-image: url("icons/note_empty.png"); - background-image: url("icons/note_empty.svg"); - } - .icon.i_category { - background-image: url("icons/category.png"); - background-image: url("icons/category.svg"); - } - .icon.i_rss { - background-image: url("icons/rss.png"); - background-image: url("icons/rss.svg"); - } - .icon.i_share { - background-image: url("icons/share.png"); - background-image: url("icons/share.svg"); - } - .icon.i_tag { - background-image: url("icons/tag.png"); - background-image: url("icons/tag.svg"); - } diff --git a/public/themes/flat-design/icons/add.png b/public/themes/flat-design/icons/add.png Binary files differdeleted file mode 100644 index 237de3e15..000000000 --- a/public/themes/flat-design/icons/add.png +++ /dev/null diff --git a/public/themes/flat-design/icons/all.png b/public/themes/flat-design/icons/all.png Binary files differdeleted file mode 100644 index 6d8338ac7..000000000 --- a/public/themes/flat-design/icons/all.png +++ /dev/null diff --git a/public/themes/flat-design/icons/category.png b/public/themes/flat-design/icons/category.png Binary files differdeleted file mode 100644 index 9243650da..000000000 --- a/public/themes/flat-design/icons/category.png +++ /dev/null diff --git a/public/themes/flat-design/icons/close.png b/public/themes/flat-design/icons/close.png Binary files differdeleted file mode 100644 index 1f91a4f4e..000000000 --- a/public/themes/flat-design/icons/close.png +++ /dev/null diff --git a/public/themes/flat-design/icons/configure.png b/public/themes/flat-design/icons/configure.png Binary files differdeleted file mode 100644 index 982c24619..000000000 --- a/public/themes/flat-design/icons/configure.png +++ /dev/null diff --git a/public/themes/flat-design/icons/down.png b/public/themes/flat-design/icons/down.png Binary files differdeleted file mode 100644 index 4603976ac..000000000 --- a/public/themes/flat-design/icons/down.png +++ /dev/null diff --git a/public/themes/flat-design/icons/help.png b/public/themes/flat-design/icons/help.png Binary files differdeleted file mode 100644 index aa63c5411..000000000 --- a/public/themes/flat-design/icons/help.png +++ /dev/null diff --git a/public/themes/flat-design/icons/help.svg b/public/themes/flat-design/icons/help.svg deleted file mode 100644 index b103ec46d..000000000 --- a/public/themes/flat-design/icons/help.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='help-browser-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='true' inkscape:cy='-218.16054' inkscape:current-layer='layer11' inkscape:window-width='1920' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='true' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='0' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='1' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='1021' borderopacity='1' guidetolerance='10' inkscape:snap-bbox-midpoints='false' inkscape:cx='-124.56688' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'> - <path d='m 190,490 c -4.4147,0 -8,3.5853 -8,8 0,4.4147 3.5853,8 8,8 4.4147,0 8,-3.5853 8,-8 0,-4.4147 -3.5853,-8 -8,-8 z m 0,2 c 3.3413,0 6,2.6587 6,6 0,3.3413 -2.6587,6 -6,6 -3.3413,0 -6,-2.6587 -6,-6 0,-3.3413 2.6587,-6 6,-6 z' id='rect11749-5-0-3' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - - <path d='M 189.34375,495 C 188.05763,495 187,496.05763 187,497.34375 l 0,1.3125 c 0,1.28612 1.05763,2.34375 2.34375,2.34375 l 1.3125,0 C 191.94237,501 193,499.94237 193,498.65625 l 0,-1.3125 C 193,496.05763 191.94237,495 190.65625,495 l -1.3125,0 z m 0,1 1.3125,0 c 0.74942,0 1.34375,0.59433 1.34375,1.34375 l 0,1.3125 C 192,499.40567 191.40567,500 190.65625,500 l -1.3125,0 C 188.59433,500 188,499.40567 188,498.65625 l 0,-1.3125 C 188,496.59433 188.59433,496 189.34375,496 z' id='rect11749-5-0-3-3' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - <path inkscape:connector-curvature='0' d='m 186.71875,491.4375 c -1.51031,0.6073 -2.6811,1.7985 -3.28125,3.3125 l 3.75,1.875 c 0.25196,-0.64029 0.74249,-1.17056 1.375,-1.4375 l -1.84375,-3.75 z m 6.5625,0 -1.84375,3.75 c 0.63251,0.26694 1.12304,0.79721 1.375,1.4375 l 3.75,-1.875 c -0.60015,-1.514 -1.77094,-2.7052 -3.28125,-3.3125 z m -6.09375,8 -3.75,1.875 c 0.60709,1.48862 1.78896,2.64995 3.28125,3.25 l 1.875,-3.75 c -0.62682,-0.25556 -1.14334,-0.75203 -1.40625,-1.375 z m 5.625,0 c -0.26291,0.62297 -0.77943,1.11944 -1.40625,1.375 l 1.875,3.75 c 1.49229,-0.60005 2.67416,-1.76138 3.28125,-3.25 l -3.75,-1.875 z' id='path4624' style='fill:#bebebe;fill-opacity:1;stroke:none'/> - </g> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-182,-490)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/link.png b/public/themes/flat-design/icons/link.png Binary files differdeleted file mode 100644 index de2b187d7..000000000 --- a/public/themes/flat-design/icons/link.png +++ /dev/null diff --git a/public/themes/flat-design/icons/link.svg b/public/themes/flat-design/icons/link.svg deleted file mode 100644 index ddc8e0706..000000000 --- a/public/themes/flat-design/icons/link.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' id='svg7384' sodipodi:docname='web-browser-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' width='16' xmlns='http://www.w3.org/2000/svg' height='16.00366'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='false' inkscape:current-layer='layer11' inkscape:bbox-paths='false' inkscape:pageshadow='2' inkscape:snap-bbox='true' inkscape:pageopacity='1' inkscape:guide-bbox='true' pagecolor='#555753' bordercolor='#666666' showguides='true' inkscape:snap-bbox-midpoints='false' inkscape:window-maximized='0' inkscape:snap-grids='true' inkscape:window-width='1595' id='namedview88' inkscape:window-x='2191' inkscape:window-y='111' gridtolerance='10' borderopacity='1' showgrid='false' inkscape:cx='21.026784' inkscape:cy='2.9913602' inkscape:snap-nodes='false' inkscape:window-height='1162' inkscape:snap-global='true' inkscape:object-nodes='false' objecttolerance='10' inkscape:snap-others='false' inkscape:zoom='1' guidetolerance='10' inkscape:snap-to-guides='true' showborder='false'> - <inkscape:grid enabled='true' type='xygrid' id='grid4866' visible='true' snapvisiblegridlinesonly='true' empspacing='2' spacingx='1px' spacingy='1px'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer9' inkscape:label='status' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-183,-529)'> - - <path style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:block;overflow:visible;enable-background:accumulate' sodipodi:nodetypes='cccccccc' id='path6242' inkscape:connector-curvature='0' d='m 191.0002,533.84553 0,10.38049 -2.34399,-2.28818 -1.33941,2.73465 c -0.32808,0.73962 -2.03368,0.14492 -1.5487,-0.84412 l 1.32547,-2.83928 -2.95789,0 6.86452,-7.14356 z'/> - <path style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans' sodipodi:nodetypes='csccssccsssc' id='path23405' inkscape:connector-curvature='0' d='m 190.15645,530.0625 c -3.82659,0.46006 -6.57883,3.95775 -6.09375,7.78125 0.13127,1.03473 0.29377,1.38184 0.29377,1.38184 l 1.67498,-1.63184 c -0.33104,-2.75343 1.62156,-5.23146 4.375,-5.5625 2.75344,-0.33104 5.23146,1.62156 5.5625,4.375 0.31355,2.60795 -1.39127,5.02493 -3.96875,5.53125 l 0.0312,2 c 0,0 0.52086,-0.1059 0.62354,-0.13097 3.41561,-0.83385 5.70627,-4.1273 5.28271,-7.65028 -0.46006,-3.8266 -3.95466,-6.55381 -7.78125,-6.09375 z'/> - <path style='opacity:0.3;color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate' id='path11289' inkscape:connector-curvature='0' d='m 187.11293,536.81497 0,-0.20574 -0.19826,0.0186 c 0.0165,-0.13095 0.0329,-0.26167 0.0496,-0.3926 l -0.11578,0 -0.11556,0.14959 -0.11578,0.0559 -0.1653,-0.0932 -0.0165,-0.20575 0.0331,-0.22438 0.24798,-0.18688 0.19826,0 0.0329,-0.11229 0.24786,0.0559 0.18183,0.2246 0.0331,-0.37419 0.31401,-0.26167 0.11567,-0.28055 0.23133,-0.0934 0.1322,-0.18688 0.29738,-0.0564 0.14885,-0.22415 c -0.14874,0 -0.29749,0 -0.44623,0 l 0.28094,-0.13095 0.19814,0 0.28106,-0.0937 0.0331,-0.11186 -0.0992,-0.0937 -0.11567,-0.0375 0.0331,-0.11208 -0.0826,-0.16822 -0.19837,0.0746 0.0331,-0.14947 -0.23134,-0.13096 -0.18171,0.3177 0.0165,0.11229 -0.18171,0.075 -0.11578,0.24302 -0.0495,-0.22438 -0.31402,-0.13095 -0.0496,-0.16822 0.41315,-0.24325 0.18182,-0.16822 0.0165,-0.20563 -0.0991,-0.0562 -0.13219,-0.0188 -0.0826,0.20575 c 0,0 -0.1382,0.0271 -0.17373,0.0358 -0.45378,0.41804 -1.37066,1.32044 -1.58368,3.02405 0.008,0.0395 0.15441,0.26854 0.15441,0.26854 l 0.347,0.20552 0.347,0.0937 m 3.96609,-4.30034 -0.4298,-0.16833 -0.49552,0.0561 -0.61161,0.16822 -0.11567,0.11229 0.38008,0.26167 0,0.14959 -0.14875,0.14959 0.19846,0.39294 0.13188,-0.075 0.16561,-0.26168 c 0.2553,-0.0789 0.4842,-0.16833 0.72686,-0.28053 l 0.19846,-0.5048 m 2.52925,0.34192 -0.375,0.0937 -0.21875,0.15625 0,0.125 -0.375,0.25 0.0937,0.34375 0.21875,-0.15625 0.125,0.15625 0.15625,0.0937 0.0937,-0.28125 -0.0625,-0.15625 0.0625,-0.0937 0.21875,-0.1875 0.0937,0 -0.0937,0.21875 0,0.1875 c 0.0892,-0.0242 0.1588,-0.051 0.25,-0.0625 l -0.25,0.1875 0,0.125 -0.3125,0.21875 -0.28125,-0.0625 0,-0.15625 -0.125,0.0625 0.0625,0.15625 -0.21875,0 -0.125,0.21875 -0.15625,0.15625 -0.0937,0.0312 0,0.1875 0.0312,0.15625 -0.0312,0 0,0.53125 0.0625,-0.0312 0.0937,-0.21875 0.1875,-0.125 0.0312,-0.0937 0.28125,-0.0625 0.15625,0.1875 0.1875,0.0937 -0.0937,0.1875 0.15625,-0.0312 0.0625,-0.21875 -0.1875,-0.21875 0.0625,0 0.21875,0.15625 0.0312,0.21875 0.15625,0.21875 0.0625,-0.3125 0.0937,-0.0312 c 0.0959,0.0996 0.1692,0.23163 0.25,0.34375 l 0.28125,0 0.1875,0.125 -0.0937,0.0937 -0.15625,0.15625 -0.25,0 -0.34375,-0.0937 -0.1875,0 -0.125,0.15625 -0.34375,-0.375 -0.25,-0.0625 -0.375,0.0625 -0.15625,0.0937 0,2.40625 0.0312,0.0312 0.25,-0.15625 0.0937,0.0937 0.28125,0 0.125,0.15625 -0.0937,0.3125 0.1875,0.1875 0,0.375 0.125,0.25 -0.0937,0.25 c -0.009,0.16159 0,0.30714 0,0.46875 0.0795,0.21894 0.14355,0.43575 0.21875,0.65625 l 0.0625,0.34375 0,0.1875 0.125,0 0.21875,-0.125 0.25,0 0.375,-0.4375 -0.0312,-0.15625 0.25,-0.21875 -0.1875,-0.1875 0.21875,-0.1875 0.21875,-0.125 0.0937,-0.125 -0.0625,-0.25 0,-0.59375 0.1875,-0.375 0.1875,-0.25 0.25,-0.5625 0,-0.15625 c -0.11654,0.0146 -0.22972,0.0231 -0.34375,0.0312 -0.0722,0.005 -0.14446,0 -0.21875,0 -0.12359,-0.25961 -0.2183,-0.50966 -0.3125,-0.78125 l -0.15625,-0.1875 -0.0937,-0.3125 0.0625,-0.0625 0.21875,0.25 0.25,0.5625 0.15625,0.15625 -0.0625,0.21875 0.15625,0.15625 0.25,-0.25 0.3125,-0.21875 0.15625,-0.1875 0,-0.21875 c -0.0389,-0.0732 -0.0547,-0.14545 -0.0937,-0.21875 l -0.15625,0.1875 -0.125,-0.15625 -0.1875,-0.125 0,-0.28125 0.21875,0.21875 0.21875,-0.0312 c 0.10166,0.0923 0.19205,0.20751 0.28125,0.3125 l 0.15625,-0.1875 c 0,-0.17463 -0.19976,-1.02044 -0.625,-1.75 -0.42526,-0.72932 -1.15625,-1.40625 -1.15625,-1.40625 l -0.0625,0.0937 -0.21875,0.21875 -0.25,-0.25 0.25,0 0.125,-0.125 -0.46875,-0.0937 -0.25,-0.0937 z'/> - </g> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-183,-529)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' transform='translate(-183,-529)'/> - <g style='display:inline' inkscape:groupmode='layer' id='layer12' inkscape:label='actions' transform='translate(-183,-529)'/> -</svg> diff --git a/public/themes/flat-design/icons/login.png b/public/themes/flat-design/icons/login.png Binary files differdeleted file mode 100644 index cebe0cf7d..000000000 --- a/public/themes/flat-design/icons/login.png +++ /dev/null diff --git a/public/themes/flat-design/icons/login.svg b/public/themes/flat-design/icons/login.svg deleted file mode 100644 index 0a8af162f..000000000 --- a/public/themes/flat-design/icons/login.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='changes-prevent-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='9.416826' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1296' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='1941' inkscape:snap-global='true' inkscape:window-y='81' gridtolerance='10' inkscape:window-height='1013' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='52.563336' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='lock' transform='translate(161.0002,-39)' id='g4053' style='fill:#bebebe;fill-opacity:1'> - - </g> - <path inkscape:connector-curvature='0' d='m 184,244 c -0.554,0 -1,0.446 -1,1 l 0,0.53125 0,5.46875 12,0 0,-5.46875 L 195,245 c 0,-0.554 -0.446,-1 -1,-1 l -10,0 z' id='rect4063' sodipodi:nodetypes='csccccscc' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible'/> - <path inkscape:connector-curvature='0' d='m 188,238 c -1.6447,0 -3,1.3553 -3,3 l 0,7 c 0,1.6447 1.3553,3 3,3 l 2,0 c 1.6447,0 3,-1.3553 3,-3 l 0,-7 c 0,-1.6447 -1.3553,-3 -3,-3 l -2,0 z m 0,2 2,0 c 0.5713,0 1,0.4287 1,1 l 0,7 c 0,0.5713 -0.4287,1 -1,1 l -2,0 c -0.5713,0 -1,-0.4287 -1,-1 l 0,-7 c 0,-0.5713 0.4287,-1 1,-1 z' id='rect4291' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-181.0002,-237)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/logout.png b/public/themes/flat-design/icons/logout.png Binary files differdeleted file mode 100644 index 49255fccd..000000000 --- a/public/themes/flat-design/icons/logout.png +++ /dev/null diff --git a/public/themes/flat-design/icons/logout.svg b/public/themes/flat-design/icons/logout.svg deleted file mode 100644 index 284a67578..000000000 --- a/public/themes/flat-design/icons/logout.svg +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='changes-allow-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='9.416826' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1296' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='1941' inkscape:snap-global='true' inkscape:window-y='81' gridtolerance='10' inkscape:window-height='1013' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='32.563336' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='lock' transform='translate(181.0002,-39)' id='g4053-3' style='fill:#bebebe;fill-opacity:1'> - - </g> - <path inkscape:connector-curvature='0' d='m 204,246 c -0.554,0 -1,0.446 -1,1 l 0,0.53125 0,5.46875 12,0 0,-5.46875 L 215,247 c 0,-0.554 -0.446,-1 -1,-1 l -10,0 z' id='rect4063-6' sodipodi:nodetypes='csccccscc' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible'/> - <path inkscape:connector-curvature='0' d='m 208.0002,237 c -1.64469,0 -3,1.35529 -3,3 l 0,3 2,0 0,-3 c 0,-0.57129 0.42873,-1 1,-1 l 2,0 c 0.57127,0 1,0.42871 1,1 l 0,7 2,0 0,-7 c 0,-1.64471 -1.35531,-3 -3,-3 l -2,0 z' id='rect4291-4' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-201.0002,-237)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/next.png b/public/themes/flat-design/icons/next.png Binary files differdeleted file mode 100644 index d240ac6eb..000000000 --- a/public/themes/flat-design/icons/next.png +++ /dev/null diff --git a/public/themes/flat-design/icons/non-starred.png b/public/themes/flat-design/icons/non-starred.png Binary files differdeleted file mode 100644 index 4aafb6d8a..000000000 --- a/public/themes/flat-design/icons/non-starred.png +++ /dev/null diff --git a/public/themes/flat-design/icons/non-starred.svg b/public/themes/flat-design/icons/non-starred.svg deleted file mode 100644 index 346717f78..000000000 --- a/public/themes/flat-design/icons/non-starred.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='non-starred-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='0.50817' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1457' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='86' inkscape:snap-global='true' inkscape:window-y='51' gridtolerance='10' inkscape:window-height='1093' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='-0.97094164' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='folder-remote' transform='translate(-166.9996,-599)' id='g5516'> - - </g> - <path inkscape:connector-curvature='0' d='m 69.002994,398.0051 c -0.2659,-9.9e-4 -0.498591,0.1006 -0.664702,0.2393 -0.166111,0.13869 -0.277415,0.32137 -0.389683,0.50028 -0.224526,0.35782 -0.392692,0.76873 -0.565462,1.20357 -0.17277,0.43483 -0.347131,0.88766 -0.504599,1.23232 -0.157468,0.34465 -0.345596,0.57871 -0.35862,0.58776 -0.01287,0.009 -0.307217,0.117 -0.695765,0.15181 -0.388547,0.0348 -0.881219,0.0492 -1.36324,0.0694 -0.482021,0.0202 -0.970296,0.0362 -1.393042,0.12817 -0.211377,0.046 -0.4154,0.10148 -0.604343,0.20956 -0.188943,0.10808 -0.366894,0.28631 -0.450041,0.53002 -0.08315,0.24371 -0.04901,0.50627 0.03636,0.70157 0.08537,0.19531 0.226562,0.36514 0.368205,0.52344 0.283296,0.31663 0.632701,0.60557 1.007899,0.89849 0.375198,0.29292 0.776711,0.55926 1.067753,0.81027 0.291042,0.25101 0.454223,0.50796 0.459122,0.52271 0.0048,0.0146 0.03119,0.33498 -0.05456,0.70231 -0.08575,0.36732 -0.223801,0.81174 -0.352819,1.26034 -0.129019,0.44861 -0.25176,0.90196 -0.291704,1.31835 -0.01997,0.20819 -0.04285,0.40729 0.0053,0.61409 0.04814,0.20679 0.184498,0.43007 0.399016,0.58168 0.214508,0.15161 0.449359,0.1881 0.668233,0.1701 0.218874,-0.018 0.435347,-0.0703 0.635153,-0.15132 0.399612,-0.16214 0.801767,-0.42851 1.206417,-0.68231 0.404649,-0.2538 0.808218,-0.52155 1.145553,-0.71107 0.337335,-0.18952 0.648398,-0.2686 0.66445,-0.26854 0.01586,6e-5 0.303381,0.0951 0.638936,0.28732 0.335555,0.19221 0.725321,0.46503 1.127607,0.72205 0.402287,0.25702 0.819957,0.49752 1.218019,0.66284 0.199027,0.0827 0.388928,0.15086 0.607623,0.17059 0.218695,0.0197 0.479784,-0.031 0.695512,-0.18105 0.215717,-0.15001 0.339276,-0.35235 0.389178,-0.55877 0.0499,-0.20642 0.04291,-0.43517 0.02476,-0.64358 -0.03632,-0.41683 -0.158989,-0.86394 -0.284099,-1.31372 -0.12511,-0.44978 -0.261649,-0.88661 -0.344206,-1.25475 -0.08256,-0.36813 -0.07169,-0.68662 -0.06666,-0.70133 0.005,-0.0145 0.187465,-0.25247 0.480599,-0.50101 0.293134,-0.24854 0.675989,-0.53755 1.053628,-0.82731 0.37764,-0.28976 0.767161,-0.57158 1.053124,-0.88579 0.142977,-0.1571 0.241796,-0.33444 0.32882,-0.52904 0.08702,-0.1946 0.135998,-0.42874 0.05481,-0.67306 -0.08119,-0.24432 -0.271084,-0.41314 -0.459122,-0.52272 -0.188037,-0.10958 -0.366789,-0.18158 -0.577821,-0.22931 -0.422064,-0.0955 -0.913586,-0.14009 -1.395564,-0.16426 -0.481977,-0.0242 -0.945835,-0.039 -1.334196,-0.077 -0.388361,-0.038 -0.715653,-0.13122 -0.728592,-0.14037 -0.01279,-0.009 -0.184019,-0.23636 -0.338404,-0.58217 -0.154385,-0.34581 -0.326584,-0.78819 -0.495481,-1.22429 -0.168896,-0.4361 -0.334766,-0.86429 -0.556091,-1.2238 -0.110668,-0.17977 -0.233352,-0.35397 -0.398259,-0.49396 -0.164907,-0.13999 -0.40309,-0.25684 -0.66899,-0.25783 z m 0.0086,0.99424 c 0.0422,0.0358 0.106711,0.13602 0.184103,0.26173 0.154773,0.25141 0.330675,0.6272 0.493967,1.04883 0.163292,0.42163 0.319052,0.88645 0.495985,1.28277 0.176933,0.39633 0.314564,0.73379 0.675296,0.98889 0.360721,0.25509 0.770187,0.29763 1.215244,0.34118 0.445058,0.0435 0.92882,0.0532 1.394807,0.0765 0.465988,0.0234 0.918236,0.0697 1.213479,0.13647 0.147627,0.0334 0.255581,0.0572 0.303811,0.0853 -0.02227,0.0498 -0.07958,0.15478 -0.179562,0.26464 -0.199968,0.21973 -0.537491,0.48746 -0.902606,0.7676 -0.365114,0.28015 -0.778037,0.57219 -1.113986,0.85704 -0.33595,0.28485 -0.639918,0.53221 -0.779871,0.94209 -0.139952,0.40986 -0.05396,0.77681 0.04065,1.19869 0.09461,0.42189 0.25406,0.87813 0.375016,1.31299 0.120956,0.43486 0.226877,0.84692 0.25228,1.13851 0.01156,0.13264 -0.01699,0.23485 -0.02778,0.29267 -0.05993,-0.008 -0.176402,-0.0324 -0.303811,-0.0853 -0.278362,-0.11561 -0.646494,-0.32808 -1.03543,-0.57657 -0.388935,-0.24849 -0.803899,-0.53053 -1.18847,-0.75081 -0.38457,-0.22028 -0.707909,-0.39837 -1.155138,-0.40015 -0.447219,-0.002 -0.792046,0.17245 -1.178633,0.38965 -0.386587,0.21719 -0.784375,0.49517 -1.175607,0.74055 -0.391232,0.24538 -0.776359,0.4507 -1.055898,0.56412 -0.139769,0.0567 -0.246155,0.0856 -0.302298,0.0902 -0.01026,-0.0578 -0.01522,-0.16008 -0.0025,-0.29243 0.02793,-0.29118 0.138178,-0.70893 0.262912,-1.14264 0.124734,-0.43372 0.254035,-0.89785 0.352315,-1.31883 0.09828,-0.42099 0.186721,-0.78846 0.05027,-1.19943 -0.136442,-0.41097 -0.43218,-0.64202 -0.765707,-0.92967 -0.333527,-0.28766 -0.705509,-0.58555 -1.068257,-0.86876 -0.362749,-0.2832 -0.717797,-0.56075 -0.915974,-0.78224 -0.09908,-0.11075 -0.162208,-0.21163 -0.184103,-0.26173 0.04834,-0.0276 0.154481,-0.058 0.302298,-0.0902 0.295625,-0.0643 0.745175,-0.10714 1.211209,-0.1267 0.466034,-0.0196 0.948238,-0.0298 1.393546,-0.0697 0.445308,-0.0399 0.846793,-0.0499 1.209695,-0.30216 0.362893,-0.25221 0.5383,-0.60511 0.718755,-1.00009 0.180456,-0.39497 0.33731,-0.84115 0.504347,-1.26156 0.167037,-0.42041 0.318485,-0.83556 0.475555,-1.08586 0.07853,-0.12515 0.167511,-0.20007 0.21012,-0.23565 z' id='path5520' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.04970646;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:label='devices' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-61.000202,-397)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/note.png b/public/themes/flat-design/icons/note.png Binary files differdeleted file mode 100644 index d819ebd02..000000000 --- a/public/themes/flat-design/icons/note.png +++ /dev/null diff --git a/public/themes/flat-design/icons/note.svg b/public/themes/flat-design/icons/note.svg deleted file mode 100644 index 117b999f7..000000000 --- a/public/themes/flat-design/icons/note.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='user-available-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='-71.46449' inkscape:current-layer='layer9' inkscape:window-width='1310' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='false' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='54' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='0' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='690' borderopacity='1' guidetolerance='10' inkscape:cx='83.606112' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path inkscape:connector-curvature='0' d='m 44.6875,358.0625 c -1.5235,0 -2.75,1.2265 -2.75,2.75 l 0,4.4375 c 0,1.5235 1.2265,2.75 2.75,2.75 l 6.305187,0 3.053347,3.98495 -0.07728,-4.0787 c 1.204532,-0.29284 2.09375,-1.35911 2.09375,-2.65625 l 0,-4.4375 c 0,-1.5235 -1.2265,-2.75 -2.75,-2.75 z' id='rect11261' sodipodi:nodetypes='sssscccssss' style='color:#bebebe;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.4000001;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate'/> - </g> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-41.000198,-357)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/note_empty.png b/public/themes/flat-design/icons/note_empty.png Binary files differdeleted file mode 100644 index c26ad2b16..000000000 --- a/public/themes/flat-design/icons/note_empty.png +++ /dev/null diff --git a/public/themes/flat-design/icons/note_empty.svg b/public/themes/flat-design/icons/note_empty.svg deleted file mode 100644 index 93092f4a4..000000000 --- a/public/themes/flat-design/icons/note_empty.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='user-invisible-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16.000198'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:object-paths='true' inkscape:cy='6.91348' inkscape:current-layer='layer9' inkscape:window-width='1920' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='false' objecttolerance='10' showgrid='true' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='0' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='1' inkscape:snap-global='true' inkscape:window-y='26' gridtolerance='10' inkscape:zoom='32' inkscape:window-height='1021' borderopacity='1' guidetolerance='10' inkscape:snap-bbox-midpoints='false' inkscape:cx='11.38402' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path d='m 104.75,357.0625 c -2.0602,0 -3.75,1.6898 -3.75,3.75 l 0,4.4375 c 0,2.0602 1.6898,3.75 3.75,3.75 l 4.9375,0 3.75,2.65625 1.59375,1.125 0,-1.96875 -0.0313,-2.5 c 1.11055,-0.59715 1.96875,-1.65265 1.96875,-3.0625 l 0,-4.4375 c 0,-2.0602 -1.6898,-3.75 -3.75,-3.75 l -8.46875,0 z m 0,2 8.46875,0 c 0.9868,0 1.75,0.7632 1.75,1.75 l 0,4.4375 c 0,0.86273 -0.63508,1.54099 -1.125,1.625 L 113,367 l 0,0.84375 0,1.03125 -2.40625,-1.6875 -0.25,-0.1875 -0.3125,0 -5.28125,0 c -0.9868,0 -1.75,-0.7632 -1.75,-1.75 l 0,-4.4375 c 0,-0.9868 0.7632,-1.75 1.75,-1.75 z' id='path12148' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#bebebe;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - </g> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> - <g transform='translate(-101,-357)' inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/previous.png b/public/themes/flat-design/icons/previous.png Binary files differdeleted file mode 100644 index b541f6c0c..000000000 --- a/public/themes/flat-design/icons/previous.png +++ /dev/null diff --git a/public/themes/flat-design/icons/read.png b/public/themes/flat-design/icons/read.png Binary files differdeleted file mode 100644 index a402689c7..000000000 --- a/public/themes/flat-design/icons/read.png +++ /dev/null diff --git a/public/themes/flat-design/icons/read.svg b/public/themes/flat-design/icons/read.svg deleted file mode 100644 index 932bfd860..000000000 --- a/public/themes/flat-design/icons/read.svg +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns='http://www.w3.org/2000/svg' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' width='16' xmlns:cc='http://creativecommons.org/ns#' height='16.001099' sodipodi:docname='mail-read-symbolic.svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.1 r9760' version='1.1'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:window-width='1226' id='namedview88' showgrid='false' objecttolerance='10' inkscape:pageopacity='1' inkscape:cx='12.92257' inkscape:cy='-1.422716' inkscape:snap-bbox='true' borderopacity='1' inkscape:object-nodes='false' inkscape:current-layer='layer9' inkscape:snap-to-guides='true' inkscape:snap-others='false' inkscape:snap-grids='true' bordercolor='#666666' inkscape:window-height='967' inkscape:snap-bbox-midpoints='false' showborder='false' inkscape:bbox-paths='false' inkscape:guide-bbox='true' inkscape:window-x='2657' inkscape:window-y='338' inkscape:snap-global='true' inkscape:snap-nodes='true' pagecolor='#555753' inkscape:object-paths='false' inkscape:pageshadow='2' inkscape:zoom='1' inkscape:window-maximized='0' gridtolerance='10' guidetolerance='10' showguides='true'> - <inkscape:grid id='grid4866' empspacing='2' type='xygrid' visible='true' snapvisiblegridlinesonly='true' enabled='true' spacingx='1px' spacingy='1px'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g id='layer9' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='status'> - - <path id='path35631' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;opacity:0.35;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans' d='m 68.8752,297 a 1.0001,1.0001 0 0 0 -0.5,0.25 l -4.90625,4 a 1.0001,1.0001 0 0 0 -0.0625,0.0312 c 0,0 -0.32587,0.29728 -0.65625,0.75 -0.22334,0.30605 -0.3527,0.8316 -0.5,1.3125 a 1.0001,1.0001 0 0 0 -0.03125,0.0312 1.0001,1.0001 0 0 0 -0.21875,0.5625 c -5.1e-4,0.0118 3.6e-4,0.0195 0,0.0312 a 1.0001,1.0001 0 0 0 0,0.0312 1.0001,1.0001 0 0 0 0,0.15625 l 0,7.84375 a 1.0001,1.0001 0 0 0 1,1 l 12,0 a 1.0001,1.0001 0 0 0 1,-1 l 0,-7.84375 a 1.0001,1.0001 0 0 0 0,-0.15625 1.0001,1.0001 0 0 0 -0.21875,-0.65625 1.0001,1.0001 0 0 0 -0.03125,-0.0312 c -0.32774,-1.18787 -1.125,-2 -1.125,-2 a 1.0001,1.0001 0 0 0 -0.0312,-0.0312 L 69.6252,297.25 a 1.0001,1.0001 0 0 0 -0.65625,-0.25 1.0001,1.0001 0 0 0 -0.0937,0 z m 0.125,2.28125 4.3125,3.53125 0.0312,0.0312 c 0.021,0.0255 0.18032,0.24952 0.34375,0.5 l -4.68745,3.53125 -4.6875,-3.53125 c 0.0259,-0.0394 0.0349,-0.0872 0.0625,-0.125 0.1908,-0.26146 0.31874,-0.41421 0.34375,-0.4375 l 0.03125,-0.0312 4.25,-3.46875 z m -5,5.09375 4.6875,3.53125 0.3125,0.21875 0.3125,-0.21875 4.6875,-3.53125 0,6.625 -10,0 0,-6.625 z' inkscape:connector-curvature='0'/> - </g> - <g id='layer10' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='devices'/> - <g id='layer11' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='apps'/> - <g id='layer13' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='places'/> - <g id='layer14' inkscape:groupmode='layer' transform='translate(-60.99995,-296.9989)' inkscape:label='mimetypes'/> - <g id='layer15' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='emblems'/> - <g id='g71291' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='emotes'/> - <g id='g4953' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='categories'/> - <g id='layer12' inkscape:groupmode='layer' style='display:inline' transform='translate(-60.99995,-296.9989)' inkscape:label='actions'/> -</svg> diff --git a/public/themes/flat-design/icons/refresh.png b/public/themes/flat-design/icons/refresh.png Binary files differdeleted file mode 100644 index 2fdd1b0a5..000000000 --- a/public/themes/flat-design/icons/refresh.png +++ /dev/null diff --git a/public/themes/flat-design/icons/rss.png b/public/themes/flat-design/icons/rss.png Binary files differdeleted file mode 100644 index c20455af0..000000000 --- a/public/themes/flat-design/icons/rss.png +++ /dev/null diff --git a/public/themes/flat-design/icons/rss.svg b/public/themes/flat-design/icons/rss.svg deleted file mode 100644 index ceaddceee..000000000 --- a/public/themes/flat-design/icons/rss.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='application-rss+xml-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.3.1 r9886' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-paths='false' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer14' inkscape:cx='27.01134' inkscape:cy='1.367038' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='709' inkscape:window-maximized='1' inkscape:window-width='1366' inkscape:window-x='0' inkscape:window-y='27' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='-319.9998px' originy='84.00012px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-561,-301.00012)'> - - <path sodipodi:cx='323.0625' sodipodi:cy='97.1875' d='m 325.0625,97.1875 a 2,3.236068 0 1 1 -4,0 2,3.236068 0 1 1 4,0 z' id='path4983' sodipodi:rx='2' sodipodi:ry='3.236068' style='color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.69602728;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.0000007,0,0,0.61803426,241.93747,252.93479)' sodipodi:type='arc'/> - <path inkscape:connector-curvature='0' d='m 563.0002,303 0,1 c 0,0.55016 0.45347,1 1,1 4.97056,0 9,4.02944 9,9 0,0.55016 0.45347,1 1,1 l 1,0 0,-1 c 0,-6.07513 -4.92487,-11 -11,-11 l -1,0 z m 0,4 0,1 c 0,0.55016 0.45347,1 1,1 2.76143,0 5,2.23857 5,5 0,0.55016 0.45347,1 1,1 l 1,0 0,-1 c 0,-3.866 -3.134,-7 -7,-7 l -1,0 z' id='path5814' style='color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.33333492;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new'/> - </g> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-561,-301.00012)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-561,-301.00012)'/> -</svg> diff --git a/public/themes/flat-design/icons/search.png b/public/themes/flat-design/icons/search.png Binary files differdeleted file mode 100644 index 60e25d121..000000000 --- a/public/themes/flat-design/icons/search.png +++ /dev/null diff --git a/public/themes/flat-design/icons/share.png b/public/themes/flat-design/icons/share.png Binary files differdeleted file mode 100644 index 74c4c5dda..000000000 --- a/public/themes/flat-design/icons/share.png +++ /dev/null diff --git a/public/themes/flat-design/icons/share.svg b/public/themes/flat-design/icons/share.svg deleted file mode 100644 index 443321882..000000000 --- a/public/themes/flat-design/icons/share.svg +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version='1.0' encoding='UTF-8' standalone='no'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:dc='http://purl.org/dc/elements/1.1/' sodipodi:docname='folder-publicshare-symbolic.svg' height='16' id='svg7384' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:svg='http://www.w3.org/2000/svg' inkscape:version='0.48.3.1 r9886' version='1.1' width='16' xmlns='http://www.w3.org/2000/svg'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:bbox-paths='false' bordercolor='#666666' borderopacity='1' inkscape:current-layer='layer13' inkscape:cx='-55.50224' inkscape:cy='-178.38158' gridtolerance='10' inkscape:guide-bbox='true' guidetolerance='10' id='namedview88' inkscape:object-nodes='false' inkscape:object-paths='false' objecttolerance='10' pagecolor='#3a3b39' inkscape:pageopacity='1' inkscape:pageshadow='2' showborder='false' showgrid='false' showguides='true' inkscape:snap-bbox='true' inkscape:snap-bbox-midpoints='false' inkscape:snap-global='true' inkscape:snap-grids='true' inkscape:snap-nodes='false' inkscape:snap-others='false' inkscape:snap-to-guides='true' inkscape:window-height='1381' inkscape:window-maximized='1' inkscape:window-width='2560' inkscape:window-x='1600' inkscape:window-y='27' inkscape:zoom='1'> - <inkscape:grid empspacing='2' enabled='true' id='grid4866' originx='-340px' originy='-20.999999px' snapvisiblegridlinesonly='true' spacingx='1px' spacingy='1px' type='xygrid' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer10' inkscape:label='devices' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer11' inkscape:label='apps' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer13' inkscape:label='places' transform='translate(-581.0002,-196)'> - - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8192' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,148.15963,-64.49107)' sodipodi:type='arc'/> - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8194' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,158.12818,-59.49107)' sodipodi:type='arc'/> - <path sodipodi:cx='289.03125' sodipodi:cy='178.03125' d='m 291,178.03125 a 1.96875,1.96875 0 1 1 -3.9375,0 1.96875,1.96875 0 1 1 3.9375,0 z' id='path8196' sodipodi:rx='1.96875' sodipodi:ry='1.96875' style='color:#000000;fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.99999994;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:new' transform='matrix(1.5079365,0,0,1.5079365,158.12818,-69.49107)' sodipodi:type='arc'/> - <path inkscape:connector-curvature='0' d='m 593.625,198.15625 -10.0625,4.875 -1.8125,0.90625 1.8125,0.90625 10.03125,5.0625 0.90625,-1.8125 -8.21875,-4.15625 8.21875,-4 -0.875,-1.78125 z' id='path8198' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans'/> - </g> - <g inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='g71291' inkscape:label='emotes' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='g4953' inkscape:label='categories' style='display:inline' transform='translate(-581.0002,-196)'/> - <g inkscape:groupmode='layer' id='layer12' inkscape:label='actions' style='display:inline' transform='translate(-581.0002,-196)'/> -</svg> diff --git a/public/themes/flat-design/icons/starred.png b/public/themes/flat-design/icons/starred.png Binary files differdeleted file mode 100644 index ff2cf31a7..000000000 --- a/public/themes/flat-design/icons/starred.png +++ /dev/null diff --git a/public/themes/flat-design/icons/starred.svg b/public/themes/flat-design/icons/starred.svg deleted file mode 100644 index d6e2cebb5..000000000 --- a/public/themes/flat-design/icons/starred.svg +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='starred-symbolic.svg' version='1.1' inkscape:version='0.48.1 r9760' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='0.50817' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:object-paths='true' inkscape:window-width='1457' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='false' bordercolor='#666666' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='86' inkscape:snap-global='true' inkscape:window-y='51' gridtolerance='10' inkscape:window-height='1093' inkscape:snap-to-guides='true' inkscape:current-layer='layer9' inkscape:snap-bbox-midpoints='false' inkscape:zoom='1' inkscape:cx='19.029058' inkscape:snap-grids='true' inkscape:pageopacity='1'> - <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g inkscape:label='status' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer9' style='display:inline'> - <g inkscape:label='folder-remote' transform='translate(-186.9996,-599)' id='g11910-1'> - - </g> - <path sodipodi:cy='180.96373' sodipodi:r2='3.8276224' transform='matrix(1.0472113,-0.00871584,0.00871584,1.0472113,-504.35434,220.15425)' inkscape:rounded='0.2104596' inkscape:flatsided='false' inkscape:transform-center-x='-0.0094346789' inkscape:transform-center-y='-0.69491065' d='m 530.9488,186.70897 c -0.77941,0.55189 -3.15759,-1.90601 -4.11253,-1.9179 -0.95532,-0.0119 -3.39494,2.38585 -4.16096,1.8149 -0.76573,-0.57072 0.83698,-3.59203 0.55319,-4.50391 -0.2839,-0.91223 -3.31818,-2.49151 -3.01189,-3.39647 0.30617,-0.90461 3.67487,-0.31399 4.45442,-0.86567 0.77986,-0.5519 1.3442,-3.92569 2.29952,-3.91404 0.95494,0.0116 1.43421,3.39798 2.19979,3.9689 0.76588,0.57114 4.14893,0.0653 4.43307,0.97746 0.28402,0.9118 -2.78848,2.41405 -3.09488,3.31858 -0.30652,0.90489 1.21999,3.96605 0.44027,4.51815 z' id='path11922-0' sodipodi:type='star' sodipodi:arg1='0.95492637' sodipodi:r1='7.0383992' style='color:#000000;fill:#f1c40f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate' sodipodi:arg2='1.5829876' inkscape:randomized='0' sodipodi:cx='526.88293' sodipodi:sides='5'/> - </g> - <g inkscape:label='devices' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer10'/> - <g inkscape:label='apps' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer11'/> - <g inkscape:label='actions' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer12'/> - <g inkscape:label='places' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer13'/> - <g inkscape:label='mimetypes' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer14'/> - <g inkscape:label='emblems' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='layer15' style='display:inline'/> - <g inkscape:label='categories' transform='translate(-41.000202,-397)' inkscape:groupmode='layer' id='g4953' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/tag.png b/public/themes/flat-design/icons/tag.png Binary files differdeleted file mode 100644 index cb1a13833..000000000 --- a/public/themes/flat-design/icons/tag.png +++ /dev/null diff --git a/public/themes/flat-design/icons/tag.svg b/public/themes/flat-design/icons/tag.svg deleted file mode 100644 index 688aca7a4..000000000 --- a/public/themes/flat-design/icons/tag.svg +++ /dev/null @@ -1,134 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - sodipodi:docname="tag.svg" - height="16" - id="svg7384" - inkscape:version="0.48.4 r9939" - version="1.1" - width="16"> - <metadata - id="metadata90"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview - inkscape:bbox-paths="false" - bordercolor="#666666" - borderopacity="1" - inkscape:current-layer="layer12" - inkscape:cx="63.620645" - inkscape:cy="27.761176" - gridtolerance="10" - inkscape:guide-bbox="true" - guidetolerance="10" - id="namedview88" - inkscape:object-nodes="false" - inkscape:object-paths="false" - objecttolerance="10" - pagecolor="#555753" - inkscape:pageopacity="1" - inkscape:pageshadow="2" - showborder="false" - showgrid="false" - showguides="true" - inkscape:snap-bbox="true" - inkscape:snap-bbox-midpoints="false" - inkscape:snap-global="true" - inkscape:snap-grids="true" - inkscape:snap-nodes="false" - inkscape:snap-others="false" - inkscape:snap-to-guides="true" - inkscape:window-height="709" - inkscape:window-maximized="1" - inkscape:window-width="1366" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:zoom="4"> - <inkscape:grid - empspacing="2" - enabled="true" - id="grid4866" - snapvisiblegridlinesonly="true" - spacingx="1px" - spacingy="1px" - type="xygrid" - visible="true" /> - </sodipodi:namedview> - <title - id="title9167">Gnome Symbolic Icon Theme</title> - <defs - id="defs7386" /> - <g - inkscape:groupmode="layer" - id="layer9" - inkscape:label="status" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer10" - inkscape:label="devices" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer11" - inkscape:label="apps" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer13" - inkscape:label="places" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer14" - inkscape:label="mimetypes" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer15" - inkscape:label="emblems" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="g71291" - inkscape:label="emotes" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="g4953" - inkscape:label="categories" - style="display:inline" - transform="translate(-141.0002,-807)" /> - <g - inkscape:groupmode="layer" - id="layer12" - inkscape:label="actions" - style="display:inline" - transform="translate(-141.0002,-807)"> - <path - inkscape:connector-curvature="0" - d="m 149.0002,809 0,13 4,-4 4,4 c 0.0525,-6.84943 -0.0285,-10.58353 0,-13 z" - id="path12292" - sodipodi:nodetypes="cccccc" - style="fill:#bebebe;fill-opacity:1;stroke:none" /> - </g> -</svg> diff --git a/public/themes/flat-design/icons/unread.png b/public/themes/flat-design/icons/unread.png Binary files differdeleted file mode 100644 index ffcd323b1..000000000 --- a/public/themes/flat-design/icons/unread.png +++ /dev/null diff --git a/public/themes/flat-design/icons/unread.svg b/public/themes/flat-design/icons/unread.svg deleted file mode 100644 index f541fb25b..000000000 --- a/public/themes/flat-design/icons/unread.svg +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version='1.0' encoding='UTF-8'?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' version='1.1' inkscape:version='0.47 r22583' height='16' sodipodi:docname='mail-unread-symbolic.svg' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'> - <metadata id='metadata90'> - <rdf:RDF> - <cc:Work rdf:about=''> - <dc:format>image/svg+xml</dc:format> - <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/> - <dc:title>Gnome Symbolic Icon Theme</dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <sodipodi:namedview inkscape:cy='-166.8292' inkscape:current-layer='layer9' inkscape:window-width='1310' pagecolor='#555753' showborder='false' showguides='true' inkscape:snap-nodes='true' objecttolerance='10' showgrid='false' inkscape:object-nodes='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:window-x='54' inkscape:snap-bbox='true' bordercolor='#666666' id='namedview88' inkscape:window-maximized='0' inkscape:snap-global='true' inkscape:window-y='25' gridtolerance='10' inkscape:zoom='1' inkscape:window-height='690' borderopacity='1' guidetolerance='10' inkscape:cx='315.82499' inkscape:bbox-paths='false' inkscape:snap-grids='true' inkscape:pageopacity='1' inkscape:snap-to-guides='true'> - <inkscape:grid visible='true' spacingx='1px' type='xygrid' spacingy='1px' id='grid4866' empspacing='2' enabled='true' snapvisiblegridlinesonly='true'/> - </sodipodi:namedview> - <title id='title9167'>Gnome Symbolic Icon Theme</title> - <defs id='defs7386'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer9' inkscape:label='status' style='display:inline'> - - <path d='m 43.78125,301 a 1.0000999,1.0000999 0 0 0 -0.40625,1.78125 l 5,4 0.625,0.5 0.625,-0.5 5,-4 a 1.0004882,1.0004882 0 1 0 -1.25,-1.5625 l -4.375,3.5 -4.375,-3.5 A 1.0000999,1.0000999 0 0 0 43.78125,301 z' id='path35600' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#e74c3c;fill-opacity:1;stroke:none;stroke-width:1.99999976;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - <path d='M 42.90625,300 A 1.0001,1.0001 0 0 0 42,301 l 0,9 a 1.0001,1.0001 0 0 0 1,1 l 12,0 a 1.0001,1.0001 0 0 0 1,-1 l 0,-9 a 1.0001,1.0001 0 0 0 -1,-1 l -12,0 a 1.0001,1.0001 0 0 0 -0.09375,0 z M 44,302 l 10,0 0,7 -10,0 0,-7 z' id='rect35604' style='font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#e74c3c;fill-opacity:1;stroke:none;stroke-width:2;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Bitstream Vera Sans;-inkscape-font-specification:Bitstream Vera Sans'/> - </g> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer10' inkscape:label='devices'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer11' inkscape:label='apps'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer12' inkscape:label='actions'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer13' inkscape:label='places'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer14' inkscape:label='mimetypes'/> - <g transform='translate(-40.99995,-297)' inkscape:groupmode='layer' id='layer15' inkscape:label='emblems' style='display:inline'/> -</svg> diff --git a/public/themes/flat-design/icons/up.png b/public/themes/flat-design/icons/up.png Binary files differdeleted file mode 100644 index 3a6fcd898..000000000 --- a/public/themes/flat-design/icons/up.png +++ /dev/null diff --git a/public/themes/printer/style.css b/public/themes/printer/style.css deleted file mode 100644 index 87d019c58..000000000 --- a/public/themes/printer/style.css +++ /dev/null @@ -1,34 +0,0 @@ -.header, -.aside, -.nav_menu, -.day, -.flux_header, -.flux_content .bottom, -.pagination { - display: none; -} - -html, body { - background: #fff; - color: #000; - font-family: Serif; - font-size: 12pt; -} - -#global, -.flux_content { - display: block !important; -} - -.flux_content .content { - width: 100% !important; - text-align: justify; -} - -.flux_content .content a { - color: #000; -} -.flux_content .content a:after { - content: " (" attr(href) ") "; - text-decoration: underline; -}
\ No newline at end of file |
