From a77ab86a3896c99fdad27703516c2bae70c5533b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment?= Date: Thu, 1 Sep 2016 08:31:03 +0200 Subject: [PATCH] [enh][wip]Installation Refactoring --- scripts/install | 49 +- sources/CHANGELOG.md | 558 -- sources/CONTRIBUTING.md | 57 - sources/CREDITS.md | 25 - sources/LICENSE | 662 -- sources/README.fr.md | 138 - sources/README.md | 138 - sources/app/.htaccess | 3 - sources/app/Controllers/authController.php | 358 -- .../app/Controllers/categoryController.php | 194 - .../app/Controllers/configureController.php | 331 - sources/app/Controllers/entryController.php | 192 - sources/app/Controllers/errorController.php | 53 - .../app/Controllers/extensionController.php | 215 - sources/app/Controllers/feedController.php | 573 -- .../Controllers/importExportController.php | 611 -- sources/app/Controllers/indexController.php | 240 - .../app/Controllers/javascriptController.php | 54 - sources/app/Controllers/statsController.php | 128 - .../Controllers/subscriptionController.php | 116 - sources/app/Controllers/updateController.php | 161 - sources/app/Controllers/userController.php | 275 - sources/app/Exceptions/BadUrlException.php | 9 - sources/app/Exceptions/ContextException.php | 8 - sources/app/Exceptions/DAOException.php | 5 - .../app/Exceptions/EntriesGetterException.php | 5 - sources/app/Exceptions/FeedException.php | 5 - sources/app/FreshRSS.php | 136 - sources/app/Models/Auth.php | 254 - sources/app/Models/Category.php | 80 - sources/app/Models/CategoryDAO.php | 257 - sources/app/Models/ConfigurationSetter.php | 389 -- sources/app/Models/Context.php | 314 - sources/app/Models/DatabaseDAO.php | 83 - sources/app/Models/DatabaseDAOSQLite.php | 48 - sources/app/Models/Days.php | 7 - sources/app/Models/Entry.php | 207 - sources/app/Models/EntryDAO.php | 742 --- sources/app/Models/EntryDAOSQLite.php | 189 - sources/app/Models/Factory.php | 41 - sources/app/Models/Feed.php | 490 -- sources/app/Models/FeedDAO.php | 391 -- sources/app/Models/FeedDAOSQLite.php | 19 - sources/app/Models/Log.php | 26 - sources/app/Models/LogDAO.php | 30 - sources/app/Models/Search.php | 229 - sources/app/Models/Searchable.php | 6 - sources/app/Models/Share.php | 238 - sources/app/Models/StatsDAO.php | 427 -- sources/app/Models/StatsDAOSQLite.php | 87 - sources/app/Models/Themes.php | 118 - sources/app/Models/UserDAO.php | 68 - sources/app/Models/UserQuery.php | 226 - sources/app/SQL/install.sql.mysql.php | 64 - sources/app/SQL/install.sql.sqlite.php | 62 - sources/app/actualize_script.php | 86 - sources/app/i18n/cz/admin.php | 183 - sources/app/i18n/cz/conf.php | 174 - sources/app/i18n/cz/feedback.php | 110 - sources/app/i18n/cz/gen.php | 184 - sources/app/i18n/cz/index.php | 61 - sources/app/i18n/cz/install.php | 121 - sources/app/i18n/cz/sub.php | 62 - sources/app/i18n/de/admin.php | 183 - sources/app/i18n/de/conf.php | 174 - sources/app/i18n/de/feedback.php | 110 - sources/app/i18n/de/gen.php | 185 - sources/app/i18n/de/index.php | 61 - sources/app/i18n/de/install.php | 121 - sources/app/i18n/de/sub.php | 62 - sources/app/i18n/en/admin.php | 183 - sources/app/i18n/en/conf.php | 174 - sources/app/i18n/en/feedback.php | 110 - sources/app/i18n/en/gen.php | 185 - sources/app/i18n/en/index.php | 61 - sources/app/i18n/en/install.php | 121 - sources/app/i18n/en/sub.php | 62 - sources/app/i18n/fr/admin.php | 183 - sources/app/i18n/fr/conf.php | 174 - sources/app/i18n/fr/feedback.php | 110 - sources/app/i18n/fr/gen.php | 185 - sources/app/i18n/fr/index.php | 61 - sources/app/i18n/fr/install.php | 121 - sources/app/i18n/fr/sub.php | 62 - sources/app/i18n/it/admin.php | 183 - sources/app/i18n/it/conf.php | 174 - sources/app/i18n/it/feedback.php | 110 - sources/app/i18n/it/gen.php | 185 - sources/app/i18n/it/index.php | 61 - sources/app/i18n/it/install.php | 122 - sources/app/i18n/it/sub.php | 62 - sources/app/i18n/nl/admin.php | 188 - sources/app/i18n/nl/conf.php | 174 - sources/app/i18n/nl/feedback.php | 111 - sources/app/i18n/nl/gen.php | 185 - sources/app/i18n/nl/index.php | 61 - sources/app/i18n/nl/install.php | 121 - sources/app/i18n/nl/sub.php | 62 - sources/app/i18n/ru/admin.php | 183 - sources/app/i18n/ru/conf.php | 174 - sources/app/i18n/ru/feedback.php | 110 - sources/app/i18n/ru/gen.php | 185 - sources/app/i18n/ru/index.php | 61 - sources/app/i18n/ru/install.php | 113 - sources/app/i18n/ru/sub.php | 62 - sources/app/i18n/tr/admin.php | 183 - sources/app/i18n/tr/conf.php | 174 - sources/app/i18n/tr/feedback.php | 110 - sources/app/i18n/tr/gen.php | 185 - sources/app/i18n/tr/index.php | 61 - sources/app/i18n/tr/install.php | 121 - sources/app/i18n/tr/sub.php | 62 - sources/app/index.html | 13 - sources/app/install.php | 897 --- sources/app/layout/aside_configure.phtml | 49 - sources/app/layout/aside_feed.phtml | 95 - sources/app/layout/aside_stats.phtml | 12 - sources/app/layout/aside_subscription.phtml | 17 - sources/app/layout/header.phtml | 93 - sources/app/layout/layout.phtml | 72 - sources/app/layout/nav_entries.phtml | 5 - sources/app/layout/nav_menu.phtml | 198 - sources/app/views/auth/formLogin.phtml | 32 - sources/app/views/auth/index.phtml | 85 - sources/app/views/auth/logout.phtml | 0 sources/app/views/auth/personaLogin.phtml | 28 - sources/app/views/auth/register.phtml | 38 - sources/app/views/auth/reset.phtml | 33 - sources/app/views/configure/archiving.phtml | 84 - sources/app/views/configure/display.phtml | 123 - sources/app/views/configure/queries.phtml | 87 - sources/app/views/configure/reading.phtml | 177 - sources/app/views/configure/sharing.phtml | 66 - sources/app/views/configure/shortcut.phtml | 135 - sources/app/views/configure/system.phtml | 61 - sources/app/views/entry/bookmark.phtml | 17 - sources/app/views/entry/read.phtml | 17 - sources/app/views/error/index.phtml | 9 - sources/app/views/extension/configure.phtml | 3 - sources/app/views/extension/index.phtml | 44 - sources/app/views/feed/actualize.phtml | 1 - sources/app/views/feed/add.phtml | 91 - sources/app/views/feed/move.phtml | 0 .../app/views/helpers/export/articles.phtml | 47 - sources/app/views/helpers/export/opml.phtml | 28 - .../views/helpers/extension/configure.phtml | 19 - .../app/views/helpers/extension/details.phtml | 21 - sources/app/views/helpers/feed/update.phtml | 182 - .../helpers/index/normal/entry_bottom.phtml | 86 - .../helpers/index/normal/entry_header.phtml | 33 - .../app/views/helpers/javascript_vars.phtml | 54 - .../app/views/helpers/logs_pagination.phtml | 47 - sources/app/views/helpers/pagination.phtml | 41 - sources/app/views/importExport/export.phtml | 0 sources/app/views/importExport/index.phtml | 61 - sources/app/views/index/about.phtml | 24 - sources/app/views/index/global.phtml | 54 - sources/app/views/index/index.phtml | 0 sources/app/views/index/logs.phtml | 25 - sources/app/views/index/normal.phtml | 93 - sources/app/views/index/reader.phtml | 47 - sources/app/views/index/rss.phtml | 29 - sources/app/views/javascript/actualize.phtml | 13 - .../views/javascript/nbUnreadsPerFeed.phtml | 8 - sources/app/views/javascript/nonce.phtml | 2 - sources/app/views/stats/idle.phtml | 48 - sources/app/views/stats/index.phtml | 93 - sources/app/views/stats/repartition.phtml | 74 - sources/app/views/subscription/feed.phtml | 15 - sources/app/views/subscription/index.phtml | 148 - sources/app/views/update/apply.phtml | 9 - sources/app/views/update/checkInstall.phtml | 38 - sources/app/views/update/index.phtml | 34 - sources/app/views/user/manage.phtml | 80 - sources/app/views/user/profile.phtml | 91 - sources/constants.php | 27 - sources/data/.gitignore | 10 - sources/data/.htaccess | 3 - sources/data/PubSubHubbub/feeds/.gitignore | 1 - sources/data/PubSubHubbub/feeds/README.md | 7 - sources/data/PubSubHubbub/keys/.gitignore | 1 - sources/data/PubSubHubbub/keys/README.md | 4 - sources/data/cache/.gitignore | 1 - sources/data/cache/index.html | 13 - sources/data/config.default.php | 141 - sources/data/do-install.txt | 0 sources/data/favicons/.gitignore | 2 - sources/data/favicons/index.html | 13 - sources/data/force-https.default.txt | 7 - sources/data/index.html | 13 - sources/data/persona/.gitignore | 1 - sources/data/persona/index.html | 13 - sources/data/shares.php | 92 - sources/data/tokens/.gitignore | 1 - sources/data/tokens/index.html | 13 - sources/data/users/.gitignore | 4 - sources/data/users/_/config.default.php | 72 - sources/data/users/index.html | 13 - sources/extensions/.gitignore | 1 - sources/extensions/README.md | 3 - sources/index.html | 13 - sources/index.php | 3 - sources/{app/SQL => }/install_ynh.sql | 0 sources/lib/.htaccess | 3 - sources/lib/Favicon/DataAccess.php | 41 - sources/lib/Favicon/Favicon.php | 293 - sources/lib/JSON.php | 933 --- sources/lib/Minz/ActionController.php | 38 - sources/lib/Minz/ActionException.php | 9 - sources/lib/Minz/Configuration.php | 215 - sources/lib/Minz/ConfigurationException.php | 8 - .../Minz/ConfigurationNamespaceException.php | 4 - .../lib/Minz/ConfigurationParamException.php | 4 - ...ControllerNotActionControllerException.php | 9 - .../lib/Minz/ControllerNotExistException.php | 9 - .../Minz/CurrentPagePaginationException.php | 8 - sources/lib/Minz/Dispatcher.php | 160 - sources/lib/Minz/Error.php | 82 - sources/lib/Minz/Exception.php | 16 - sources/lib/Minz/Extension.php | 208 - sources/lib/Minz/ExtensionException.php | 15 - sources/lib/Minz/ExtensionManager.php | 301 - sources/lib/Minz/FileNotExistException.php | 8 - sources/lib/Minz/FrontController.php | 135 - sources/lib/Minz/Helper.php | 33 - sources/lib/Minz/Log.php | 105 - sources/lib/Minz/Model.php | 12 - sources/lib/Minz/ModelArray.php | 81 - sources/lib/Minz/ModelPdo.php | 140 - sources/lib/Minz/PDOConnectionException.php | 9 - sources/lib/Minz/Paginator.php | 196 - .../lib/Minz/PermissionDeniedException.php | 8 - sources/lib/Minz/Request.php | 255 - sources/lib/Minz/Session.php | 100 - sources/lib/Minz/Translate.php | 230 - sources/lib/Minz/Url.php | 128 - sources/lib/Minz/View.php | 266 - sources/lib/SimplePie/SimplePie.php | 3212 ---------- sources/lib/SimplePie/SimplePie/Author.php | 157 - sources/lib/SimplePie/SimplePie/Cache.php | 133 - .../lib/SimplePie/SimplePie/Cache/Base.php | 114 - sources/lib/SimplePie/SimplePie/Cache/DB.php | 137 - .../lib/SimplePie/SimplePie/Cache/File.php | 165 - .../SimplePie/SimplePie/Cache/Memcache.php | 181 - .../lib/SimplePie/SimplePie/Cache/MySQL.php | 439 -- sources/lib/SimplePie/SimplePie/Caption.php | 210 - sources/lib/SimplePie/SimplePie/Category.php | 157 - .../SimplePie/Content/Type/Sniffer.php | 332 - sources/lib/SimplePie/SimplePie/Copyright.php | 130 - sources/lib/SimplePie/SimplePie/Core.php | 57 - sources/lib/SimplePie/SimplePie/Credit.php | 156 - .../SimplePie/Decode/HTML/Entities.php | 616 -- sources/lib/SimplePie/SimplePie/Enclosure.php | 1380 ---- sources/lib/SimplePie/SimplePie/Exception.php | 52 - sources/lib/SimplePie/SimplePie/File.php | 308 - .../lib/SimplePie/SimplePie/HTTP/Parser.php | 500 -- sources/lib/SimplePie/SimplePie/IRI.php | 1238 ---- sources/lib/SimplePie/SimplePie/Item.php | 2991 --------- sources/lib/SimplePie/SimplePie/Locator.php | 372 -- sources/lib/SimplePie/SimplePie/Misc.php | 2254 ------- sources/lib/SimplePie/SimplePie/Net/IPv6.php | 276 - .../lib/SimplePie/SimplePie/Parse/Date.php | 984 --- sources/lib/SimplePie/SimplePie/Parser.php | 438 -- sources/lib/SimplePie/SimplePie/Rating.php | 129 - sources/lib/SimplePie/SimplePie/Registry.php | 225 - .../lib/SimplePie/SimplePie/Restriction.php | 155 - sources/lib/SimplePie/SimplePie/Sanitize.php | 669 -- sources/lib/SimplePie/SimplePie/Source.php | 611 -- .../SimplePie/XML/Declaration/Parser.php | 362 -- sources/lib/SimplePie/SimplePie/gzdecode.php | 371 -- sources/lib/http-conditional.php | 212 - sources/lib/lib_date.php | 131 - sources/lib/lib_opml.php | 313 - sources/lib/lib_phpQuery.php | 5702 ----------------- sources/lib/lib_rss.php | 554 -- sources/lib/password_compat.php | 279 - sources/p/.htaccess | 33 - sources/p/Web.config | 36 - sources/p/api/.htaccess | 4 - sources/p/api/greader.php | 668 -- sources/p/api/index.html | 20 - sources/p/api/pshb.php | 142 - sources/p/ext.php | 71 - sources/p/f.php | 91 - sources/p/favicon.ico | Bin 18102 -> 0 bytes sources/p/i/.gitignore | 1 - sources/p/i/index.php | 52 - sources/p/index.html | 19 - sources/p/robots.txt | 2 - sources/p/scripts/bcrypt.min.js | 45 - sources/p/scripts/category.js | 118 - sources/p/scripts/flotr2.min.js | 27 - sources/p/scripts/global_view.js | 85 - sources/p/scripts/install.js | 76 - sources/p/scripts/jquery.min.js | 4 - sources/p/scripts/main.js | 1354 ---- sources/p/scripts/persona.js | 76 - sources/p/scripts/repartition.js | 72 - sources/p/scripts/shortcut.js | 225 - sources/p/scripts/stats.js | 56 - sources/p/themes/.htaccess | 21 - sources/p/themes/BlueLagoon/BlueLagoon.css | 1205 ---- sources/p/themes/BlueLagoon/README.md | 38 - .../p/themes/BlueLagoon/icons/bookmark.svg | 5 - .../BlueLagoon/icons/favicon-16-32-48-64.ico | Bin 1150 -> 0 bytes .../p/themes/BlueLagoon/icons/favicon-256.png | Bin 16517 -> 0 bytes sources/p/themes/BlueLagoon/icons/favicon.svg | 32 - sources/p/themes/BlueLagoon/icons/icon.svg | 34 - .../p/themes/BlueLagoon/icons/non-starred.svg | 5 - sources/p/themes/BlueLagoon/icons/read.svg | 4 - sources/p/themes/BlueLagoon/icons/starred.svg | 5 - sources/p/themes/BlueLagoon/icons/unread.svg | 3 - sources/p/themes/BlueLagoon/loader.gif | Bin 3164 -> 0 bytes sources/p/themes/BlueLagoon/metadata.json | 7 - sources/p/themes/BlueLagoon/template.css | 695 -- .../p/themes/BlueLagoon/thumbs/original.png | Bin 153829 -> 0 bytes sources/p/themes/Dark/dark.css | 1035 --- sources/p/themes/Dark/icons/icon.svg | 12 - sources/p/themes/Dark/loader.gif | Bin 404 -> 0 bytes sources/p/themes/Dark/metadata.json | 7 - sources/p/themes/Dark/thumbs/original.png | Bin 123928 -> 0 bytes sources/p/themes/Flat/flat.css | 1024 --- sources/p/themes/Flat/icons/add.svg | 30 - sources/p/themes/Flat/icons/all.svg | 32 - sources/p/themes/Flat/icons/category.svg | 31 - sources/p/themes/Flat/icons/close.svg | 28 - sources/p/themes/Flat/icons/configure.svg | 31 - sources/p/themes/Flat/icons/down.svg | 31 - sources/p/themes/Flat/icons/icon.svg | 12 - sources/p/themes/Flat/icons/key.svg | 7 - sources/p/themes/Flat/icons/next.svg | 31 - sources/p/themes/Flat/icons/prev.svg | 31 - sources/p/themes/Flat/icons/refresh.svg | 31 - sources/p/themes/Flat/icons/rss.svg | 6 - sources/p/themes/Flat/icons/search.svg | 32 - sources/p/themes/Flat/icons/up.svg | 31 - sources/p/themes/Flat/icons/view-global.svg | 1 - sources/p/themes/Flat/icons/view-normal.svg | 1 - sources/p/themes/Flat/icons/view-reader.svg | 1 - sources/p/themes/Flat/loader.gif | Bin 4251 -> 0 bytes sources/p/themes/Flat/metadata.json | 7 - sources/p/themes/Flat/thumbs/original.png | Bin 133420 -> 0 bytes sources/p/themes/Origine/loader.gif | Bin 4167 -> 0 bytes sources/p/themes/Origine/metadata.json | 7 - sources/p/themes/Origine/origine.css | 1071 ---- sources/p/themes/Origine/thumbs/original.png | Bin 135886 -> 0 bytes sources/p/themes/Pafat/README.md | 4 - sources/p/themes/Pafat/icons/all.svg | 7 - sources/p/themes/Pafat/icons/bookmark.svg | 5 - sources/p/themes/Pafat/icons/down.svg | 5 - sources/p/themes/Pafat/icons/icon.svg | 12 - sources/p/themes/Pafat/icons/link.svg | 7 - sources/p/themes/Pafat/icons/login.svg | 6 - sources/p/themes/Pafat/icons/logout.svg | 6 - sources/p/themes/Pafat/icons/next.svg | 5 - sources/p/themes/Pafat/icons/non-starred.svg | 5 - sources/p/themes/Pafat/icons/prev.svg | 5 - sources/p/themes/Pafat/icons/read.svg | 5 - sources/p/themes/Pafat/icons/share.svg | 8 - sources/p/themes/Pafat/icons/starred.svg | 5 - sources/p/themes/Pafat/icons/tag.svg | 5 - sources/p/themes/Pafat/icons/unread.svg | 6 - sources/p/themes/Pafat/icons/up.svg | 5 - sources/p/themes/Pafat/loader.gif | Bin 2608 -> 0 bytes sources/p/themes/Pafat/metadata.json | 7 - sources/p/themes/Pafat/pafat.css | 1082 ---- sources/p/themes/Pafat/thumbs/original.png | Bin 129220 -> 0 bytes sources/p/themes/Screwdriver/README.md | 36 - .../p/themes/Screwdriver/icons/bookmark.svg | 5 - .../Screwdriver/icons/favicon-16-32-48-64.ico | Bin 1150 -> 0 bytes .../themes/Screwdriver/icons/favicon-256.png | Bin 15724 -> 0 bytes .../p/themes/Screwdriver/icons/favicon.svg | 34 - sources/p/themes/Screwdriver/icons/icon.svg | 34 - sources/p/themes/Screwdriver/icons/read.svg | 3 - .../p/themes/Screwdriver/icons/starred.svg | 5 - sources/p/themes/Screwdriver/icons/unread.svg | 3 - sources/p/themes/Screwdriver/loader.gif | Bin 4167 -> 0 bytes sources/p/themes/Screwdriver/metadata.json | 7 - sources/p/themes/Screwdriver/screwdriver.css | 1204 ---- .../p/themes/Screwdriver/thumbs/original.png | Bin 133107 -> 0 bytes sources/p/themes/base-theme/README.md | 12 - sources/p/themes/base-theme/base.css | 818 --- sources/p/themes/base-theme/loader.gif | Bin 4167 -> 0 bytes sources/p/themes/base-theme/metadata.json | 7 - sources/p/themes/base-theme/template.css | 908 --- sources/p/themes/fonts/openSans.woff | Bin 21956 -> 0 bytes sources/p/themes/icons/add.svg | 5 - sources/p/themes/icons/all.svg | 7 - sources/p/themes/icons/apple-touch-icon.png | Bin 5648 -> 0 bytes sources/p/themes/icons/bookmark-add.svg | 6 - sources/p/themes/icons/bookmark.svg | 5 - sources/p/themes/icons/category-white.svg | 7 - sources/p/themes/icons/category.svg | 7 - sources/p/themes/icons/close.svg | 7 - sources/p/themes/icons/configure.svg | 5 - sources/p/themes/icons/default_favicon.ico | Bin 1150 -> 0 bytes sources/p/themes/icons/down.svg | 5 - .../p/themes/icons/favicon-16-32-48-64.ico | Bin 32038 -> 0 bytes sources/p/themes/icons/favicon-256.png | Bin 17174 -> 0 bytes sources/p/themes/icons/favicon.svg | 13 - sources/p/themes/icons/grey.gif | Bin 56 -> 0 bytes sources/p/themes/icons/help.svg | 7 - sources/p/themes/icons/icon.svg | 12 - sources/p/themes/icons/import.svg | 1 - sources/p/themes/icons/key.svg | 7 - sources/p/themes/icons/link.svg | 7 - sources/p/themes/icons/login.svg | 6 - sources/p/themes/icons/logout.svg | 6 - sources/p/themes/icons/next.svg | 5 - sources/p/themes/icons/non-starred.svg | 5 - sources/p/themes/icons/prev.svg | 5 - sources/p/themes/icons/read.svg | 5 - sources/p/themes/icons/refresh.svg | 5 - sources/p/themes/icons/rss.svg | 6 - sources/p/themes/icons/search.svg | 6 - sources/p/themes/icons/share.svg | 8 - sources/p/themes/icons/starred.svg | 5 - sources/p/themes/icons/stats.svg | 6 - sources/p/themes/icons/tag.svg | 5 - sources/p/themes/icons/unread.svg | 6 - sources/p/themes/icons/up.svg | 5 - sources/p/themes/icons/view-global.svg | 1 - sources/p/themes/icons/view-normal.svg | 1 - sources/p/themes/icons/view-reader.svg | 1 - sources/p/themes/index.html | 13 - sources/p/themes/p.css | 17 - sources/tests/app/Models/CategoryTest.php | 32 - sources/tests/app/Models/ContextTest.php | 5 - sources/tests/app/Models/SearchTest.php | 293 - sources/tests/app/Models/UserQueryTest.php | 229 - sources/tests/bootstrap.php | 7 - sources/tests/phpunit.xml | 13 - 432 files changed, 29 insertions(+), 66900 deletions(-) delete mode 100755 sources/CHANGELOG.md delete mode 100755 sources/CONTRIBUTING.md delete mode 100755 sources/CREDITS.md delete mode 100755 sources/LICENSE delete mode 100755 sources/README.fr.md delete mode 100755 sources/README.md delete mode 100755 sources/app/.htaccess delete mode 100755 sources/app/Controllers/authController.php delete mode 100755 sources/app/Controllers/categoryController.php delete mode 100755 sources/app/Controllers/configureController.php delete mode 100755 sources/app/Controllers/entryController.php delete mode 100755 sources/app/Controllers/errorController.php delete mode 100755 sources/app/Controllers/extensionController.php delete mode 100755 sources/app/Controllers/feedController.php delete mode 100755 sources/app/Controllers/importExportController.php delete mode 100755 sources/app/Controllers/indexController.php delete mode 100755 sources/app/Controllers/javascriptController.php delete mode 100755 sources/app/Controllers/statsController.php delete mode 100755 sources/app/Controllers/subscriptionController.php delete mode 100755 sources/app/Controllers/updateController.php delete mode 100755 sources/app/Controllers/userController.php delete mode 100755 sources/app/Exceptions/BadUrlException.php delete mode 100755 sources/app/Exceptions/ContextException.php delete mode 100755 sources/app/Exceptions/DAOException.php delete mode 100755 sources/app/Exceptions/EntriesGetterException.php delete mode 100755 sources/app/Exceptions/FeedException.php delete mode 100755 sources/app/FreshRSS.php delete mode 100755 sources/app/Models/Auth.php delete mode 100755 sources/app/Models/Category.php delete mode 100755 sources/app/Models/CategoryDAO.php delete mode 100755 sources/app/Models/ConfigurationSetter.php delete mode 100755 sources/app/Models/Context.php delete mode 100755 sources/app/Models/DatabaseDAO.php delete mode 100755 sources/app/Models/DatabaseDAOSQLite.php delete mode 100755 sources/app/Models/Days.php delete mode 100755 sources/app/Models/Entry.php delete mode 100755 sources/app/Models/EntryDAO.php delete mode 100755 sources/app/Models/EntryDAOSQLite.php delete mode 100755 sources/app/Models/Factory.php delete mode 100755 sources/app/Models/Feed.php delete mode 100755 sources/app/Models/FeedDAO.php delete mode 100755 sources/app/Models/FeedDAOSQLite.php delete mode 100755 sources/app/Models/Log.php delete mode 100755 sources/app/Models/LogDAO.php delete mode 100755 sources/app/Models/Search.php delete mode 100755 sources/app/Models/Searchable.php delete mode 100755 sources/app/Models/Share.php delete mode 100755 sources/app/Models/StatsDAO.php delete mode 100755 sources/app/Models/StatsDAOSQLite.php delete mode 100755 sources/app/Models/Themes.php delete mode 100755 sources/app/Models/UserDAO.php delete mode 100755 sources/app/Models/UserQuery.php delete mode 100755 sources/app/SQL/install.sql.mysql.php delete mode 100755 sources/app/SQL/install.sql.sqlite.php delete mode 100755 sources/app/actualize_script.php delete mode 100755 sources/app/i18n/cz/admin.php delete mode 100755 sources/app/i18n/cz/conf.php delete mode 100755 sources/app/i18n/cz/feedback.php delete mode 100755 sources/app/i18n/cz/gen.php delete mode 100755 sources/app/i18n/cz/index.php delete mode 100755 sources/app/i18n/cz/install.php delete mode 100755 sources/app/i18n/cz/sub.php delete mode 100755 sources/app/i18n/de/admin.php delete mode 100755 sources/app/i18n/de/conf.php delete mode 100755 sources/app/i18n/de/feedback.php delete mode 100755 sources/app/i18n/de/gen.php delete mode 100755 sources/app/i18n/de/index.php delete mode 100755 sources/app/i18n/de/install.php delete mode 100755 sources/app/i18n/de/sub.php delete mode 100755 sources/app/i18n/en/admin.php delete mode 100755 sources/app/i18n/en/conf.php delete mode 100755 sources/app/i18n/en/feedback.php delete mode 100755 sources/app/i18n/en/gen.php delete mode 100755 sources/app/i18n/en/index.php delete mode 100755 sources/app/i18n/en/install.php delete mode 100755 sources/app/i18n/en/sub.php delete mode 100755 sources/app/i18n/fr/admin.php delete mode 100755 sources/app/i18n/fr/conf.php delete mode 100755 sources/app/i18n/fr/feedback.php delete mode 100755 sources/app/i18n/fr/gen.php delete mode 100755 sources/app/i18n/fr/index.php delete mode 100755 sources/app/i18n/fr/install.php delete mode 100755 sources/app/i18n/fr/sub.php delete mode 100755 sources/app/i18n/it/admin.php delete mode 100755 sources/app/i18n/it/conf.php delete mode 100755 sources/app/i18n/it/feedback.php delete mode 100755 sources/app/i18n/it/gen.php delete mode 100755 sources/app/i18n/it/index.php delete mode 100755 sources/app/i18n/it/install.php delete mode 100755 sources/app/i18n/it/sub.php delete mode 100755 sources/app/i18n/nl/admin.php delete mode 100755 sources/app/i18n/nl/conf.php delete mode 100755 sources/app/i18n/nl/feedback.php delete mode 100755 sources/app/i18n/nl/gen.php delete mode 100755 sources/app/i18n/nl/index.php delete mode 100755 sources/app/i18n/nl/install.php delete mode 100755 sources/app/i18n/nl/sub.php delete mode 100755 sources/app/i18n/ru/admin.php delete mode 100755 sources/app/i18n/ru/conf.php delete mode 100755 sources/app/i18n/ru/feedback.php delete mode 100755 sources/app/i18n/ru/gen.php delete mode 100755 sources/app/i18n/ru/index.php delete mode 100755 sources/app/i18n/ru/install.php delete mode 100755 sources/app/i18n/ru/sub.php delete mode 100755 sources/app/i18n/tr/admin.php delete mode 100755 sources/app/i18n/tr/conf.php delete mode 100755 sources/app/i18n/tr/feedback.php delete mode 100755 sources/app/i18n/tr/gen.php delete mode 100755 sources/app/i18n/tr/index.php delete mode 100755 sources/app/i18n/tr/install.php delete mode 100755 sources/app/i18n/tr/sub.php delete mode 100755 sources/app/index.html delete mode 100755 sources/app/install.php delete mode 100755 sources/app/layout/aside_configure.phtml delete mode 100755 sources/app/layout/aside_feed.phtml delete mode 100755 sources/app/layout/aside_stats.phtml delete mode 100755 sources/app/layout/aside_subscription.phtml delete mode 100755 sources/app/layout/header.phtml delete mode 100755 sources/app/layout/layout.phtml delete mode 100755 sources/app/layout/nav_entries.phtml delete mode 100755 sources/app/layout/nav_menu.phtml delete mode 100755 sources/app/views/auth/formLogin.phtml delete mode 100755 sources/app/views/auth/index.phtml delete mode 100755 sources/app/views/auth/logout.phtml delete mode 100755 sources/app/views/auth/personaLogin.phtml delete mode 100755 sources/app/views/auth/register.phtml delete mode 100755 sources/app/views/auth/reset.phtml delete mode 100755 sources/app/views/configure/archiving.phtml delete mode 100755 sources/app/views/configure/display.phtml delete mode 100755 sources/app/views/configure/queries.phtml delete mode 100755 sources/app/views/configure/reading.phtml delete mode 100755 sources/app/views/configure/sharing.phtml delete mode 100755 sources/app/views/configure/shortcut.phtml delete mode 100755 sources/app/views/configure/system.phtml delete mode 100755 sources/app/views/entry/bookmark.phtml delete mode 100755 sources/app/views/entry/read.phtml delete mode 100755 sources/app/views/error/index.phtml delete mode 100755 sources/app/views/extension/configure.phtml delete mode 100755 sources/app/views/extension/index.phtml delete mode 100755 sources/app/views/feed/actualize.phtml delete mode 100755 sources/app/views/feed/add.phtml delete mode 100755 sources/app/views/feed/move.phtml delete mode 100755 sources/app/views/helpers/export/articles.phtml delete mode 100755 sources/app/views/helpers/export/opml.phtml delete mode 100755 sources/app/views/helpers/extension/configure.phtml delete mode 100755 sources/app/views/helpers/extension/details.phtml delete mode 100755 sources/app/views/helpers/feed/update.phtml delete mode 100755 sources/app/views/helpers/index/normal/entry_bottom.phtml delete mode 100755 sources/app/views/helpers/index/normal/entry_header.phtml delete mode 100755 sources/app/views/helpers/javascript_vars.phtml delete mode 100755 sources/app/views/helpers/logs_pagination.phtml delete mode 100755 sources/app/views/helpers/pagination.phtml delete mode 100755 sources/app/views/importExport/export.phtml delete mode 100755 sources/app/views/importExport/index.phtml delete mode 100755 sources/app/views/index/about.phtml delete mode 100755 sources/app/views/index/global.phtml delete mode 100755 sources/app/views/index/index.phtml delete mode 100755 sources/app/views/index/logs.phtml delete mode 100755 sources/app/views/index/normal.phtml delete mode 100755 sources/app/views/index/reader.phtml delete mode 100755 sources/app/views/index/rss.phtml delete mode 100755 sources/app/views/javascript/actualize.phtml delete mode 100755 sources/app/views/javascript/nbUnreadsPerFeed.phtml delete mode 100755 sources/app/views/javascript/nonce.phtml delete mode 100755 sources/app/views/stats/idle.phtml delete mode 100755 sources/app/views/stats/index.phtml delete mode 100755 sources/app/views/stats/repartition.phtml delete mode 100755 sources/app/views/subscription/feed.phtml delete mode 100755 sources/app/views/subscription/index.phtml delete mode 100755 sources/app/views/update/apply.phtml delete mode 100755 sources/app/views/update/checkInstall.phtml delete mode 100755 sources/app/views/update/index.phtml delete mode 100755 sources/app/views/user/manage.phtml delete mode 100755 sources/app/views/user/profile.phtml delete mode 100755 sources/constants.php delete mode 100755 sources/data/.gitignore delete mode 100755 sources/data/.htaccess delete mode 100755 sources/data/PubSubHubbub/feeds/.gitignore delete mode 100755 sources/data/PubSubHubbub/feeds/README.md delete mode 100755 sources/data/PubSubHubbub/keys/.gitignore delete mode 100755 sources/data/PubSubHubbub/keys/README.md delete mode 100755 sources/data/cache/.gitignore delete mode 100755 sources/data/cache/index.html delete mode 100755 sources/data/config.default.php delete mode 100755 sources/data/do-install.txt delete mode 100755 sources/data/favicons/.gitignore delete mode 100755 sources/data/favicons/index.html delete mode 100755 sources/data/force-https.default.txt delete mode 100755 sources/data/index.html delete mode 100755 sources/data/persona/.gitignore delete mode 100755 sources/data/persona/index.html delete mode 100755 sources/data/shares.php delete mode 100755 sources/data/tokens/.gitignore delete mode 100755 sources/data/tokens/index.html delete mode 100755 sources/data/users/.gitignore delete mode 100755 sources/data/users/_/config.default.php delete mode 100755 sources/data/users/index.html delete mode 100755 sources/extensions/.gitignore delete mode 100755 sources/extensions/README.md delete mode 100755 sources/index.html delete mode 100755 sources/index.php rename sources/{app/SQL => }/install_ynh.sql (100%) delete mode 100755 sources/lib/.htaccess delete mode 100755 sources/lib/Favicon/DataAccess.php delete mode 100755 sources/lib/Favicon/Favicon.php delete mode 100755 sources/lib/JSON.php delete mode 100755 sources/lib/Minz/ActionController.php delete mode 100755 sources/lib/Minz/ActionException.php delete mode 100755 sources/lib/Minz/Configuration.php delete mode 100755 sources/lib/Minz/ConfigurationException.php delete mode 100755 sources/lib/Minz/ConfigurationNamespaceException.php delete mode 100755 sources/lib/Minz/ConfigurationParamException.php delete mode 100755 sources/lib/Minz/ControllerNotActionControllerException.php delete mode 100755 sources/lib/Minz/ControllerNotExistException.php delete mode 100755 sources/lib/Minz/CurrentPagePaginationException.php delete mode 100755 sources/lib/Minz/Dispatcher.php delete mode 100755 sources/lib/Minz/Error.php delete mode 100755 sources/lib/Minz/Exception.php delete mode 100755 sources/lib/Minz/Extension.php delete mode 100755 sources/lib/Minz/ExtensionException.php delete mode 100755 sources/lib/Minz/ExtensionManager.php delete mode 100755 sources/lib/Minz/FileNotExistException.php delete mode 100755 sources/lib/Minz/FrontController.php delete mode 100755 sources/lib/Minz/Helper.php delete mode 100755 sources/lib/Minz/Log.php delete mode 100755 sources/lib/Minz/Model.php delete mode 100755 sources/lib/Minz/ModelArray.php delete mode 100755 sources/lib/Minz/ModelPdo.php delete mode 100755 sources/lib/Minz/PDOConnectionException.php delete mode 100755 sources/lib/Minz/Paginator.php delete mode 100755 sources/lib/Minz/PermissionDeniedException.php delete mode 100755 sources/lib/Minz/Request.php delete mode 100755 sources/lib/Minz/Session.php delete mode 100755 sources/lib/Minz/Translate.php delete mode 100755 sources/lib/Minz/Url.php delete mode 100755 sources/lib/Minz/View.php delete mode 100755 sources/lib/SimplePie/SimplePie.php delete mode 100755 sources/lib/SimplePie/SimplePie/Author.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache/Base.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache/DB.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache/File.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache/Memcache.php delete mode 100755 sources/lib/SimplePie/SimplePie/Cache/MySQL.php delete mode 100755 sources/lib/SimplePie/SimplePie/Caption.php delete mode 100755 sources/lib/SimplePie/SimplePie/Category.php delete mode 100755 sources/lib/SimplePie/SimplePie/Content/Type/Sniffer.php delete mode 100755 sources/lib/SimplePie/SimplePie/Copyright.php delete mode 100755 sources/lib/SimplePie/SimplePie/Core.php delete mode 100755 sources/lib/SimplePie/SimplePie/Credit.php delete mode 100755 sources/lib/SimplePie/SimplePie/Decode/HTML/Entities.php delete mode 100755 sources/lib/SimplePie/SimplePie/Enclosure.php delete mode 100755 sources/lib/SimplePie/SimplePie/Exception.php delete mode 100755 sources/lib/SimplePie/SimplePie/File.php delete mode 100755 sources/lib/SimplePie/SimplePie/HTTP/Parser.php delete mode 100755 sources/lib/SimplePie/SimplePie/IRI.php delete mode 100755 sources/lib/SimplePie/SimplePie/Item.php delete mode 100755 sources/lib/SimplePie/SimplePie/Locator.php delete mode 100755 sources/lib/SimplePie/SimplePie/Misc.php delete mode 100755 sources/lib/SimplePie/SimplePie/Net/IPv6.php delete mode 100755 sources/lib/SimplePie/SimplePie/Parse/Date.php delete mode 100755 sources/lib/SimplePie/SimplePie/Parser.php delete mode 100755 sources/lib/SimplePie/SimplePie/Rating.php delete mode 100755 sources/lib/SimplePie/SimplePie/Registry.php delete mode 100755 sources/lib/SimplePie/SimplePie/Restriction.php delete mode 100755 sources/lib/SimplePie/SimplePie/Sanitize.php delete mode 100755 sources/lib/SimplePie/SimplePie/Source.php delete mode 100755 sources/lib/SimplePie/SimplePie/XML/Declaration/Parser.php delete mode 100755 sources/lib/SimplePie/SimplePie/gzdecode.php delete mode 100755 sources/lib/http-conditional.php delete mode 100755 sources/lib/lib_date.php delete mode 100755 sources/lib/lib_opml.php delete mode 100755 sources/lib/lib_phpQuery.php delete mode 100755 sources/lib/lib_rss.php delete mode 100755 sources/lib/password_compat.php delete mode 100755 sources/p/.htaccess delete mode 100755 sources/p/Web.config delete mode 100755 sources/p/api/.htaccess delete mode 100755 sources/p/api/greader.php delete mode 100755 sources/p/api/index.html delete mode 100755 sources/p/api/pshb.php delete mode 100755 sources/p/ext.php delete mode 100755 sources/p/f.php delete mode 100755 sources/p/favicon.ico delete mode 100755 sources/p/i/.gitignore delete mode 100755 sources/p/i/index.php delete mode 100755 sources/p/index.html delete mode 100755 sources/p/robots.txt delete mode 100755 sources/p/scripts/bcrypt.min.js delete mode 100755 sources/p/scripts/category.js delete mode 100755 sources/p/scripts/flotr2.min.js delete mode 100755 sources/p/scripts/global_view.js delete mode 100755 sources/p/scripts/install.js delete mode 100755 sources/p/scripts/jquery.min.js delete mode 100755 sources/p/scripts/main.js delete mode 100755 sources/p/scripts/persona.js delete mode 100755 sources/p/scripts/repartition.js delete mode 100755 sources/p/scripts/shortcut.js delete mode 100755 sources/p/scripts/stats.js delete mode 100755 sources/p/themes/.htaccess delete mode 100755 sources/p/themes/BlueLagoon/BlueLagoon.css delete mode 100755 sources/p/themes/BlueLagoon/README.md delete mode 100755 sources/p/themes/BlueLagoon/icons/bookmark.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/favicon-16-32-48-64.ico delete mode 100755 sources/p/themes/BlueLagoon/icons/favicon-256.png delete mode 100755 sources/p/themes/BlueLagoon/icons/favicon.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/icon.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/non-starred.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/read.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/starred.svg delete mode 100755 sources/p/themes/BlueLagoon/icons/unread.svg delete mode 100755 sources/p/themes/BlueLagoon/loader.gif delete mode 100755 sources/p/themes/BlueLagoon/metadata.json delete mode 100755 sources/p/themes/BlueLagoon/template.css delete mode 100755 sources/p/themes/BlueLagoon/thumbs/original.png delete mode 100755 sources/p/themes/Dark/dark.css delete mode 100755 sources/p/themes/Dark/icons/icon.svg delete mode 100755 sources/p/themes/Dark/loader.gif delete mode 100755 sources/p/themes/Dark/metadata.json delete mode 100755 sources/p/themes/Dark/thumbs/original.png delete mode 100755 sources/p/themes/Flat/flat.css delete mode 100755 sources/p/themes/Flat/icons/add.svg delete mode 100755 sources/p/themes/Flat/icons/all.svg delete mode 100755 sources/p/themes/Flat/icons/category.svg delete mode 100755 sources/p/themes/Flat/icons/close.svg delete mode 100755 sources/p/themes/Flat/icons/configure.svg delete mode 100755 sources/p/themes/Flat/icons/down.svg delete mode 100755 sources/p/themes/Flat/icons/icon.svg delete mode 100755 sources/p/themes/Flat/icons/key.svg delete mode 100755 sources/p/themes/Flat/icons/next.svg delete mode 100755 sources/p/themes/Flat/icons/prev.svg delete mode 100755 sources/p/themes/Flat/icons/refresh.svg delete mode 100755 sources/p/themes/Flat/icons/rss.svg delete mode 100755 sources/p/themes/Flat/icons/search.svg delete mode 100755 sources/p/themes/Flat/icons/up.svg delete mode 100755 sources/p/themes/Flat/icons/view-global.svg delete mode 100755 sources/p/themes/Flat/icons/view-normal.svg delete mode 100755 sources/p/themes/Flat/icons/view-reader.svg delete mode 100755 sources/p/themes/Flat/loader.gif delete mode 100755 sources/p/themes/Flat/metadata.json delete mode 100755 sources/p/themes/Flat/thumbs/original.png delete mode 100755 sources/p/themes/Origine/loader.gif delete mode 100755 sources/p/themes/Origine/metadata.json delete mode 100755 sources/p/themes/Origine/origine.css delete mode 100755 sources/p/themes/Origine/thumbs/original.png delete mode 100755 sources/p/themes/Pafat/README.md delete mode 100755 sources/p/themes/Pafat/icons/all.svg delete mode 100755 sources/p/themes/Pafat/icons/bookmark.svg delete mode 100755 sources/p/themes/Pafat/icons/down.svg delete mode 100755 sources/p/themes/Pafat/icons/icon.svg delete mode 100755 sources/p/themes/Pafat/icons/link.svg delete mode 100755 sources/p/themes/Pafat/icons/login.svg delete mode 100755 sources/p/themes/Pafat/icons/logout.svg delete mode 100755 sources/p/themes/Pafat/icons/next.svg delete mode 100755 sources/p/themes/Pafat/icons/non-starred.svg delete mode 100755 sources/p/themes/Pafat/icons/prev.svg delete mode 100755 sources/p/themes/Pafat/icons/read.svg delete mode 100755 sources/p/themes/Pafat/icons/share.svg delete mode 100755 sources/p/themes/Pafat/icons/starred.svg delete mode 100755 sources/p/themes/Pafat/icons/tag.svg delete mode 100755 sources/p/themes/Pafat/icons/unread.svg delete mode 100755 sources/p/themes/Pafat/icons/up.svg delete mode 100755 sources/p/themes/Pafat/loader.gif delete mode 100755 sources/p/themes/Pafat/metadata.json delete mode 100755 sources/p/themes/Pafat/pafat.css delete mode 100755 sources/p/themes/Pafat/thumbs/original.png delete mode 100755 sources/p/themes/Screwdriver/README.md delete mode 100755 sources/p/themes/Screwdriver/icons/bookmark.svg delete mode 100755 sources/p/themes/Screwdriver/icons/favicon-16-32-48-64.ico delete mode 100755 sources/p/themes/Screwdriver/icons/favicon-256.png delete mode 100755 sources/p/themes/Screwdriver/icons/favicon.svg delete mode 100755 sources/p/themes/Screwdriver/icons/icon.svg delete mode 100755 sources/p/themes/Screwdriver/icons/read.svg delete mode 100755 sources/p/themes/Screwdriver/icons/starred.svg delete mode 100755 sources/p/themes/Screwdriver/icons/unread.svg delete mode 100755 sources/p/themes/Screwdriver/loader.gif delete mode 100755 sources/p/themes/Screwdriver/metadata.json delete mode 100755 sources/p/themes/Screwdriver/screwdriver.css delete mode 100755 sources/p/themes/Screwdriver/thumbs/original.png delete mode 100755 sources/p/themes/base-theme/README.md delete mode 100755 sources/p/themes/base-theme/base.css delete mode 100755 sources/p/themes/base-theme/loader.gif delete mode 100755 sources/p/themes/base-theme/metadata.json delete mode 100755 sources/p/themes/base-theme/template.css delete mode 100755 sources/p/themes/fonts/openSans.woff delete mode 100755 sources/p/themes/icons/add.svg delete mode 100755 sources/p/themes/icons/all.svg delete mode 100755 sources/p/themes/icons/apple-touch-icon.png delete mode 100755 sources/p/themes/icons/bookmark-add.svg delete mode 100755 sources/p/themes/icons/bookmark.svg delete mode 100755 sources/p/themes/icons/category-white.svg delete mode 100755 sources/p/themes/icons/category.svg delete mode 100755 sources/p/themes/icons/close.svg delete mode 100755 sources/p/themes/icons/configure.svg delete mode 100755 sources/p/themes/icons/default_favicon.ico delete mode 100755 sources/p/themes/icons/down.svg delete mode 100755 sources/p/themes/icons/favicon-16-32-48-64.ico delete mode 100755 sources/p/themes/icons/favicon-256.png delete mode 100755 sources/p/themes/icons/favicon.svg delete mode 100755 sources/p/themes/icons/grey.gif delete mode 100755 sources/p/themes/icons/help.svg delete mode 100755 sources/p/themes/icons/icon.svg delete mode 100755 sources/p/themes/icons/import.svg delete mode 100755 sources/p/themes/icons/key.svg delete mode 100755 sources/p/themes/icons/link.svg delete mode 100755 sources/p/themes/icons/login.svg delete mode 100755 sources/p/themes/icons/logout.svg delete mode 100755 sources/p/themes/icons/next.svg delete mode 100755 sources/p/themes/icons/non-starred.svg delete mode 100755 sources/p/themes/icons/prev.svg delete mode 100755 sources/p/themes/icons/read.svg delete mode 100755 sources/p/themes/icons/refresh.svg delete mode 100755 sources/p/themes/icons/rss.svg delete mode 100755 sources/p/themes/icons/search.svg delete mode 100755 sources/p/themes/icons/share.svg delete mode 100755 sources/p/themes/icons/starred.svg delete mode 100755 sources/p/themes/icons/stats.svg delete mode 100755 sources/p/themes/icons/tag.svg delete mode 100755 sources/p/themes/icons/unread.svg delete mode 100755 sources/p/themes/icons/up.svg delete mode 100755 sources/p/themes/icons/view-global.svg delete mode 100755 sources/p/themes/icons/view-normal.svg delete mode 100755 sources/p/themes/icons/view-reader.svg delete mode 100755 sources/p/themes/index.html delete mode 100755 sources/p/themes/p.css delete mode 100755 sources/tests/app/Models/CategoryTest.php delete mode 100755 sources/tests/app/Models/ContextTest.php delete mode 100755 sources/tests/app/Models/SearchTest.php delete mode 100755 sources/tests/app/Models/UserQueryTest.php delete mode 100755 sources/tests/bootstrap.php delete mode 100755 sources/tests/phpunit.xml diff --git a/scripts/install b/scripts/install index 549c1c3..2918d07 100755 --- a/scripts/install +++ b/scripts/install @@ -1,10 +1,19 @@ #!/bin/bash +# Exit on command errors and treat unset variables as an error +set -eu + # Retrieve arguments domain=$1 path=$2 admin_user=$3 +# Load common variables and helpers +. ./_common.sh + +# Source app helpers +. /usr/share/yunohost/helpers + # Check user parameter if not empty if [[ $admin_user != '' ]]; then @@ -34,21 +43,21 @@ sudo yunohost app initdb $db_user -p $db_pwd sudo yunohost app setting freshrss mysqlpwd -v $db_pwd # Copy files to the right place -final_path=/var/www/freshrss -sudo mkdir -p $final_path -sudo cp -a ../sources/* $final_path -sudo cp -a ../conf/config.php $final_path/data +sudo mkdir -p $FINAL_PATH +extract_freshrss "$FINAL_PATH" +sudo cp -a ../conf/config.php $FINAL_PATH/data +sudo cp ../sources/install_ynh.sql $FINAL_PATH/app/SQL/install_ynh.sql # Change variables in freshrss configuration -sudo sed -i "s/yunouser/$db_user/g" $final_path/data/config.php -sudo sed -i "s/yunopass/$db_pwd/g" $final_path/data/config.php -sudo sed -i "s/yunobase/$db_user/g" $final_path/data/config.php -sudo sed -i "s/yunosalt/$app_salt/g" $final_path/data/config.php -sudo sed -i "s@yunopath@$path@g" $final_path/data/config.php +sudo sed -i "s/yunouser/$db_user/g" $FINAL_PATH/data/config.php +sudo sed -i "s/yunopass/$db_pwd/g" $FINAL_PATH/data/config.php +sudo sed -i "s/yunobase/$db_user/g" $FINAL_PATH/data/config.php +sudo sed -i "s/yunosalt/$app_salt/g" $FINAL_PATH/data/config.php +sudo sed -i "s@yunopath@$path@g" $FINAL_PATH/data/config.php if [[ $admin_user != '' ]]; then - sudo sed -i "s/yunoadminuser/$admin_user/g" $final_path/data/config.php + sudo sed -i "s/yunoadminuser/$admin_user/g" $FINAL_PATH/data/config.php else - sudo sed -i '/yunoadminuser/d' $final_path/data/config.php + sudo sed -i '/yunoadminuser/d' $FINAL_PATH/data/config.php fi # Add users @@ -66,7 +75,7 @@ freshrss_users=$(ldapsearch -h localhost -b ou=users,dc=yunohost,dc=org -x objec for myuser in $freshrss_users do #copy sql - sudo cp ../sources/app/SQL/install_ynh.sql /tmp/$myuser-install.sql + sudo cp ../sources/install_ynh.sql /tmp/$myuser-install.sql #change username in sql sudo sed -i "s/YnoUser/$myuser/g" /tmp/$myuser-install.sql #create tables @@ -74,32 +83,32 @@ do #remove temp sql sudo rm /tmp/$myuser-install.sql #copy default conf - sudo cp -r $final_path/data/users/_/ $final_path/data/users/$myuser - sudo mv $final_path/data/users/$myuser/config.default.php $final_path/data/users/$myuser/config.php + sudo cp -r $FINAL_PATH/data/users/_/ $FINAL_PATH/data/users/$myuser + sudo mv $FINAL_PATH/data/users/$myuser/config.default.php $FINAL_PATH/data/users/$myuser/config.php if [[ $sharingEnable -eq 1 ]]; then - sudo sed -i "s@'sharing'\ =>\ array\ (@$sharingWallabag@g" $final_path/data/users/$myuser/config.php + sudo sed -i "s@'sharing'\ =>\ array\ (@$sharingWallabag@g" $FINAL_PATH/data/users/$myuser/config.php fi done # Delete install directive -sudo rm $final_path/data/do-install.txt +sudo rm $FINAL_PATH/data/do-install.txt # Modify Nginx configuration file and copy it to Nginx conf directory sed -i "s@PATHTOCHANGE@$path@g" ../conf/nginx.conf -sed -i "s@ALIASTOCHANGE@$final_path/@g" ../conf/nginx.conf +sed -i "s@ALIASTOCHANGE@$FINAL_PATH/@g" ../conf/nginx.conf sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/freshrss.conf #install php5-cli sudo apt-get update sudo apt-get install -y php5-cli php5-gmp #install update cron -echo "*/10 * * * * www-data /usr/bin/php $final_path/app/actualize_script.php >/tmp/FreshRSS.log 2>&1" > /tmp/cronfreshrss +echo "*/10 * * * * www-data /usr/bin/php $FINAL_PATH/app/actualize_script.php >/tmp/FreshRSS.log 2>&1" > /tmp/cronfreshrss sudo mv /tmp/cronfreshrss /etc/cron.d/freshrss sudo chown root /etc/cron.d/freshrss # Set permissions to freshrss directory -sudo chown -R www-data: $final_path/data/ -sudo chown -R www-data: $final_path/extensions/ +sudo chown -R www-data: $FINAL_PATH/data/ +sudo chown -R www-data: $FINAL_PATH/extensions/ #skip api directory sudo yunohost app setting freshrss skipped_uris -v /api/greader.php diff --git a/sources/CHANGELOG.md b/sources/CHANGELOG.md deleted file mode 100755 index 5f0eb45..0000000 --- a/sources/CHANGELOG.md +++ /dev/null @@ -1,558 +0,0 @@ -# Changelog - -## 2016-07-23 FreshRSS 1.4.0 -## 2016-06-12 FreshRSS 1.3.2-beta - -* Compatibility - * Require at least PHP 5.3+ (drop PHP 5.2) [#1133](https://github.com/FreshRSS/FreshRSS/pull/1133) -* Features - * Support for MySQL 5.7+ (e.g. Ubuntu 16.04 LTS) [#1132](https://github.com/FreshRSS/FreshRSS/pull/1132) - * Speed optimization for HTTP/2 [#1133](https://github.com/FreshRSS/FreshRSS/pull/1133) - * API support for REDIRECT_* HTTP headers (fcgi) [#1128](https://github.com/FreshRSS/FreshRSS/issues/1128) -* SimplePie - * Support for feeds with invalid whitespace [#1142](https://github.com/FreshRSS/FreshRSS/issues/1142) -* Bug fixing - * Fix bug when adding feeds with passwords [#1137](https://github.com/FreshRSS/FreshRSS/pull/1137) - * Fix validator link [#1147](https://github.com/FreshRSS/FreshRSS/pull/1147) - * Fix Favicon small bugs [#1135](https://github.com/FreshRSS/FreshRSS/pull/1135) -* Security - * CSP compatibility for homepage [#1120](https://github.com/FreshRSS/FreshRSS/pull/1120) -* I18n - * Draft of Russian [#1085](https://github.com/FreshRSS/FreshRSS/pull/1085) -* Misc. - * Change default feed timeout to 15 seconds [#1146](https://github.com/FreshRSS/FreshRSS/pull/1146) - * Updated Wallabag v2 [#1150](https://github.com/FreshRSS/FreshRSS/pull/1150) - - -## 2016-03-11 FreshRSS 1.3.1-beta - -* Security - * Added CSP `Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *` [#1075](https://github.com/FreshRSS/FreshRSS/issues/1075), [#1114](https://github.com/FreshRSS/FreshRSS/issues/1114) - * Added `X-Content-Type-Options: nosniff` [#1116](https://github.com/FreshRSS/FreshRSS/pull/1116) - * Cookie with `Secure` tag when used over HTTPS [#1117](https://github.com/FreshRSS/FreshRSS/pull/1117) - * Limit API post input to 1MB [#1118](https://github.com/FreshRSS/FreshRSS/pull/1118) -* Features - * New list of domains for which to force HTTPS (for images, videos, iframes…) defined in `./data/force-https.default.txt` and `./data/force-https.txt` [#1083](https://github.com/FreshRSS/FreshRSS/issues/1083) - * In particular useful for privacy and to avoid mixed content errors, e.g. to see YouTube videos when FreshRSS is in HTTPS - * Add sharing with “Journal du Hacker” [#1056](https://github.com/FreshRSS/FreshRSS/pull/1056) -* UI - * Updated to jQuery 2.2.1 and changed code for auto-load on scroll [#1050](https://github.com/FreshRSS/FreshRSS/pull/1050), [#1091](https://github.com/FreshRSS/FreshRSS/pull/1091) -* I18n - * Turkish [#1073](https://github.com/FreshRSS/FreshRSS/issues/1073) -* Bug fixing - * Fixed OPML import title bug [#1048](https://github.com/FreshRSS/FreshRSS/issues/1048) - * Fixed upgrade bug with SQLite when articles were marked as unread [#1049](https://github.com/FreshRSS/FreshRSS/issues/1049) - * Fixed error when deleting feeds from statistics page [#1047](https://github.com/FreshRSS/FreshRSS/issues/1047) - * Fixed several small bugs in global and reader view [#1050](https://github.com/FreshRSS/FreshRSS/pull/1050) - * Fixed sharing bug with PHP7 [#1072](https://github.com/FreshRSS/FreshRSS/issues/1072) - * Fixed fall-back when php-json is not installed [#1092](https://github.com/FreshRSS/FreshRSS/issues/1092) -* API - * Possibility to show only read items [#1035](https://github.com/FreshRSS/FreshRSS/pull/1035) -* Misc. - * Filters `` attributes `srcset` and `sizes` [#1077](https://github.com/FreshRSS/FreshRSS/issues/1077), [#1086](https://github.com/FreshRSS/FreshRSS/pull/1086) - * Implement PubSubHubbub unsubscribe responses [#1058](https://github.com/FreshRSS/FreshRSS/issues/1058) - * Restored some compatibility with PHP 5.2 [#1055](https://github.com/FreshRSS/FreshRSS/issues/1055) - * Check for extension php-xml during install [#1094](https://github.com/FreshRSS/FreshRSS/issues/1094) - * Updated the sharing with Movim [#1030](https://github.com/FreshRSS/FreshRSS/pull/1030) - - -## 2015-11-03 FreshRSS 1.2.0 / 1.3.0-beta - -* Features - * Share with Movim [#992](https://github.com/FreshRSS/FreshRSS/issues/992) - * New option to allow robots / search engines [#938](https://github.com/FreshRSS/FreshRSS/issues/938) -* Security - * Invalid logins now return HTTP 403, to be easier to catch (e.g. fail2ban) [#1015](https://github.com/FreshRSS/FreshRSS/issues/1015) -* UI - * Remove "title" field during installation [#858](https://github.com/FreshRSS/FreshRSS/issues/858) - * Visual alert on categories containing feeds in error [#984](https://github.com/FreshRSS/FreshRSS/pull/984) -* I18n - * Italian [#1003](https://github.com/FreshRSS/FreshRSS/issues/1003) -* Misc. - * Support reverse proxy [#975](https://github.com/FreshRSS/FreshRSS/issues/975) - * Make auto-update server URL alterable [#1019](https://github.com/FreshRSS/FreshRSS/issues/1019) - - -## 2015-09-12 FreshRSS 1.1.3-beta - -* UI - * Configuration page for global settings such as limits [#958](https://github.com/FreshRSS/FreshRSS/pull/958) - * Add feed ID in articles to ease styling [#953](https://github.com/FreshRSS/FreshRSS/issues/953) -* I18n - * Dutch [#949](https://github.com/FreshRSS/FreshRSS/issues/949) -* Bug fixing - * Session cookie bug [#924](https://github.com/FreshRSS/FreshRSS/issues/924) - * Better error handling for PubSubHubbub [#939](https://github.com/FreshRSS/FreshRSS/issues/939) - * Fix tag search link from articles [#970](https://github.com/FreshRSS/FreshRSS/issues/970) - * Fix all quieries deleted when deleting a feed or category [#982](https://github.com/FreshRSS/FreshRSS/pull/982) - - -## 2015-07-30 FreshRSS 1.1.2-beta - -* Features - * Support for PubSubHubbub for instant notifications from compatible Web sites. [#312](https://github.com/FreshRSS/FreshRSS/issues/312) - * cURL options to use a proxy for retrieving feeds. [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#675](https://github.com/FreshRSS/FreshRSS/issues/675) - * Allow anonymous users to create an account. [#679](https://github.com/FreshRSS/FreshRSS/issues/679) -* Security - * cURL options to verify or not SSL/TLS certificates (now enabled by default). [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#502](https://github.com/FreshRSS/FreshRSS/issues/502) - * Support for SSL connection to MySQL. [#868](https://github.com/FreshRSS/FreshRSS/issues/868) - * Workaround for browsers that have disabled support for `
`. [#880](https://github.com/FreshRSS/FreshRSS/issues/880) -* UI - * Force UTF-8 for responses. [#870](https://github.com/FreshRSS/FreshRSS/issues/870) - * Increased pagination limit to 500 articles. [#872](https://github.com/FreshRSS/FreshRSS/issues/872) - * Improved UI for installation. [#855](https://github.com/FreshRSS/FreshRSS/issues/855) -* Misc. - * PHP 7 officially supported (~70% speed improvements on early tests). [#889](https://github.com/FreshRSS/FreshRSS/issues/889) - * Restore support for PHP 5.2.1+. [#214a5cc](https://github.com/Alkarex/FreshRSS/commit/214a5cc9a4c2b821961bc21f22b4b08e34b5be68) [#894](https://github.com/FreshRSS/FreshRSS/issues/894) - * Support for data-src for images of articles retrieved via the full-content module. [#877](https://github.com/FreshRSS/FreshRSS/issues/877) - * Add a couple of default feeds for fresh installations. [#886](https://github.com/FreshRSS/FreshRSS/issues/886) - * Changed some log visibilities. [#885](https://github.com/FreshRSS/FreshRSS/issues/885) - * Fix broken links for extension script / style files. [#862](https://github.com/FreshRSS/FreshRSS/issues/862) - * Load default configuration during installation to avoid hard-coded values. [#890](https://github.com/FreshRSS/FreshRSS/issues/890) - * Fix non-consistent behaviour in Minz_Request::getBaseUrl() and introduce Minz_Request::guessBaseUrl(). [#906](https://github.com/FreshRSS/FreshRSS/issues/906) - * Generate `base_url` during the installation and add a `pubsubhubbub_enabled` configuration key. [#865](https://github.com/FreshRSS/FreshRSS/issues/865) - * Load configuration by recursion to overwrite array values. [#923](https://github.com/FreshRSS/FreshRSS/issues/923) - * Cast `$limits` configuration values in integer. [#925](https://github.com/FreshRSS/FreshRSS/issues/925) - * Don't hide errors in configuration. [#920](https://github.com/FreshRSS/FreshRSS/issues/920) - - -## 2015-05-31 FreshRSS 1.1.1 (beta) - -* Features - * New option to detect and mark updated articles as unread. - * Support for internationalized domain name (IDN). - * Improved logic for automatic deletion of old articles. -* API - * Work-around for News+ bug when there is no unread article on the server. -* UI - * New confirmation message when leaving a configuration page without saving the changes. -* Bug fixing - * Corrected bug introduced in previous beta about handling of HTTP 301 (feeds that have changed address) - * Corrected bug in FreshRSS RSS feeds. -* Security - * Sanitize HTTP request header `Host`. -* Misc. - * Attempt to better handle encoded article titles. - - -## 2015-01-31 FreshRSS 1.0.0 / 1.1.0 (beta) - -* UI - * Slider math with Dark theme - * Add a message if request failed for mark as read / favourite -* I18n - * Fix some sentences - * Add German as a supported language - * Add some indications on password format -* Bug fixing - * Some shortcuts was never saved - * Global view didn't work if set by default - * Minz_Error was badly raised - * Feed update failed if nothing had changed (MySQL only) - * CRON task failed with multiple users - * Tricky bug caused by cookie path - * Email sharing was badly supported (no urlencode()) -* Misc. - * Add a CREDIT file with contributor names - * Update lib_opml - * Default favicon is now served by HTTP code 200 - * Change calls to syslog by Minz_Log::notice - * HTTP credentials are no longer logged - - -## 2015-01-15 FreshRSS 0.9.4 (beta) - -* Feature - * Extension system (!!): some extensions are available at https://github.com/FreshRSS/Extensions -* Refactoring - * Front controller (FreshRSS class) - * Configuration system - * Sharing system - * New data files organization -* Updates - * Remove restriction of 1h for updates - * Show the current version of FreshRSS and the next one -* UI - * Remove the "sticky position" of the feed aside (moved into an extension) - * "Show password" shows the password only while the user is pressing the mouse. - - -## 2014-12-12 FreshRSS 0.9.3 (beta) - -* SimplePie - * Support for content-type application/x-rss+xml - * New force_feed option (for feeds sent with the wrong content-type / MIME) by adding #force_feed at the end of the feed URL - * Improved error messages -* Statistics - * Add information on feed repartition pages - * Add percent repartition for the bigger feeds -* UI - * New theme selector - * Update Screwdriver theme - * Add BlueLagoon theme by Mister aiR -* Misc. - * Add option to remove articles after reading them - * Add comments - * Refactor i18n system to avoid loading unnecessary strings - * Fix security issue in Minz_Error::error() method - * Fix redirection after refreshing a given feed - - -## 2014-10-31 FreshRSS 0.9.2 (beta) - -* UI - * New subscription page (introduce .box items) - * Change feed category by drag and drop - * New feed aside on the main page - * New configuration / administration organization -* Configuration - * New options in config.php for cache duration, timeout, max inactivity, max number of feeds and categories per user. -* Refactoring - * Refactor authentication system (introduce FreshRSS_Auth model) - * Refactor indexController (introduce FreshRSS_Context model) - * Use ```_t()```, ```_i()```, ```_url()```, ```Minz_Request::good()``` and ```Minz_Request::bad()``` as much as possible - * Refactor javascript_vars.phtml - * Better coding style -* I18n - * Introduce a new system for i18n keys (not finished yet) -* Misc. - * Fix global view (did not work anymore) - * Add do_post_update for update system - * Introduce ```checkInstallAction``` to test if FreshRSS installation is ok - - -## 2014-10-09 FreshRSS 0.8.1 / 0.9.1 (beta) - -* UI - * Add a space after tag icon -* Statistics - * Add an average per day on the 30-day period graph - * Add percent of total on top 10 feed -* Bug fixes - * Fix "mark as read" in global view - * Fix "read all" shortcut - * Fix categories not appearing when adding a new feed (GET action) - * Fix enclosure problem - * Fix getExtension() on PHP < 5.3.7 - - -## 2014-09-26 FreshRSS 0.8.0 / 0.9.0 (beta) - -* UI - * New interface for statistics - * Fix filter buttons - * Number of articles divided by 2 in reading view - * Redesign of bigMarkAsRead -* Features - * New automatic update system - * New reset auth system -* Security - * "Mark as read" requires POST requests for several articles - * Test HTTP REFERER in install.php -* Configuration - * New "Show all articles" / "Show only unread" / "Adjust viewing" option - * New notification timeout option -* Misc. - * Improve coding style + comments - * Fix SQLite bug "ON DELETE CASCADE" - * Improve performance when importing articles - - -## 2014-08-24 FreshRSS 0.7.4 - -* UI - * Hide categories/feeds with unread articles when showing only unread articles - * Dynamic favicon showing the number of unread articles - * New theme: Screwdriver by Mister aiR -* Statistics - * New page with article repartition - * Improvements -* Security - * Basic protection against XSRF (Cross-Site Request Forgery) based on HTTP Referer (POST requests only) -* API - * Compatible with lighttpd -* Misc. - * Changed lazyload implementation - * Support of HTML5 notifications for new upcoming articles - * Add option to stay logged in -* Bug fixes in export function, add/remove users, keyboard shortcuts, etc. - - -## 2014-07-21 FreshRSS 0.7.3 - -* New options - * Add system of user queries which are shortcuts to filter the view - * New TTL option to limit the frequency at which feeds are refreshed (by cron or manual refresh button). - It is still possible to manually refresh an individual feed at a higher frequency. -* SQL - * Add support for SQLite (beta) in addition to MySQL -* SimplePie - * Complies with HTTP "301 Moved Permanently" responses by automatically updating the URL of feeds that have changed address. -* Themes - * Flat and Dark designs are based on same template file as Origine -* Statistics - * Refactor code - * Add an idle feed page -* Misc - * Several bug fixes - * Add confirmation option when marking all articles as read - * Fix some typo - - -## 2014-06-13 FreshRSS 0.7.2 - -* API compatible with Google Reader API level 2 - * FreshRSS can now be used from e.g.: - * (Android) News+ https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader - * (Android) EasyRSS https://github.com/Alkarex/EasyRSS -* Basic support for audio and video podcasts -* Searching - * New search filters date: and pubdate: accepting ISO 8601 date intervals such as `date:2013-2014` or `pubdate:P1W` - * Possibility to combine search filters, e.g. `date:2014-05 intitle:FreshRSS intitle:Open great reader #Internet` -* Change nav menu with more buttons instead of dropdown menus and add some filters -* New system of import / export - * Support OPML, Json (like Google Reader) and Zip archives - * Can export and import articles (specific option for favorites) -* Refactor "Origine" theme - * Some improvements - * Based on a template file (other themes will use it too) - - -## 2014-02-19 FreshRSS 0.7.1 - -* Mise à jour des flux plus rapide grâce à une meilleure utilisation du cache - * Utilisation d’une signature MD5 du contenu intéressant pour les flux n’implémentant pas les requêtes conditionnelles -* Modification des raccourcis - * "s" partage directement si un seul moyen de partage - * Moyens de partage accessibles par "1", "2", "3", etc. - * Premier article : Home ; Dernier article : End - * Ajout du déplacement au sein des catégories / flux (via modificateurs shift et alt) -* UI - * Séparation des descriptions des raccourcis par groupes - * Revue rapide de la page de connexion - * Amélioration de l'affichage des notifications sur mobile -* Revue du système de rafraîchissement des flux - * Meilleure gestion de la file de flux à rafraîchir en JSON - * Rafraîchissement uniquement pour les flux non rafraîchis récemment - * Possibilité donnée aux anonymes de rafraîchir les flux -* SimplePie - * Mise à jour de la lib - * Corrige fuite de mémoire - * Meilleure tolérance aux flux invalides -* Corrections divers - * Ne déplie plus l'article lors du clic sur l'icône lien externe - * Ne boucle plus à la fin de la navigation dans les articles - * Suppression du champ category.color inutile - * Corrige bug redirection infinie (Persona) - * Amélioration vérification de la requête POST - * Ajout d'un verrou lorsqu'une action mark_read ou mark_favorite est en cours - - -## 2014-01-29 FreshRSS 0.7 - -* Nouveau mode multi-utilisateur - * L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs - * Nécessite un contrôle d’accès, soit : - * par le nouveau mode de connexion par formulaire (nom d’utilisateur + mot de passe) - * relativement sûr même sans HTTPS (le mot de passe n’est pas transmis en clair) - * requiert JavaScript et PHP 5.3+ - * par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd) - * le nom d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS - * par Mozilla Persona, en renseignant l’adresse courriel des utilisateurs -* Installateur supportant les mises à jour : - * Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/” - (voir réorganisation ci-dessous) - * Pour les versions suivantes, juste garder le répertoire “./data/” -* Rafraîchissement automatique du nombre d’articles non lus toutes les deux minutes (utilise le cache HTTP à bon escient) - * Permet aussi de conserver la session valide, surtout dans le cas de Persona -* Nouvelle page de statistiques (nombres d’articles par jour / catégorie) -* Importation OPML instantanée et plus tolérante -* Nouvelle gestion des favicons avec téléchargement en parallèle -* Nouvelles options - * Réorganisation des options - * Gestion des utilisateurs - * Améliorations partage vers Shaarli, Poche, Diaspora*, Facebook, Twitter, Google+, courriel - * Raccourci ‘s’ par défaut - * Permet la suppression de tous les articles d’un flux - * Option pour marquer les articles comme lus dès la réception - * Permet de configurer plus finement le nombre d’articles minimum à conserver par flux - * Permet de modifier la description et l’adresse d’un flux RSS ainsi que le site Web associé - * Nouveau raccourci pour ouvrir/fermer un article (‘c’ par défaut) - * Boutons pour effacer les logs et pour purger les vieux articles - * Nouveaux filtres d’affichage : seulement les articles favoris, et seulement les articles lus -* SQL : - * Nouveau moteur de recherche, aussi accessible depuis la vue mobile - * Mots clefs de recherche “intitle:”, “inurl:”, “author:” - * Les articles sont triés selon la date de leur ajout dans FreshRSS plutôt que la date déclarée (souvent erronée) - * Permet de marquer tout comme lu sans affecter les nouveaux articles arrivés en cours de lecture - * Permet une pagination efficace - * Refactorisation - * Les tables sont préfixées avec le nom d’utilisateur afin de permettre le mode multi-utilisateurs - * Amélioration des performances - * Tolère un beaucoup plus grand nombre d’articles - * Compression des données côté MySQL plutôt que côté PHP - * Incompatible avec la version 0.6 (nécessite une mise à jour grâce à l’installateur) - * Affichage de la taille de la base de données dans FreshRSS - * Correction problème de marquage de tous les favoris comme lus -* HTML5 : - * Support des balises HTML5 audio, video, et éléments associés - * Utilisation de preload="none", et réécriture correcte des adresses, aussi en HTTPS - * Protection HTML5 des iframe (sandbox="allow-scripts allow-same-origin") - * Filtrage des object et embed - * Chargement différé HTML5 (postpone="") pour iframe et video - * Chargement différé JavaScript pour iframe -* CSS : - * Nouveau thème sombre - * Chargement plus robuste des thèmes - * Meilleur support des longs titres d’articles sur des écrans étroits - * Meilleure accessibilité - * FreshRSS fonctionne aussi en mode dégradé sans images (alternatives Unicode) et/ou sans CSS - * Diverses améliorations -* PHP : - * Encore plus tolérant pour les flux comportant des erreurs - * Mise à jour automatique de l’URL du flux (en base de données) lorsque SimplePie découvre qu’elle a changé - * Meilleure gestion des caractères spéciaux dans différents cas - * Compatibilité PHP 5.5+ avec OPcache - * Amélioration des performances - * Chargement automatique des classes - * Alternative dans le cas d’absence de librairie JSON - * Pour le développement, le cache HTTP peut être désactivé en créant un fichier “./data/no-cache.txt” -* Réorganisation des fichiers et répertoires, en particulier : - * Tous les fichiers utilisateur sont dans “./data/” (y compris “cache”, “favicons”, et “log”) - * Déplacement de “./app/configuration/application.ini” vers “./data/config.php” - * Meilleure sécurité et compatibilité - * Déplacement de “./public/data/Configuration.array.php” vers “./data/*_user.php” - * Déplacement de “./public/” vers “./p/” - * Déplacement de “./public/index.php” vers “./p/i/index.php” (voir cookie ci-dessous) - * Déplacement de “./actualize_script.php” vers “./app/actualize_script.php” (pour une meilleure sécurité) - * Pensez à mettre à jour votre Cron ! -* Divers : - * Nouvelle politique de cookie de session (témoin de connexion) - * Utilise un nom poli “FreshRSS” (évite des problèmes avec certains filtres) - * Se limite au répertoire “./FreshRSS/p/i/” pour de meilleures performances HTTP - * Les images, CSS, scripts sont servis sans cookie - * Utilise “HttpOnly” pour plus de sécurité - * Nouvel “agent utilisateur” exposé lors du téléchargement des flux, par exemple : - * “FreshRSS/0.7 (Linux; http://freshrss.org) SimplePie/1.3.1” - * Script d’actualisation avec plus de messages - * Sur la sortie standard, ainsi que dans le log système (syslog) - * Affichage du numéro de version dans "À propos" - - -## 2013-11-21 FreshRSS 0.6.1 - -* Corrige bug chargement du JavaScript -* Affiche un message d’erreur plus explicite si fichier de configuration inaccessible - - -## 2013-11-17 FreshRSS 0.6 - -* Nettoyage du code JavaScript + optimisations -* Utilisation d’adresses relatives -* Amélioration des performances coté client -* Mise à jour automatique du nombre d’articles non lus -* Corrections traductions -* Mise en cache de FreshRSS -* Amélioration des retours utilisateur lorsque la configuration n’est pas bonne -* Actualisation des flux après une importation OPML -* Meilleure prise en charge des flux RSS invalides -* Amélioration de la vue globale -* Possibilité de personnaliser les icônes de lecture -* Suppression de champs lors de l’installation (base_url et sel) -* Correction bugs divers - - -## 2013-10-15 FreshRSS 0.5.1 - -* Correction bug des catégories disparues -* Correction traduction i18n/fr et i18n/en -* Suppression de certains appels à la feuille de style fallback.css - - -## 2013-10-12 FreshRSS 0.5.0 - -* Possibilité d’interdire la lecture anonyme -* Option pour garder l’historique d’un flux -* Lors d’un clic sur “Marquer tous les articles comme lus”, FreshRSS peut désormais sauter à la prochaine catégorie / prochain flux avec des articles non lus. -* Ajout d’un token pour accéder aux flux RSS générés par FreshRSS sans nécessiter de connexion -* Possibilité de partager vers Facebook, Twitter et Google+ -* Possibilité de changer de thème -* Le menu de navigation (article précédent / suivant / haut de page) a été ajouté à la vue non mobile -* La police OpenSans est désormais appliquée -* Amélioration de la page de configuration -* Une meilleure sortie pour l’imprimante -* Quelques retouches du design par défaut -* Les vidéos ne dépassent plus du cadre de l’écran -* Nouveau logo -* Possibilité d’ajouter un préfixe aux tables lors de l’installation -* Ajout d’un champ en base de données keep_history à la table feed -* Si possible, création automatique de la base de données si elle n’existe pas lors de l’installation -* L’utilisation d’UTF-8 est forcée -* Le marquage automatique au défilement de la page a été amélioré -* La vue globale a été énormément améliorée et est beaucoup plus utile -* Amélioration des requêtes SQL -* Amélioration du JavaScript -* Correction bugs divers - - -## 2013-07-02 FreshRSS 0.4.0 - -* Correction bug et ajout notification lors de la phase d’installation -* Affichage d’erreur si fichier OPML invalide -* Les tags sont maintenant cliquables pour filtrer dessus -* Amélioration vue mobile (boutons plus gros et ajout d’une barre de navigation) -* Possibilité d’ajouter directement un flux dans une catégorie dès son ajout -* Affichage des flux en erreur (injoignable par exemple) en rouge pour les différencier -* Possibilité de changer les noms des flux -* Ajout d’une option (désactivable donc) pour charger les images en lazyload permettant de ne pas charger toutes les images d’un coup -* Le framework Minz est maintenant directement inclus dans l’archive (plus besoin de passer par ./build.sh) -* Amélioration des performances pour la récupération des flux tronqués -* Possibilité d’importer des flux sans catégorie lors de l’import OPML -* Suppression de “l’API” (qui était de toute façon très basique) et de la fonctionnalité de “notes” -* Amélioration de la recherche (garde en mémoire si l’on a sélectionné une catégorie) par exemple -* Modification apparence des balises hr et pre -* Meilleure vérification des champs de formulaire -* Remise en place du mode “endless” (permettant de simplement charger les articles qui suivent plutôt que de charger une nouvelle page) -* Ajout d’une page de visualisation des logs -* Ajout d’une option pour optimiser la BDD (diminue sa taille) -* Ajout des vues lecture et globale (assez basique) -* Les vidéos YouTube ne débordent plus du cadre sur les petits écrans -* Ajout d’une option pour marquer les articles comme lus lors du défilement (et suppression de celle au chargement de la page) - - -## 2013-05-05 FreshRSS 0.3.0 - -* Fallback pour les icônes SVG (utilisation de PNG à la place) -* Fallback pour les propriétés CSS3 (utilisation de préfixes) -* Affichage des tags associés aux articles -* Internationalisation de l’application (gestion des langues anglaise et française) -* Gestion des flux protégés par authentification HTTP -* Mise en cache des favicons -* Création d’un logo *temporaire* -* Affichage des vidéos dans les articles -* Gestion de la recherche et filtre par tags pleinement fonctionnels -* Création d’un vrai script CRON permettant de mettre tous les flux à jour -* Correction bugs divers - - -## 2013-04-17 FreshRSS 0.2.0 - -* Création d’un installateur -* Actualisation des flux en Ajax -* Partage par mail et Shaarli ajouté -* Export par flux RSS -* Possibilité de vider une catégorie -* Possibilité de sélectionner les catégories en vue mobile -* Les flux peuvent être sortis du flux principal (système de priorité) -* Amélioration ajout / import / export des flux -* Amélioration actualisation (meilleure gestion des erreurs) -* Améliorations CSS -* Changements dans la base de données -* Màj de la librairie SimplePie -* Flux sans auteurs gérés normalement -* Correction bugs divers - - -## 2013-04-08 FreshRSS 0.1.0 - -* “Première” version diff --git a/sources/CONTRIBUTING.md b/sources/CONTRIBUTING.md deleted file mode 100755 index 1338137..0000000 --- a/sources/CONTRIBUTING.md +++ /dev/null @@ -1,57 +0,0 @@ -# How to contribute to FreshRSS? - -## Join us on the mailing lists - -Do you want to ask us some questions? Do you want to discuss with us? Don't hesitate to subscribe to our mailing lists! - -- The first mailing is destined to generic information, it should be adapted to users. [Join mailing@freshrss.org](https://freshrss.org/mailman/listinfo/mailing). -- The second mailing is mainly for developers. [Join dev@freshrss.org](https://freshrss.org/mailman/listinfo/dev) - -## Report a bug - -You found a bug? Don't panic, here are some steps to report it easily: - -1. Search for it on [the bug tracker](https://github.com/FreshRSS/FreshRSS/issues) (don't forget to use the search bar). -2. If you find a similar bug, don't hesitate to post a comment to add more importance to the related ticket. -3. If you didn't find it, [open a new ticket](https://github.com/FreshRSS/FreshRSS/issues/new). - -If you have to create a new ticket, try to apply the following advices: - -- Give an explicit title to the ticket so it will be easier to find it later. -- Be as exhaustive as possible in the description: what did you do? What is the bug? What are the steps to reproduce the bug? -- We also need some information: - + Your FreshRSS version (on about page or `constants.php` file) - + Your server configuration: type of hosting, PHP version - + Your storage system (MySQL / MariaDB or SQLite) - + If possible, the related logs (PHP logs and FreshRSS logs under `data/users/your_user/log.txt`) - -## Fix a bug - -Did you want to fix a bug? To keep a great coordination between collaborators, you will have to follow these indications: - -1. Be sure the bug is associated to a ticket and say you work on it. -2. [Fork this project repository](https://help.github.com/articles/fork-a-repo/). -3. [Create a new branch](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/). The name of the branch must be explicit and being prefixed by the related ticket id. For instance, `783-contributing-file` to fix [ticket #783](https://github.com/FreshRSS/FreshRSS/issues/783). -4. Make your changes to your fork and [send a pull request](https://help.github.com/articles/using-pull-requests/) on the **dev branch**. - -If you have to write code, please follow [our coding style recommendations](http://doc2.freshrss.org/en/Developer_documentation/First_steps/Coding_style). - -**Tip:** if you are searching for bugs easy to fix, have a look at the « [New comers](https://github.com/FreshRSS/FreshRSS/labels/New%20comers) » ticket label. - -## Submit an idea - -You have great ideas, yes! Don't be shy and open [a new ticket](https://github.com/FreshRSS/FreshRSS/issues/new) on our bug tracker to ask if we can implement it. The greatest ideas often come from the shyest suggestions! - -If your idea is nice, we'll have a look at it. - -## Contribute to internationalization (i18n) - -If you want to improve internationalization, please open a new ticket first and follow indications from « Fix a bug » section. - -Translations are present in the subdirectories of `./app/i18n/`. - -We are working on a better way to handle internationalization but don't hesitate to suggest any idea! - -## Contribute to documentation - -The documentation needs a lot of improvements in order to be more useful to new contributors and we are working on it. If you want to give some help, meet us on [the dedicated repository](https://github.com/FreshRSS/documentation)! diff --git a/sources/CREDITS.md b/sources/CREDITS.md deleted file mode 100755 index b21e8be..0000000 --- a/sources/CREDITS.md +++ /dev/null @@ -1,25 +0,0 @@ -This is a credit file of people who have [contributed to FreshRSS](https://github.com/FreshRSS/FreshRSS/graphs/contributors) with, at least, -one commit on the FreshRSS repository (at https://github.com/FreshRSS/FreshRSS). -Please note a commit on THIS specific file is not considered as a contribution -(too easy!). Its purpose is to show that even the smallest contribution is important. -People are sorted by name so please keep this order. - ---- - -* [Alexandre Alapetite](https://github.com/Alkarex): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Alkarex), [Web](http://alexandre.alapetite.fr/) -* [Alexis Degrugillier](https://github.com/aledeg): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=aledeg) -* [Alwaysin](https://github.com/Alwaysin): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Alwaysin) -* [Amaury Carrade](https://github.com/AmauryCarrade): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=AmauryCarrade) -* [ealdraed](https://github.com/ealdraed): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ealdraed) -* [Luc Didry](https://github.com/ldidry): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ldidry) -* [Marcus Rohrmoser](https://github.com/mro): -[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=mro) -* [Marien Fressinaud](https://github.com/marienfressinaud): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marienfressinaud), [Web](http://marienfressinaud.fr/) -* [Melvyn Laïly](https://github.com/yaurthek): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=yaurthek) -* [Nicolas Elie](https://github.com/nicolaselie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicolaselie) -* [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop) -* [Tets42](https://github.com/Tets42): -[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Tets42) -* [thomasE1993](https://github.com/thomasE1993): -[contributions](https://github.com/FreshRSS/FreshRSS/commits?author=thomasE1993) -* [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue) diff --git a/sources/LICENSE b/sources/LICENSE deleted file mode 100755 index a871fcf..0000000 --- a/sources/LICENSE +++ /dev/null @@ -1,662 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - 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 . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. - diff --git a/sources/README.fr.md b/sources/README.fr.md deleted file mode 100755 index c28cd30..0000000 --- a/sources/README.fr.md +++ /dev/null @@ -1,138 +0,0 @@ -* [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. -Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles. - -* Site officiel : http://freshrss.org -* Démo : http://demo.freshrss.org/ -* Licence : [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html) - -![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) - -# Téléchargement -Voir la [liste des versions](../../releases). - -## Note sur les branches -**Ce logiciel est en développement permanent !** Veuillez vous assurer d'utiliser la branche qui vous correspond : - -* Utilisez [la branche master](https://github.com/FreshRSS/FreshRSS/tree/master/) si vous visez la stabilité. -* [La branche beta](https://github.com/FreshRSS/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois. -* Pour les développeurs et ceux qui veulent aider à tester les toutes dernières fonctionnalités, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras ! - -# Avertissements -Cette application a été développée pour s’adapter principalement à des besoins personnels, et aucune garantie n'est fournie. -Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues). -Nous sommes une communauté amicale. - -# Prérequis -* Serveur modeste, par exemple sous Linux ou Windows - * Fonctionne même sur un Raspberry Pi 1 avec des temps de réponse < 1s (testé sur 150 flux, 22k articles) -* Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres) -* PHP 5.3+ (PHP 5.3.7+ recommandé, et PHP 5.5+ pour les performances, et PHP 7+ pour d’encore meilleures performances) - * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés) - * Recommandés : [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib) - * Inclus par défaut : [DOM](http://php.net/dom), [XML](http://php.net/xml)… -* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ -* Un navigateur Web récent tel Firefox, Chrome, Opera, Safari. [Internet Explorer ne fonctionne plus, mais ce sera corrigé](https://github.com/FreshRSS/FreshRSS/issues/772). - * Fonctionne aussi sur mobile -* L’entête HTTP `Referer` ne doit pas être désactivé pour pouvoir utiliser le formulaire de connexion - -![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) - -# Installation -1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](../releases) -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. -6. Des paramètres de configuration avancée peuvent être accédés depuis [config.php](./data/config.default.php). - -## Installation automatisée -[![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/gh-pages/img/deploy.png)](https://dfabric.github.io/DPlatform-ShellCore) - -## Exemple d’installation complète sur Linux Debian/Ubuntu -```sh -# Si vous utilisez le serveur Web Apache (sinon il faut un autre serveur Web) -sudo apt-get install apache2 -sudo a2enmod headers expires rewrite ssl -# (optionnel) Si vous voulez un serveur de base de données MySQL -sudo apt-get install mysql-server mysql-client php5-mysql -# Composants principaux (git est optionnel si vous déployez manuellement les fichiers d’installation) -sudo apt-get install git php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite -# Redémarrage du serveur Web -sudo service apache2 restart - -# Pour FreshRSS lui-même -cd /usr/share/ -sudo git clone https://github.com/FreshRSS/FreshRSS.git -# Mettre les droits d’accès pour le serveur Web -cd FreshRSS -sudo chown -R :www-data . -sudo chmod -R g+w ./data/ -# Publier FreshRSS dans votre répertoire HTML public -sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS -# Naviguez vers http://example.net/FreshRSS pour terminer l’installation. -# (Si vous le faite depuis localhost, vous pourrez avoir à ajuster le réglage de votre adresse publique) - -# Mettre à jour FreshRSS vers une nouvelle version -cd /usr/share/FreshRSS -sudo git reset --hard -sudo git pull -sudo chown -R :www-data . -sudo chmod -R g+w ./data/ -``` - -# Contrôle 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 /votre-chemin/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 -``` - -# Conseils -* Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`. - * En particulier, les données personnelles se trouvent dans le répertoire `./data/`. -* Le fichier `./constants.php` définit les chemins 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êmes, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL : - -```bash -mysqldump -u utilisateur -p --databases freshrss > freshrss.sql -``` - - -# Bibliothèques incluses -* [SimplePie](http://simplepie.org/) -* [MINZ](https://github.com/marienfressinaud/MINZ) -* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/) -* [jQuery](http://jquery.com/) -* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) -* [flotr2](http://www.humblesoftware.com/flotr2) - -## Uniquement pour certaines options -* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) -* [phpQuery](http://code.google.com/p/phpquery/) - -## Si les fonctions natives ne sont pas disponibles -* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198) -* [password_compat](https://github.com/ircmaxell/password_compat) diff --git a/sources/README.md b/sources/README.md deleted file mode 100755 index db06b43..0000000 --- a/sources/README.md +++ /dev/null @@ -1,138 +0,0 @@ -* [Version française](README.fr.md) - -# FreshRSS -FreshRSS is a self-hosted RSS feed aggregator such as [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/). - -It is at the same time lightweight, easy to work with, powerful and customizable. - -It is a multi-user application with an anonymous reading mode. -It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites. - -* Official website: http://freshrss.org -* Demo: http://demo.freshrss.org/ -* License: [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html) - -![FreshRSS logo](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png) - -# Releases -See the [list of releases](../../releases). - -## Note on branches -**This application is under continuous development!** Please use the branch that suits your needs: - -* Use [the master branch](https://github.com/FreshRSS/FreshRSS/tree/master/) if you need a stable version. -* [The beta branch](https://github.com/FreshRSS/FreshRSS/tree/beta) is the default branch: new features are added on a monthly basis. -* For developers and tech savvy persons willing to help testing the latest features, [the dev branch](https://github.com/FreshRSS/FreshRSS/tree/dev) is waiting for you! - -# Disclaimer -This application was developed to fulfil personal needs primarily, and comes with absolutely no warranty. -Feature requests, bug reports, and other contributions are welcome. The best way is to [open issues on GitHub](https://github.com/FreshRSS/FreshRSS/issues). -We are a friendly community. - -# Requirements -* Light server running Linux or Windows - * It even works on Raspberry Pi 1 with response time under a second (tested with 150 feeds, 22k articles) -* A web server: Apache2 (recommended), nginx, lighttpd (not tested on others) -* PHP 5.3+ (PHP 5.3.7+ recommended, and PHP 5.5+ for performance, and PHP 7 for even higher performance) - * Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names) - * Recommended extensions: [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib) - * Enabled by default: [DOM](http://php.net/dom), [XML](http://php.net/xml)… -* MySQL 5.0.3+ (recommended) or SQLite 3.7.4+ -* A recent browser like Firefox, Chrome, Opera, Safari. [Internet Explorer currently not supported, but support will come back](https://github.com/FreshRSS/FreshRSS/issues/772). - * Works on mobile -* The browser HTTP `Referer` header must not be disabled when using the form login method - -![FreshRSS screenshot](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png) - -# Installation -1. Get FreshRSS with git or [by downloading the archive](https://github.com/FreshRSS/FreshRSS/archive/master.zip) -2. Dump the application on your server (expose only the `./p/` folder) -3. Add write access on `./data/` folder to the webserver user -4. Access FreshRSS with your browser and follow the installation process -5. Everything should be working :) If you encounter any problem, feel free to contact me. -6. Advanced configuration settings can be seen in [config.php](./data/config.default.php). - -## Automated install -[![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/gh-pages/img/deploy.png)](https://dfabric.github.io/DPlatform-ShellCore) - -## Example of full installation on Linux Debian/Ubuntu -```sh -# If you use an Apache Web server (otherwise you need another Web server) -sudo apt-get install apache2 -sudo a2enmod headers expires rewrite ssl -# (Optional) If you want a MySQL database server -sudo apt-get install mysql-server mysql-client php5-mysql -# Main components (git is optional if you manually download the installation files) -sudo apt-get install git php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite -# Restart Web server -sudo service apache2 restart - -# For FreshRSS itself -cd /usr/share/ -sudo git clone https://github.com/FreshRSS/FreshRSS.git -# Set the rights so that your Web browser can access the files -cd FreshRSS -sudo chown -R :www-data . -sudo chmod -R g+w ./data/ -# Publish FreshRSS in your public HTML directory -sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS -# Navigate to http://example.net/FreshRSS to complete the installation. -# (If you do it from localhost, you may have to adjust the setting of your public address later) - -# Update to a newer version of FreshRSS -cd /usr/share/FreshRSS -sudo git reset --hard -sudo git pull -sudo chown -R :www-data . -sudo chmod -R g+w ./data/ -``` - -# Access control -It is needed for the multi-user mode to limit access to FreshRSS. You can: -* use form authentication (need JavaScript and PHP 5.3.7+, works with some PHP 5.3.3+) -* use [Mozilla Persona](https://login.persona.org/about) authentication included in FreshRSS -* use HTTP authentication supported by your web server - * See [Apache documentation](http://httpd.apache.org/docs/trunk/howto/auth.html) - * In that case, create a `./p/i/.htaccess` file with a matching `.htpasswd` file. - -# Automatic feed update -* You can add a Cron job to launch the update script. -Check the Cron documentation related to your distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron)…). -It’s a good idea to use the Web server user. -For example, if you want to run the script every hour: - -``` -7 * * * * php /your-path/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1 -``` - -# Advices -* For a better security, expose only the `./p/` folder on the web. - * Be aware that the `./data/` folder contains all personal data, so it is a bad idea to expose it. -* The `./constants.php` file defines access to application folder. If you want to customize your installation, every thing happens here. -* If you encounter any problem, logs are accessible from the interface or manually in `./data/log/*.log` files. - -# Backup -* You need to keep `./data/config.php`, `./data/*_user.php` and `./data/persona/` files -* You can export your feed list in OPML format from FreshRSS -* To save articles, you can use [phpMyAdmin](http://www.phpmyadmin.net) or MySQL tools: - -```bash -mysqldump -u user -p --databases freshrss > freshrss.sql -``` - - -# Included libraries -* [SimplePie](http://simplepie.org/) -* [MINZ](https://github.com/marienfressinaud/MINZ) -* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/) -* [jQuery](http://jquery.com/) -* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) -* [flotr2](http://www.humblesoftware.com/flotr2) - -## Only for some options -* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) -* [phpQuery](http://code.google.com/p/phpquery/) - -## If native functions are not available -* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198) -* [password_compat](https://github.com/ircmaxell/password_compat) diff --git a/sources/app/.htaccess b/sources/app/.htaccess deleted file mode 100755 index 9e76839..0000000 --- a/sources/app/.htaccess +++ /dev/null @@ -1,3 +0,0 @@ -Order Allow,Deny -Deny from all -Satisfy all diff --git a/sources/app/Controllers/authController.php b/sources/app/Controllers/authController.php deleted file mode 100755 index f58b008..0000000 --- a/sources/app/Controllers/authController.php +++ /dev/null @@ -1,358 +0,0 @@ -token; - $token = Minz_Request::param('token', $current_token); - FreshRSS_Context::$user_conf->token = $token; - $ok &= FreshRSS_Context::$user_conf->save(); - - $anon = Minz_Request::param('anon_access', false); - $anon = ((bool)$anon) && ($anon !== 'no'); - $anon_refresh = Minz_Request::param('anon_refresh', false); - $anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no'); - $auth_type = Minz_Request::param('auth_type', 'none'); - $unsafe_autologin = Minz_Request::param('unsafe_autologin', false); - $api_enabled = Minz_Request::param('api_enabled', false); - if ($anon != FreshRSS_Context::$system_conf->allow_anonymous || - $auth_type != FreshRSS_Context::$system_conf->auth_type || - $anon_refresh != FreshRSS_Context::$system_conf->allow_anonymous_refresh || - $unsafe_autologin != FreshRSS_Context::$system_conf->unsafe_autologin_enabled || - $api_enabled != FreshRSS_Context::$system_conf->api_enabled) { - - // TODO: test values from form - FreshRSS_Context::$system_conf->auth_type = $auth_type; - FreshRSS_Context::$system_conf->allow_anonymous = $anon; - FreshRSS_Context::$system_conf->allow_anonymous_refresh = $anon_refresh; - FreshRSS_Context::$system_conf->unsafe_autologin_enabled = $unsafe_autologin; - FreshRSS_Context::$system_conf->api_enabled = $api_enabled; - - $ok &= FreshRSS_Context::$system_conf->save(); - } - - invalidateHttpCache(); - - if ($ok) { - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'auth', 'a' => 'index')); - } else { - Minz_Request::bad(_t('feedback.conf.error'), - array('c' => 'auth', 'a' => 'index')); - } - } - } - - /** - * This action handles the login page. - * - * It forwards to the correct login page (form or Persona) or main page if - * the user is already connected. - */ - public function loginAction() { - if (FreshRSS_Auth::hasAccess()) { - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } - - $auth_type = FreshRSS_Context::$system_conf->auth_type; - switch ($auth_type) { - case 'form': - Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin')); - break; - case 'persona': - Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin')); - break; - case 'http_auth': - case 'none': - // It should not happened! - Minz_Error::error(404); - default: - // TODO load plugin instead - Minz_Error::error(404); - } - } - - /** - * This action handles form login page. - * - * If this action is reached through a POST request, username and password - * are compared to login the current user. - * - * Parameters are: - * - nonce (default: false) - * - username (default: '') - * - challenge (default: '') - * - keep_logged_in (default: false) - * - * @todo move unsafe autologin in an extension. - */ - public function formLoginAction() { - invalidateHttpCache(); - - $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); - Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); - - if (Minz_Request::isPost()) { - $nonce = Minz_Session::param('nonce'); - $username = Minz_Request::param('username', ''); - $challenge = Minz_Request::param('challenge', ''); - - $conf = get_user_configuration($username); - if (is_null($conf)) { - Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); - return; - } - - $ok = FreshRSS_FormAuth::checkCredentials( - $username, $conf->passwordHash, $nonce, $challenge - ); - if ($ok) { - // Set session parameter to give access to the user. - Minz_Session::_param('currentUser', $username); - Minz_Session::_param('passwordHash', $conf->passwordHash); - FreshRSS_Auth::giveAccess(); - - // Set cookie parameter if nedded. - if (Minz_Request::param('keep_logged_in')) { - FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash); - } else { - FreshRSS_FormAuth::deleteCookie(); - } - - // All is good, go back to the index. - Minz_Request::good(_t('feedback.auth.login.success'), - array('c' => 'index', 'a' => 'index')); - } else { - Minz_Log::warning('Password mismatch for' . - ' user=' . $username . - ', nonce=' . $nonce . - ', c=' . $challenge); - Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); - } - } elseif (FreshRSS_Context::$system_conf->unsafe_autologin_enabled) { - $username = Minz_Request::param('u', ''); - $password = Minz_Request::param('p', ''); - Minz_Request::_param('p'); - - if (!$username) { - return; - } - - $conf = get_user_configuration($username); - if (is_null($conf)) { - return; - } - - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - - $s = $conf->passwordHash; - $ok = password_verify($password, $s); - unset($password); - if ($ok) { - Minz_Session::_param('currentUser', $username); - Minz_Session::_param('passwordHash', $s); - FreshRSS_Auth::giveAccess(); - - Minz_Request::good(_t('feedback.auth.login.success'), - array('c' => 'index', 'a' => 'index')); - } else { - Minz_Log::warning('Unsafe password mismatch for user ' . $username); - Minz_Error::error(403, array(_t('feedback.auth.login.invalid')), false); - } - } - } - - /** - * This action handles Persona login page. - * - * If this action is reached through a POST request, assertion from Persona - * is verificated and user connected if all is ok. - * - * Parameter is: - * - assertion (default: false) - * - * @todo: Persona system should be moved to a plugin - */ - public function personaLoginAction() { - $this->view->res = false; - - if (Minz_Request::isPost()) { - $this->view->_useLayout(false); - - $assert = Minz_Request::param('assertion'); - $url = 'https://verifier.login.persona.org/verify'; - $params = 'assertion=' . $assert . '&audience=' . - urlencode(Minz_Url::display(null, 'php', true)); - $ch = curl_init(); - $options = array( - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => TRUE, - CURLOPT_POST => 2, - CURLOPT_POSTFIELDS => $params - ); - curl_setopt_array($ch, $options); - $result = curl_exec($ch); - curl_close($ch); - - $res = json_decode($result, true); - - $login_ok = false; - $reason = ''; - if ($res['status'] === 'okay') { - $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL); - if ($email != '') { - $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; - if (($current_user = @file_get_contents($persona_file)) !== false) { - $current_user = trim($current_user); - $conf = get_user_configuration($current_user); - if (!is_null($conf)) { - $login_ok = strcasecmp($email, $conf->mail_login) === 0; - } else { - $reason = 'Invalid configuration for user ' . - '[' . $current_user . ']'; - } - } - } else { - $reason = 'Invalid email format [' . $res['email'] . ']'; - } - } else { - $reason = $res['reason']; - } - - if ($login_ok) { - Minz_Session::_param('currentUser', $current_user); - Minz_Session::_param('mail', $email); - FreshRSS_Auth::giveAccess(); - invalidateHttpCache(); - } else { - Minz_Log::warning($reason); - - $res = array(); - $res['status'] = 'failure'; - $res['reason'] = _t('feedback.auth.login.invalid'); - } - - header('Content-Type: application/json; charset=UTF-8'); - $this->view->res = $res; - } - } - - /** - * This action removes all accesses of the current user. - */ - public function logoutAction() { - invalidateHttpCache(); - FreshRSS_Auth::removeAccess(); - Minz_Request::good(_t('feedback.auth.logout.success'), - array('c' => 'index', 'a' => 'index')); - } - - /** - * This action resets the authentication system. - * - * After reseting, form auth is set by default. - */ - public function resetAction() { - Minz_View::prependTitle(_t('admin.auth.title_reset') . ' · '); - - Minz_View::appendScript(Minz_Url::display( - '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') - )); - - $this->view->no_form = false; - // Enable changement of auth only if Persona! - if (FreshRSS_Context::$system_conf->auth_type != 'persona') { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.auth.not_persona') - ); - $this->view->no_form = true; - return; - } - - $conf = get_user_configuration(FreshRSS_Context::$system_conf->default_user); - if (is_null($conf)) { - return; - } - - // Admin user must have set its master password. - if (!$conf->passwordHash) { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.auth.no_password_set') - ); - $this->view->no_form = true; - return; - } - - invalidateHttpCache(); - - if (Minz_Request::isPost()) { - $nonce = Minz_Session::param('nonce'); - $username = Minz_Request::param('username', ''); - $challenge = Minz_Request::param('challenge', ''); - - $ok = FreshRSS_FormAuth::checkCredentials( - $username, $conf->passwordHash, $nonce, $challenge - ); - - if ($ok) { - FreshRSS_Context::$system_conf->auth_type = 'form'; - $ok = FreshRSS_Context::$system_conf->save(); - - if ($ok) { - Minz_Request::good(_t('feedback.auth.form.set')); - } else { - Minz_Request::bad(_t('feedback.auth.form.not_set'), - array('c' => 'auth', 'a' => 'reset')); - } - } else { - Minz_Log::warning('Password mismatch for' . - ' user=' . $username . - ', nonce=' . $nonce . - ', c=' . $challenge); - Minz_Request::bad(_t('feedback.auth.login.invalid'), - array('c' => 'auth', 'a' => 'reset')); - } - } - } - - /** - * This action gives possibility to a user to create an account. - */ - public function registerAction() { - if (max_registrations_reached()) { - Minz_Error::error(403); - } - - Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · '); - } -} diff --git a/sources/app/Controllers/categoryController.php b/sources/app/Controllers/categoryController.php deleted file mode 100755 index e65c146..0000000 --- a/sources/app/Controllers/categoryController.php +++ /dev/null @@ -1,194 +0,0 @@ -checkDefault(); - } - - /** - * This action creates a new category. - * - * Request parameter is: - * - new-category - */ - public function createAction() { - $catDAO = new FreshRSS_CategoryDAO(); - $url_redirect = array('c' => 'subscription', 'a' => 'index'); - - $limits = FreshRSS_Context::$system_conf->limits; - $this->view->categories = $catDAO->listCategories(false); - - if (count($this->view->categories) >= $limits['max_categories']) { - Minz_Request::bad(_t('feedback.sub.category.over_max', $limits['max_categories']), - $url_redirect); - } - - if (Minz_Request::isPost()) { - invalidateHttpCache(); - - $cat_name = Minz_Request::param('new-category'); - if (!$cat_name) { - Minz_Request::bad(_t('feedback.sub.category.no_name'), $url_redirect); - } - - $cat = new FreshRSS_Category($cat_name); - - if ($catDAO->searchByName($cat->name()) != null) { - Minz_Request::bad(_t('feedback.sub.category.name_exists'), $url_redirect); - } - - $values = array( - 'id' => $cat->id(), - 'name' => $cat->name(), - ); - - if ($catDAO->addCategory($values)) { - Minz_Request::good(_t('feedback.sub.category.created', $cat->name()), $url_redirect); - } else { - Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } - - /** - * This action updates the given category. - * - * Request parameters are: - * - id - * - name - */ - public function updateAction() { - $catDAO = new FreshRSS_CategoryDAO(); - $url_redirect = array('c' => 'subscription', 'a' => 'index'); - - if (Minz_Request::isPost()) { - invalidateHttpCache(); - - $id = Minz_Request::param('id'); - $name = Minz_Request::param('name', ''); - if (strlen($name) <= 0) { - Minz_Request::bad(_t('feedback.sub.category.no_name'), $url_redirect); - } - - if ($catDAO->searchById($id) == null) { - Minz_Request::bad(_t('feedback.sub.category.not_exist'), $url_redirect); - } - - $cat = new FreshRSS_Category($name); - $values = array( - 'name' => $cat->name(), - ); - - if ($catDAO->updateCategory($id, $values)) { - Minz_Request::good(_t('feedback.sub.category.updated'), $url_redirect); - } else { - Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } - - /** - * This action deletes a category. - * Feeds in the given category are moved in the default category. - * Related user queries are deleted too. - * - * Request parameter is: - * - id (of a category) - */ - public function deleteAction() { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $catDAO = new FreshRSS_CategoryDAO(); - $default_category = $catDAO->getDefault(); - $url_redirect = array('c' => 'subscription', 'a' => 'index'); - - if (Minz_Request::isPost()) { - invalidateHttpCache(); - - $id = Minz_Request::param('id'); - if (!$id) { - Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect); - } - - if ($id === $default_category->id()) { - Minz_Request::bad(_t('feedback.sub.category.not_delete_default'), $url_redirect); - } - - if ($feedDAO->changeCategory($id, $default_category->id()) === false) { - Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); - } - - if ($catDAO->deleteCategory($id) === false) { - Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); - } - - // Remove related queries. - FreshRSS_Context::$user_conf->queries = remove_query_by_get( - 'c_' . $id, FreshRSS_Context::$user_conf->queries); - FreshRSS_Context::$user_conf->save(); - - Minz_Request::good(_t('feedback.sub.category.deleted'), $url_redirect); - } - - Minz_Request::forward($url_redirect, true); - } - - /** - * This action deletes all the feeds relative to a given category. - * Feed-related queries are deleted. - * - * Request parameter is: - * - id (of a category) - */ - public function emptyAction() { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $url_redirect = array('c' => 'subscription', 'a' => 'index'); - - if (Minz_Request::isPost()) { - invalidateHttpCache(); - - $id = Minz_Request::param('id'); - if (!$id) { - Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect); - } - - // List feeds to remove then related user queries. - $feeds = $feedDAO->listByCategory($id); - - if ($feedDAO->deleteFeedByCategory($id)) { - // TODO: Delete old favicons - - // Remove related queries - foreach ($feeds as $feed) { - FreshRSS_Context::$user_conf->queries = remove_query_by_get( - 'f_' . $feed->id(), FreshRSS_Context::$user_conf->queries); - } - FreshRSS_Context::$user_conf->save(); - - Minz_Request::good(_t('feedback.sub.category.emptied'), $url_redirect); - } else { - Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } -} diff --git a/sources/app/Controllers/configureController.php b/sources/app/Controllers/configureController.php deleted file mode 100755 index d0f0bd6..0000000 --- a/sources/app/Controllers/configureController.php +++ /dev/null @@ -1,331 +0,0 @@ -language = Minz_Request::param('language', 'en'); - FreshRSS_Context::$user_conf->theme = Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme); - FreshRSS_Context::$user_conf->content_width = Minz_Request::param('content_width', 'thin'); - FreshRSS_Context::$user_conf->topline_read = Minz_Request::param('topline_read', false); - FreshRSS_Context::$user_conf->topline_favorite = Minz_Request::param('topline_favorite', false); - FreshRSS_Context::$user_conf->topline_date = Minz_Request::param('topline_date', false); - FreshRSS_Context::$user_conf->topline_link = Minz_Request::param('topline_link', false); - FreshRSS_Context::$user_conf->bottomline_read = Minz_Request::param('bottomline_read', false); - FreshRSS_Context::$user_conf->bottomline_favorite = Minz_Request::param('bottomline_favorite', false); - FreshRSS_Context::$user_conf->bottomline_sharing = Minz_Request::param('bottomline_sharing', false); - FreshRSS_Context::$user_conf->bottomline_tags = Minz_Request::param('bottomline_tags', false); - FreshRSS_Context::$user_conf->bottomline_date = Minz_Request::param('bottomline_date', false); - FreshRSS_Context::$user_conf->bottomline_link = Minz_Request::param('bottomline_link', false); - FreshRSS_Context::$user_conf->html5_notif_timeout = Minz_Request::param('html5_notif_timeout', 0); - FreshRSS_Context::$user_conf->save(); - - Minz_Session::_param('language', FreshRSS_Context::$user_conf->language); - Minz_Translate::reset(FreshRSS_Context::$user_conf->language); - invalidateHttpCache(); - - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'configure', 'a' => 'display')); - } - - $this->view->themes = FreshRSS_Themes::get(); - - Minz_View::prependTitle(_t('conf.display.title') . ' · '); - } - - /** - * This action handles the reading configuration page. - * - * It displays the reading configuration page. - * If this action is reached through a POST request, it stores all new - * configuration values then sends a notification to the user. - * - * The options available on the page are: - * - number of posts per page (default: 10) - * - view mode (default: normal) - * - default article view (default: all) - * - load automatically articles - * - display expanded articles - * - display expanded categories - * - hide categories and feeds without unread articles - * - jump on next category or feed when marked as read - * - image lazy loading - * - stick open articles to the top - * - display a confirmation when reading all articles - * - auto remove article after reading - * - article order (default: DESC) - * - mark articles as read when: - * - displayed - * - opened on site - * - scrolled - * - received - * Default values are false unless specified. - */ - public function readingAction() { - if (Minz_Request::isPost()) { - FreshRSS_Context::$user_conf->posts_per_page = Minz_Request::param('posts_per_page', 10); - FreshRSS_Context::$user_conf->view_mode = Minz_Request::param('view_mode', 'normal'); - FreshRSS_Context::$user_conf->default_view = Minz_Request::param('default_view', 'adaptive'); - FreshRSS_Context::$user_conf->auto_load_more = Minz_Request::param('auto_load_more', false); - FreshRSS_Context::$user_conf->display_posts = Minz_Request::param('display_posts', false); - FreshRSS_Context::$user_conf->display_categories = Minz_Request::param('display_categories', false); - FreshRSS_Context::$user_conf->hide_read_feeds = Minz_Request::param('hide_read_feeds', false); - FreshRSS_Context::$user_conf->onread_jump_next = Minz_Request::param('onread_jump_next', false); - FreshRSS_Context::$user_conf->lazyload = Minz_Request::param('lazyload', false); - FreshRSS_Context::$user_conf->sticky_post = Minz_Request::param('sticky_post', false); - FreshRSS_Context::$user_conf->reading_confirm = Minz_Request::param('reading_confirm', false); - FreshRSS_Context::$user_conf->auto_remove_article = Minz_Request::param('auto_remove_article', false); - FreshRSS_Context::$user_conf->mark_updated_article_unread = Minz_Request::param('mark_updated_article_unread', false); - FreshRSS_Context::$user_conf->sort_order = Minz_Request::param('sort_order', 'DESC'); - FreshRSS_Context::$user_conf->mark_when = array( - 'article' => Minz_Request::param('mark_open_article', false), - 'site' => Minz_Request::param('mark_open_site', false), - 'scroll' => Minz_Request::param('mark_scroll', false), - 'reception' => Minz_Request::param('mark_upon_reception', false), - ); - FreshRSS_Context::$user_conf->save(); - invalidateHttpCache(); - - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'configure', 'a' => 'reading')); - } - - Minz_View::prependTitle(_t('conf.reading.title') . ' · '); - } - - /** - * This action handles the sharing configuration page. - * - * It displays the sharing configuration page. - * If this action is reached through a POST request, it stores all - * configuration values then sends a notification to the user. - */ - public function sharingAction() { - if (Minz_Request::isPost()) { - $params = Minz_Request::params(); - FreshRSS_Context::$user_conf->sharing = $params['share']; - FreshRSS_Context::$user_conf->save(); - invalidateHttpCache(); - - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'configure', 'a' => 'sharing')); - } - - Minz_View::prependTitle(_t('conf.sharing.title') . ' · '); - } - - /** - * This action handles the shortcut configuration page. - * - * It displays the shortcut configuration page. - * If this action is reached through a POST request, it stores all new - * configuration values then sends a notification to the user. - * - * The authorized values for shortcuts are letters (a to z), numbers (0 - * to 9), function keys (f1 to f12), backspace, delete, down, end, enter, - * escape, home, insert, left, page down, page up, return, right, space, - * tab and up. - */ - public function shortcutAction() { - $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', - 'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left', - 'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right', - 's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y', - 'z', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', - 'f10', 'f11', 'f12'); - $this->view->list_keys = $list_keys; - - if (Minz_Request::isPost()) { - $shortcuts = Minz_Request::param('shortcuts'); - $shortcuts_ok = array(); - - foreach ($shortcuts as $key => $value) { - if (in_array($value, $list_keys)) { - $shortcuts_ok[$key] = $value; - } - } - - FreshRSS_Context::$user_conf->shortcuts = $shortcuts_ok; - FreshRSS_Context::$user_conf->save(); - invalidateHttpCache(); - - Minz_Request::good(_t('feedback.conf.shortcuts_updated'), - array('c' => 'configure', 'a' => 'shortcut')); - } - - Minz_View::prependTitle(_t('conf.shortcut.title') . ' · '); - } - - /** - * This action handles the archive configuration page. - * - * It displays the archive configuration page. - * If this action is reached through a POST request, it stores all new - * configuration values then sends a notification to the user. - * - * The options available on that page are: - * - duration to retain old article (default: 3) - * - number of article to retain per feed (default: 0) - * - refresh frequency (default: -2) - * - * @todo explain why the default value is -2 but this value does not - * exist in the drop-down list - */ - public function archivingAction() { - if (Minz_Request::isPost()) { - FreshRSS_Context::$user_conf->old_entries = Minz_Request::param('old_entries', 3); - FreshRSS_Context::$user_conf->keep_history_default = Minz_Request::param('keep_history_default', 0); - FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', -2); - FreshRSS_Context::$user_conf->save(); - invalidateHttpCache(); - - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'configure', 'a' => 'archiving')); - } - - Minz_View::prependTitle(_t('conf.archiving.title') . ' · '); - - $entryDAO = FreshRSS_Factory::createEntryDao(); - $this->view->nb_total = $entryDAO->count(); - $this->view->size_user = $entryDAO->size(); - - if (FreshRSS_Auth::hasAccess('admin')) { - $this->view->size_total = $entryDAO->size(true); - } - } - - /** - * This action handles the user queries configuration page. - * - * If this action is reached through a POST request, it stores all new - * configuration values then sends a notification to the user then - * redirect to the same page. - * If this action is not reached through a POST request, it displays the - * configuration page and verifies that every user query is runable by - * checking if categories and feeds are still in use. - */ - public function queriesAction() { - $category_dao = new FreshRSS_CategoryDAO(); - $feed_dao = FreshRSS_Factory::createFeedDao(); - if (Minz_Request::isPost()) { - $params = Minz_Request::param('queries', array()); - - foreach ($params as $key => $query) { - if (!$query['name']) { - $query['name'] = _t('conf.query.number', $key + 1); - } - $queries[] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao); - } - FreshRSS_Context::$user_conf->queries = $queries; - FreshRSS_Context::$user_conf->save(); - - Minz_Request::good(_t('feedback.conf.updated'), - array('c' => 'configure', 'a' => 'queries')); - } else { - $this->view->queries = array(); - foreach (FreshRSS_Context::$user_conf->queries as $key => $query) { - $this->view->queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao); - } - } - - Minz_View::prependTitle(_t('conf.query.title') . ' · '); - } - - /** - * This action handles the creation of a user query. - * - * It gets the GET parameters and stores them in the configuration query - * storage. Before it is saved, the unwanted parameters are unset to keep - * lean data. - */ - public function addQueryAction() { - $category_dao = new FreshRSS_CategoryDAO(); - $feed_dao = FreshRSS_Factory::createFeedDao(); - $queries = array(); - foreach (FreshRSS_Context::$user_conf->queries as $key => $query) { - $queries[$key] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao); - } - $params = Minz_Request::params(); - $params['url'] = Minz_Url::display(array('params' => $params)); - $params['name'] = _t('conf.query.number', count($queries) + 1); - $queries[] = new FreshRSS_UserQuery($params, $feed_dao, $category_dao); - - FreshRSS_Context::$user_conf->queries = $queries; - FreshRSS_Context::$user_conf->save(); - - Minz_Request::good(_t('feedback.conf.query_created', $query['name']), - array('c' => 'configure', 'a' => 'queries')); - } - - /** - * This action handles the system configuration page. - * - * It displays the system configuration page. - * If this action is reach through a POST request, it stores all new - * configuration values then sends a notification to the user. - * - * The options available on the page are: - * - user limit (default: 1) - * - user category limit (default: 16384) - * - user feed limit (default: 16384) - */ - public function systemAction() { - if (!FreshRSS_Auth::hasAccess('admin')) { - Minz_Error::error(403); - } - if (Minz_Request::isPost()) { - $limits = FreshRSS_Context::$system_conf->limits; - $limits['max_registrations'] = Minz_Request::param('max-registrations', 1); - $limits['max_feeds'] = Minz_Request::param('max-feeds', 16384); - $limits['max_categories'] = Minz_Request::param('max-categories', 16384); - FreshRSS_Context::$system_conf->limits = $limits; - FreshRSS_Context::$system_conf->title = Minz_Request::param('instance-name', 'FreshRSS'); - FreshRSS_Context::$system_conf->auto_update_url = Minz_Request::param('auto-update-url', false); - FreshRSS_Context::$system_conf->save(); - - invalidateHttpCache(); - - Minz_Session::_param('notification', array( - 'type' => 'good', - 'content' => _t('feedback.conf.updated') - )); - } - } -} diff --git a/sources/app/Controllers/entryController.php b/sources/app/Controllers/entryController.php deleted file mode 100755 index bff1073..0000000 --- a/sources/app/Controllers/entryController.php +++ /dev/null @@ -1,192 +0,0 @@ -ajax = Minz_Request::param('ajax'); - if ($this->ajax) { - $this->view->_useLayout(false); - Minz_Request::_param('ajax'); - } - } - - /** - * Mark one or several entries as read (or not!). - * - * If request concerns several entries, it MUST be a POST request. - * If request concerns several entries, only mark them as read is available. - * - * Parameters are: - * - id (default: false) - * - get (default: false) /(c_\d+|f_\d+|s|a)/ - * - nextGet (default: $get) - * - idMax (default: 0) - * - is_read (default: true) - */ - public function readAction() { - $id = Minz_Request::param('id'); - $get = Minz_Request::param('get'); - $next_get = Minz_Request::param('nextGet', $get); - $id_max = Minz_Request::param('idMax', 0); - $params = array(); - - $entryDAO = FreshRSS_Factory::createEntryDao(); - if ($id === false) { - // id is false? It MUST be a POST request! - if (!Minz_Request::isPost()) { - Minz_Request::bad(_t('feedback.access.not_found'), array('c' => 'index', 'a' => 'index')); - return; - } - - if (!$get) { - // No get? Mark all entries as read (from $id_max) - $entryDAO->markReadEntries($id_max); - } else { - $type_get = $get[0]; - $get = substr($get, 2); - switch($type_get) { - case 'c': - $entryDAO->markReadCat($get, $id_max); - break; - case 'f': - $entryDAO->markReadFeed($get, $id_max); - break; - case 's': - $entryDAO->markReadEntries($id_max, true); - break; - case 'a': - $entryDAO->markReadEntries($id_max); - break; - } - - if ($next_get !== 'a') { - // Redirect to the correct page (category, feed or starred) - // Not "a" because it is the default value if nothing is - // given. - $params['get'] = $next_get; - } - } - } else { - $is_read = (bool)(Minz_Request::param('is_read', true)); - $entryDAO->markRead($id, $is_read); - } - - if (!$this->ajax) { - Minz_Request::good(_t('feedback.sub.feed.marked_read'), array( - 'c' => 'index', - 'a' => 'index', - 'params' => $params, - ), true); - } - } - - /** - * This action marks an entry as favourite (bookmark) or not. - * - * Parameter is: - * - id (default: false) - * - is_favorite (default: true) - * If id is false, nothing happened. - */ - public function bookmarkAction() { - $id = Minz_Request::param('id'); - $is_favourite = (bool)Minz_Request::param('is_favorite', true); - if ($id !== false) { - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entryDAO->markFavorite($id, $is_favourite); - } - - if (!$this->ajax) { - Minz_Request::forward(array( - 'c' => 'index', - 'a' => 'index', - ), true); - } - } - - /** - * This action optimizes database to reduce its size. - * - * This action shouldbe reached by a POST request. - * - * @todo move this action in configure controller. - * @todo call this action through web-cron when available - */ - public function optimizeAction() { - $url_redirect = array( - 'c' => 'configure', - 'a' => 'archiving', - ); - - if (!Minz_Request::isPost()) { - Minz_Request::forward($url_redirect, true); - } - - @set_time_limit(300); - - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entryDAO->optimizeTable(); - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $feedDAO->updateCachedValues(); - - invalidateHttpCache(); - Minz_Request::good(_t('feedback.admin.optimization_complete'), $url_redirect); - } - - /** - * This action purges old entries from feeds. - * - * @todo should be a POST request - * @todo should be in feedController - */ - public function purgeAction() { - @set_time_limit(300); - - $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $feeds = $feedDAO->listFeeds(); - $nb_total = 0; - - invalidateHttpCache(); - - foreach ($feeds as $feed) { - $feed_history = $feed->keepHistory(); - if ($feed_history == -2) { - // TODO: -2 must be a constant! - // -2 means we take the default value from configuration - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } - - if ($feed_history >= 0) { - $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feed_history); - if ($nb > 0) { - $nb_total += $nb; - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']'); - } - } - } - - $feedDAO->updateCachedValues(); - - invalidateHttpCache(); - Minz_Request::good(_t('feedback.sub.purge_completed', $nb_total), array( - 'c' => 'configure', - 'a' => 'archiving' - )); - } -} diff --git a/sources/app/Controllers/errorController.php b/sources/app/Controllers/errorController.php deleted file mode 100755 index b0bafda..0000000 --- a/sources/app/Controllers/errorController.php +++ /dev/null @@ -1,53 +0,0 @@ -view->code = 'Error 403 - Forbidden'; - $this->view->errorMessage = _t('feedback.access.denied'); - break; - case 500: - header('HTTP/1.1 500 Internal Server Error'); - $this->view->code = 'Error 500 - Internal Server Error'; - break; - case 503: - header('HTTP/1.1 503 Service Unavailable'); - $this->view->code = 'Error 503 - Service Unavailable'; - break; - case 404: - default: - header('HTTP/1.1 404 Not Found'); - $this->view->code = 'Error 404 - Not found'; - $this->view->errorMessage = _t('feedback.access.not_found'); - } - - $error_message = trim(implode($error_logs)); - if ($error_message !== '') { - $this->view->errorMessage = $error_message; - } - - Minz_View::prependTitle($this->view->code . ' · '); - } -} diff --git a/sources/app/Controllers/extensionController.php b/sources/app/Controllers/extensionController.php deleted file mode 100755 index b6d2d3f..0000000 --- a/sources/app/Controllers/extensionController.php +++ /dev/null @@ -1,215 +0,0 @@ -view->extension_list = array( - 'system' => array(), - 'user' => array(), - ); - - $extensions = Minz_ExtensionManager::listExtensions(); - foreach ($extensions as $ext) { - $this->view->extension_list[$ext->getType()][] = $ext; - } - } - - /** - * This action handles configuration of a given extension. - * - * Only administrator can configure a system extension. - * - * Parameters are: - * - e: the extension name (urlencoded) - * - additional parameters which should be handle by the extension - * handleConfigureAction() method (POST request). - */ - public function configureAction() { - if (Minz_Request::param('ajax')) { - $this->view->_useLayout(false); - } else { - $this->indexAction(); - $this->view->change_view('extension', 'index'); - } - - $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::findExtension($ext_name); - - if (is_null($ext)) { - Minz_Error::error(404); - } - if ($ext->getType() === 'system' && !FreshRSS_Auth::hasAccess('admin')) { - Minz_Error::error(403); - } - - $this->view->extension = $ext; - $this->view->extension->handleConfigureAction(); - } - - /** - * This action enables a disabled extension for the current user. - * - * System extensions can only be enabled by an administrator. - * This action must be reached by a POST request. - * - * Parameter is: - * - e: the extension name (urlencoded). - */ - public function enableAction() { - $url_redirect = array('c' => 'extension', 'a' => 'index'); - - if (Minz_Request::isPost()) { - $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::findExtension($ext_name); - - if (is_null($ext)) { - Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), - $url_redirect); - } - - if ($ext->isEnabled()) { - Minz_Request::bad(_t('feedback.extensions.already_enabled', $ext_name), - $url_redirect); - } - - $conf = null; - if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { - $conf = FreshRSS_Context::$system_conf; - } elseif ($ext->getType() === 'user') { - $conf = FreshRSS_Context::$user_conf; - } else { - Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name), - $url_redirect); - } - - $res = $ext->install(); - - if ($res === true) { - $ext_list = $conf->extensions_enabled; - array_push_unique($ext_list, $ext_name); - $conf->extensions_enabled = $ext_list; - $conf->save(); - - Minz_Request::good(_t('feedback.extensions.enable.ok', $ext_name), - $url_redirect); - } else { - Minz_Log::warning('Can not enable extension ' . $ext_name . ': ' . $res); - Minz_Request::bad(_t('feedback.extensions.enable.ko', $ext_name, _url('index', 'logs')), - $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } - - /** - * This action disables an enabled extension for the current user. - * - * System extensions can only be disabled by an administrator. - * This action must be reached by a POST request. - * - * Parameter is: - * - e: the extension name (urlencoded). - */ - public function disableAction() { - $url_redirect = array('c' => 'extension', 'a' => 'index'); - - if (Minz_Request::isPost()) { - $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::findExtension($ext_name); - - if (is_null($ext)) { - Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), - $url_redirect); - } - - if (!$ext->isEnabled()) { - Minz_Request::bad(_t('feedback.extensions.not_enabled', $ext_name), - $url_redirect); - } - - $conf = null; - if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { - $conf = FreshRSS_Context::$system_conf; - } elseif ($ext->getType() === 'user') { - $conf = FreshRSS_Context::$user_conf; - } else { - Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name), - $url_redirect); - } - - $res = $ext->uninstall(); - - if ($res === true) { - $ext_list = $conf->extensions_enabled; - array_remove($ext_list, $ext_name); - $conf->extensions_enabled = $ext_list; - $conf->save(); - - Minz_Request::good(_t('feedback.extensions.disable.ok', $ext_name), - $url_redirect); - } else { - Minz_Log::warning('Can not unable extension ' . $ext_name . ': ' . $res); - Minz_Request::bad(_t('feedback.extensions.disable.ko', $ext_name, _url('index', 'logs')), - $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } - - /** - * This action handles deletion of an extension. - * - * Only administrator can remove an extension. - * This action must be reached by a POST request. - * - * Parameter is: - * -e: extension name (urlencoded) - */ - public function removeAction() { - if (!FreshRSS_Auth::hasAccess('admin')) { - Minz_Error::error(403); - } - - $url_redirect = array('c' => 'extension', 'a' => 'index'); - - if (Minz_Request::isPost()) { - $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::findExtension($ext_name); - - if (is_null($ext)) { - Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), - $url_redirect); - } - - $res = recursive_unlink($ext->getPath()); - if ($res) { - Minz_Request::good(_t('feedback.extensions.removed', $ext_name), - $url_redirect); - } else { - Minz_Request::bad(_t('feedback.extensions.cannot_delete', $ext_name), - $url_redirect); - } - } - - Minz_Request::forward($url_redirect, true); - } -} diff --git a/sources/app/Controllers/feedController.php b/sources/app/Controllers/feedController.php deleted file mode 100755 index 6a8aa01..0000000 --- a/sources/app/Controllers/feedController.php +++ /dev/null @@ -1,573 +0,0 @@ -token; - $token_param = Minz_Request::param('token', ''); - $token_is_ok = ($token != '' && $token == $token_param); - $action = Minz_Request::actionName(); - $allow_anonymous_refresh = FreshRSS_Context::$system_conf->allow_anonymous_refresh; - if ($action !== 'actualize' || - !($allow_anonymous_refresh || $token_is_ok)) { - Minz_Error::error(403); - } - } - } - - /** - * This action subscribes to a feed. - * - * It can be reached by both GET and POST requests. - * - * GET request displays a form to add and configure a feed. - * Request parameter is: - * - url_rss (default: false) - * - * POST request adds a feed in database. - * Parameters are: - * - url_rss (default: false) - * - category (default: false) - * - new_category (required if category == 'nc') - * - http_user (default: false) - * - http_pass (default: false) - * It tries to get website information from RSS feed. - * If no category is given, feed is added to the default one. - * - * If url_rss is false, nothing happened. - */ - public function addAction() { - $url = Minz_Request::param('url_rss'); - - if ($url === false) { - // No url, do nothing - Minz_Request::forward(array( - 'c' => 'subscription', - 'a' => 'index' - ), true); - } - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->catDAO = new FreshRSS_CategoryDAO(); - $url_redirect = array( - 'c' => 'subscription', - 'a' => 'index', - 'params' => array(), - ); - - $limits = FreshRSS_Context::$system_conf->limits; - $this->view->feeds = $feedDAO->listFeeds(); - if (count($this->view->feeds) >= $limits['max_feeds']) { - Minz_Request::bad(_t('feedback.sub.feed.over_max', $limits['max_feeds']), - $url_redirect); - } - - if (Minz_Request::isPost()) { - @set_time_limit(300); - - $cat = Minz_Request::param('category'); - if ($cat === 'nc') { - // User want to create a new category, new_category parameter - // must exist - $new_cat = Minz_Request::param('new_category'); - if (empty($new_cat['name'])) { - $cat = false; - } else { - $cat = $this->catDAO->addCategory($new_cat); - } - } - - if ($cat === false) { - // If category was not given or if creating new category failed, - // get the default category - $this->catDAO->checkDefault(); - $def_cat = $this->catDAO->getDefault(); - $cat = $def_cat->id(); - } - - // HTTP information are useful if feed is protected behind a - // HTTP authentication - $user = trim(Minz_Request::param('http_user', '')); - $pass = Minz_Request::param('http_pass', ''); - $http_auth = ''; - if ($user != '' && $pass != '') { //TODO: Sanitize - $http_auth = $user . ':' . $pass; - } - - $transaction_started = false; - try { - $feed = new FreshRSS_Feed($url); - } catch (FreshRSS_BadUrl_Exception $e) { - // Given url was not a valid url! - Minz_Log::warning($e->getMessage()); - Minz_Request::bad(_t('feedback.sub.feed.invalid_url', $url), $url_redirect); - } - - $feed->_httpAuth($http_auth); - - try { - $feed->load(true); - } catch (FreshRSS_Feed_Exception $e) { - // Something went bad (timeout, server not found, etc.) - Minz_Log::warning($e->getMessage()); - Minz_Request::bad( - _t('feedback.sub.feed.internal_problem', _url('index', 'logs')), - $url_redirect - ); - } catch (Minz_FileNotExistException $e) { - // Cache directory doesn't exist! - Minz_Log::error($e->getMessage()); - Minz_Request::bad( - _t('feedback.sub.feed.internal_problem', _url('index', 'logs')), - $url_redirect - ); - } - - if ($feedDAO->searchByUrl($feed->url())) { - Minz_Request::bad( - _t('feedback.sub.feed.already_subscribed', $feed->name()), - $url_redirect - ); - } - - $feed->_category($cat); - - // Call the extension hook - $name = $feed->name(); - $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); - if ($feed === null) { - Minz_Request::bad(_t('feedback.sub.feed.not_added', $name), $url_redirect); - } - - $values = array( - 'url' => $feed->url(), - 'category' => $feed->category(), - 'name' => $feed->name(), - 'website' => $feed->website(), - 'description' => $feed->description(), - 'lastUpdate' => time(), - 'httpAuth' => $feed->httpAuth(), - ); - - $id = $feedDAO->addFeed($values); - if (!$id) { - // There was an error in database... we cannot say what here. - Minz_Request::bad(_t('feedback.sub.feed.not_added', $feed->name()), $url_redirect); - } - - // Ok, feed has been added in database. Now we have to refresh entries. - $feed->_id($id); - $feed->faviconPrepare(); - //$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed - - $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; - - $entryDAO = FreshRSS_Factory::createEntryDao(); - // We want chronological order and SimplePie uses reverse order. - $entries = array_reverse($feed->entries()); - - // Calculate date of oldest entries we accept in DB. - $nb_month_old = FreshRSS_Context::$user_conf->old_entries; - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - // Use a shared statement and a transaction to improve a LOT the - // performances. - $feedDAO->beginTransaction(); - foreach ($entries as $entry) { - // Entries are added without any verification. - $entry->_feed($feed->id()); - $entry->_id(min(time(), $entry->date(true)) . uSecString()); - $entry->_isRead($is_read); - - $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); - if ($entry === null) { - // An extension has returned a null value, there is nothing to insert. - continue; - } - - $values = $entry->toArray(); - $entryDAO->addEntry($values); - } - $feedDAO->updateLastUpdate($feed->id()); - $feedDAO->commit(); - - // Entries are in DB, we redirect to feed configuration page. - $url_redirect['params']['id'] = $feed->id(); - Minz_Request::good(_t('feedback.sub.feed.added', $feed->name()), $url_redirect); - } else { - // GET request: we must ask confirmation to user before adding feed. - Minz_View::prependTitle(_t('sub.feed.title_add') . ' · '); - - $this->view->categories = $this->catDAO->listCategories(false); - $this->view->feed = new FreshRSS_Feed($url); - try { - // We try to get more information about the feed. - $this->view->feed->load(true); - $this->view->load_ok = true; - } catch (Exception $e) { - $this->view->load_ok = false; - } - - $feed = $feedDAO->searchByUrl($this->view->feed->url()); - if ($feed) { - // Already subscribe so we redirect to the feed configuration page. - $url_redirect['params']['id'] = $feed->id(); - Minz_Request::good(_t('feedback.sub.feed.already_subscribed', $feed->name()), $url_redirect); - } - } - } - - /** - * This action remove entries from a given feed. - * - * It should be reached by a POST action. - * - * Parameter is: - * - id (default: false) - */ - public function truncateAction() { - $id = Minz_Request::param('id'); - $url_redirect = array( - 'c' => 'subscription', - 'a' => 'index', - 'params' => array('id' => $id) - ); - - if (!Minz_Request::isPost()) { - Minz_Request::forward($url_redirect, true); - } - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $n = $feedDAO->truncate($id); - - invalidateHttpCache(); - if ($n === false) { - Minz_Request::bad(_t('feedback.sub.feed.error'), $url_redirect); - } else { - Minz_Request::good(_t('feedback.sub.feed.n_entries_deleted', $n), $url_redirect); - } - } - - /** - * This action actualizes entries from one or several feeds. - * - * Parameters are: - * - id (default: false): Feed ID - * - url (default: false): Feed URL - * - force (default: false) - * If id and url are not specified, all the feeds are actualized. But if force is - * false, process stops at 10 feeds to avoid time execution problem. - */ - public function actualizeAction($simplePiePush = null) { - @set_time_limit(300); - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $entryDAO = FreshRSS_Factory::createEntryDao(); - - Minz_Session::_param('actualize_feeds', false); - $id = Minz_Request::param('id'); - $url = Minz_Request::param('url'); - $force = Minz_Request::param('force'); - - // Create a list of feeds to actualize. - // If id is set and valid, corresponding feed is added to the list but - // alone in order to automatize further process. - $feeds = array(); - if ($id || $url) { - $feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url); - if ($feed) { - $feeds[] = $feed; - } - } else { - $feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default); - } - - // Calculate date of oldest entries we accept in DB. - $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - // PubSubHubbub support - $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled; - $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration. - - $updated_feeds = 0; - $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; - foreach ($feeds as $feed) { - $url = $feed->url(); //For detection of HTTP 301 - - $pubSubHubbubEnabled = $pubsubhubbubEnabledGeneral && $feed->pubSubHubbubEnabled(); - if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) { - //$text = 'Skip pull of feed using PubSubHubbub: ' . $url; - //Minz_Log::debug($text); - //file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); - continue; //When PubSubHubbub is used, do not pull refresh so often - } - - if (!$feed->lock()) { - Minz_Log::notice('Feed already being actualized: ' . $feed->url()); - continue; - } - - try { - if ($simplePiePush) { - $feed->loadEntries($simplePiePush); //Used by PubSubHubbub - } else { - $feed->load(false); - } - } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::warning($e->getMessage()); - $feedDAO->updateLastUpdate($feed->id(), true); - $feed->unlock(); - continue; - } - - $feed_history = $feed->keepHistory(); - if ($feed_history == -2) { - // TODO: -2 must be a constant! - // -2 means we take the default value from configuration - $feed_history = FreshRSS_Context::$user_conf->keep_history_default; - } - - // We want chronological order and SimplePie uses reverse order. - $entries = array_reverse($feed->entries()); - if (count($entries) > 0) { - $newGuids = array(); - foreach ($entries as $entry) { - $newGuids[] = $entry->guid(); - } - // For this feed, check existing GUIDs already in database. - $existingHashForGuids = $entryDAO->listHashForFeedGuids($feed->id(), $newGuids); - unset($newGuids); - - $oldGuids = array(); - // Add entries in database if possible. - foreach ($entries as $entry) { - $entry_date = $entry->date(true); - if (isset($existingHashForGuids[$entry->guid()])) { - $existingHash = $existingHashForGuids[$entry->guid()]; - if (strcasecmp($existingHash, $entry->hash()) === 0 || trim($existingHash, '0') == '') { - //This entry already exists and is unchanged. TODO: Remove the test with the zero'ed hash in FreshRSS v1.3 - $oldGuids[] = $entry->guid(); - } else { //This entry already exists but has been updated - //Minz_Log::debug('Entry with GUID `' . $entry->guid() . '` updated in feed ' . $feed->id() . - //', old hash ' . $existingHash . ', new hash ' . $entry->hash()); - //TODO: Make an updated/is_read policy by feed, in addition to the global one. - $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy. - if (!$entryDAO->hasTransaction()) { - $entryDAO->beginTransaction(); - } - $entryDAO->updateEntry($entry->toArray()); - } - } elseif ($feed_history == 0 && $entry_date < $date_min) { - // This entry should not be added considering configuration and date. - $oldGuids[] = $entry->guid(); - } else { - if ($entry_date < $date_min) { - $id = min(time(), $entry_date) . uSecString(); - $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read - } else { - $id = uTimeString(); - $entry->_isRead($is_read); - } - $entry->_id($id); - - $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); - if ($entry === null) { - // An extension has returned a null value, there is nothing to insert. - continue; - } - - if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull! - $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid(); - file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); - Minz_Log::warning($text); - $pubSubHubbubEnabled = false; - $feed->pubSubHubbubError(true); - } - - if (!$entryDAO->hasTransaction()) { - $entryDAO->beginTransaction(); - } - $entryDAO->addEntry($entry->toArray()); - } - } - $entryDAO->updateLastSeen($feed->id(), $oldGuids); - } - - if ($feed_history >= 0 && rand(0, 30) === 1) { - // TODO: move this function in web cron when available (see entry::purge) - // Remove old entries once in 30. - if (!$entryDAO->hasTransaction()) { - $entryDAO->beginTransaction(); - } - - $nb = $feedDAO->cleanOldEntries($feed->id(), - $date_min, - max($feed_history, count($entries) + 10)); - if ($nb > 0) { - Minz_Log::debug($nb . ' old entries cleaned in feed [' . - $feed->url() . ']'); - } - } - - $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->hasTransaction()); - if ($entryDAO->hasTransaction()) { - $entryDAO->commit(); - } - - if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub - if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs - $selfUrl = checkUrl($feed->selfUrl()); - if ($selfUrl) { - Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url()); - if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe - Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url()); - } - $feed->_url($selfUrl, false); - Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url()); - $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); - } - } - } - elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently - Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); - $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); - } - - $feed->faviconPrepare(); - if ($pubsubhubbubEnabledGeneral && $feed->pubSubHubbubPrepare()) { - Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url()); - if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe - Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url()); - } - } - $feed->unlock(); - $updated_feeds++; - unset($feed); - - // No more than 10 feeds unless $force is true to avoid overloading - // the server. - if ($updated_feeds >= 10 && !$force) { - break; - } - } - - if (Minz_Request::param('ajax')) { - // Most of the time, ajax request is for only one feed. But since - // there are several parallel requests, we should return that there - // are several updated feeds. - $notif = array( - 'type' => 'good', - 'content' => _t('feedback.sub.feed.actualizeds') - ); - Minz_Session::_param('notification', $notif); - // No layout in ajax request. - $this->view->_useLayout(false); - } else { - // Redirect to the main page with correct notification. - if ($updated_feeds === 1) { - $feed = reset($feeds); - Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array( - 'params' => array('get' => 'f_' . $feed->id()) - )); - } elseif ($updated_feeds > 1) { - Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array()); - } else { - Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array()); - } - } - return $updated_feeds; - } - - /** - * This action changes the category of a feed. - * - * This page must be reached by a POST request. - * - * Parameters are: - * - f_id (default: false) - * - c_id (default: false) - * If c_id is false, default category is used. - * - * @todo should handle order of the feed inside the category. - */ - public function moveAction() { - if (!Minz_Request::isPost()) { - Minz_Request::forward(array('c' => 'subscription'), true); - } - - $feed_id = Minz_Request::param('f_id'); - $cat_id = Minz_Request::param('c_id'); - - if ($cat_id === false) { - // If category was not given get the default one. - $catDAO = new FreshRSS_CategoryDAO(); - $catDAO->checkDefault(); - $def_cat = $catDAO->getDefault(); - $cat_id = $def_cat->id(); - } - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $values = array('category' => $cat_id); - - $feed = $feedDAO->searchById($feed_id); - if ($feed && ($feed->category() == $cat_id || - $feedDAO->updateFeed($feed_id, $values))) { - // TODO: return something useful - } else { - Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' . - 'in the category `' . $cat_id . '`'); - Minz_Error::error(404); - } - } - - /** - * This action deletes a feed. - * - * This page must be reached by a POST request. - * If there are related queries, they are deleted too. - * - * Parameters are: - * - id (default: false) - * - r (default: false) - * r permits to redirect to a given page at the end of this action. - * - * @todo handle "r" redirection in Minz_Request::forward()? - */ - public function deleteAction() { - $redirect_url = Minz_Request::param('r', false, true); - if (!$redirect_url) { - $redirect_url = array('c' => 'subscription', 'a' => 'index'); - } - - if (!Minz_Request::isPost()) { - Minz_Request::forward($redirect_url, true); - } - - $id = Minz_Request::param('id'); - $feedDAO = FreshRSS_Factory::createFeedDao(); - if ($feedDAO->deleteFeed($id)) { - // TODO: Delete old favicon - - // Remove related queries - FreshRSS_Context::$user_conf->queries = remove_query_by_get( - 'f_' . $id, FreshRSS_Context::$user_conf->queries); - FreshRSS_Context::$user_conf->save(); - - Minz_Request::good(_t('feedback.sub.feed.deleted'), $redirect_url); - } else { - Minz_Request::bad(_t('feedback.sub.feed.error'), $redirect_url); - } - } -} diff --git a/sources/app/Controllers/importExportController.php b/sources/app/Controllers/importExportController.php deleted file mode 100755 index 60e4672..0000000 --- a/sources/app/Controllers/importExportController.php +++ /dev/null @@ -1,611 +0,0 @@ -catDAO = new FreshRSS_CategoryDAO(); - $this->entryDAO = FreshRSS_Factory::createEntryDao(); - $this->feedDAO = FreshRSS_Factory::createFeedDao(); - } - - /** - * This action displays the main page for import / export system. - */ - public function indexAction() { - $this->view->feeds = $this->feedDAO->listFeeds(); - Minz_View::prependTitle(_t('sub.import_export.title') . ' · '); - } - - /** - * This action handles import action. - * - * It must be reached by a POST request. - * - * Parameter is: - * - file (default: nothing!) - * Available file types are: zip, json or xml. - */ - public function importAction() { - if (!Minz_Request::isPost()) { - Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); - } - - $file = $_FILES['file']; - $status_file = $file['error']; - - if ($status_file !== 0) { - Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file); - Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'), - array('c' => 'importExport', 'a' => 'index')); - } - - @set_time_limit(300); - - $type_file = $this->guessFileType($file['name']); - - $list_files = array( - 'opml' => array(), - 'json_starred' => array(), - 'json_feed' => array() - ); - - // We try to list all files according to their type - $list = array(); - if ($type_file === 'zip' && extension_loaded('zip')) { - $zip = zip_open($file['tmp_name']); - - if (!is_resource($zip)) { - // zip_open cannot open file: something is wrong - Minz_Log::warning('Zip archive cannot be imported. Error code: ' . $zip); - Minz_Request::bad(_t('feedback.import_export.zip_error'), - array('c' => 'importExport', 'a' => 'index')); - } - - while (($zipfile = zip_read($zip)) !== false) { - if (!is_resource($zipfile)) { - // zip_entry() can also return an error code! - Minz_Log::warning('Zip file cannot be imported. Error code: ' . $zipfile); - } else { - $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); - if ($type_file !== 'unknown') { - $list_files[$type_zipfile][] = zip_entry_read( - $zipfile, - zip_entry_filesize($zipfile) - ); - } - } - } - - zip_close($zip); - } elseif ($type_file === 'zip') { - // Zip extension is not loaded - Minz_Request::bad(_t('feedback.import_export.no_zip_extension'), - array('c' => 'importExport', 'a' => 'index')); - } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents($file['tmp_name']); - } - - // Import file contents. - // OPML first(so categories and feeds are imported) - // Starred articles then so the "favourite" status is already set - // And finally all other files. - $error = false; - foreach ($list_files['opml'] as $opml_file) { - $error = $this->importOpml($opml_file); - } - foreach ($list_files['json_starred'] as $article_file) { - $error = $this->importJson($article_file, true); - } - foreach ($list_files['json_feed'] as $article_file) { - $error = $this->importJson($article_file); - } - - // And finally, we get import status and redirect to the home page - Minz_Session::_param('actualize_feeds', true); - $content_notif = $error === true ? _t('feedback.import_export.feeds_imported_with_errors') : - _t('feedback.import_export.feeds_imported'); - Minz_Request::good($content_notif); - } - - /** - * This method tries to guess the file type based on its name. - * - * Itis a *very* basic guess file type function. Only based on filename. - * That's could be improved but should be enough for what we have to do. - */ - private function guessFileType($filename) { - if (substr_compare($filename, '.zip', -4) === 0) { - return 'zip'; - } elseif (substr_compare($filename, '.opml', -5) === 0 || - substr_compare($filename, '.xml', -4) === 0) { - return 'opml'; - } elseif (substr_compare($filename, '.json', -5) === 0 && - strpos($filename, 'starred') !== false) { - return 'json_starred'; - } elseif (substr_compare($filename, '.json', -5) === 0) { - return 'json_feed'; - } else { - return 'unknown'; - } - } - - /** - * This method parses and imports an OPML file. - * - * @param string $opml_file the OPML file content. - * @return boolean true if an error occured, false else. - */ - private function importOpml($opml_file) { - $opml_array = array(); - try { - $opml_array = libopml_parse_string($opml_file, false); - } catch (LibOPML_Exception $e) { - Minz_Log::warning($e->getMessage()); - return true; - } - - $this->catDAO->checkDefault(); - - return $this->addOpmlElements($opml_array['body']); - } - - /** - * This method imports an OPML file based on its body. - * - * @param array $opml_elements an OPML element (body or outline). - * @param string $parent_cat the name of the parent category. - * @return boolean true if an error occured, false else. - */ - private function addOpmlElements($opml_elements, $parent_cat = null) { - $error = false; - - $nb_feeds = count($this->feedDAO->listFeeds()); - $nb_cats = count($this->catDAO->listCategories(false)); - $limits = FreshRSS_Context::$system_conf->limits; - - foreach ($opml_elements as $elt) { - $is_error = false; - if (isset($elt['xmlUrl'])) { - // If xmlUrl exists, it means it is a feed - if ($nb_feeds >= $limits['max_feeds']) { - Minz_Log::warning(_t('feedback.sub.feed.over_max', - $limits['max_feeds'])); - $is_error = true; - continue; - } - - $is_error = $this->addFeedOpml($elt, $parent_cat); - if (!$is_error) { - $nb_feeds += 1; - } - } else { - // No xmlUrl? It should be a category! - $limit_reached = ($nb_cats >= $limits['max_categories']); - if ($limit_reached) { - Minz_Log::warning(_t('feedback.sub.category.over_max', - $limits['max_categories'])); - } - - $is_error = $this->addCategoryOpml($elt, $parent_cat, $limit_reached); - if (!$is_error) { - $nb_cats += 1; - } - } - - if (!$error && $is_error) { - // oops: there is at least one error! - $error = $is_error; - } - } - - return $error; - } - - /** - * This method imports an OPML feed element. - * - * @param array $feed_elt an OPML element (must be a feed element). - * @param string $parent_cat the name of the parent category. - * @return boolean true if an error occured, false else. - */ - private function addFeedOpml($feed_elt, $parent_cat) { - $default_cat = $this->catDAO->getDefault(); - if (is_null($parent_cat)) { - // This feed has no parent category so we get the default one - $parent_cat = $default_cat->name(); - } - - $cat = $this->catDAO->searchByName($parent_cat); - if (is_null($cat)) { - // If there is not $cat, it means parent category does not exist in - // database. - // If it happens, take the default category. - $cat = $default_cat; - } - - // We get different useful information - $url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']); - $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']); - $website = ''; - if (isset($feed_elt['htmlUrl'])) { - $website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']); - } - $description = ''; - if (isset($feed_elt['description'])) { - $description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description']); - } - - $error = false; - try { - // Create a Feed object and add it in DB - $feed = new FreshRSS_Feed($url); - $feed->_category($cat->id()); - $feed->_name($name); - $feed->_website($website); - $feed->_description($description); - - // Call the extension hook - $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); - if (!is_null($feed)) { - // addFeedObject checks if feed is already in DB so nothing else to - // check here - $id = $this->feedDAO->addFeedObject($feed); - $error = ($id === false); - } else { - $error = true; - } - } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::warning($e->getMessage()); - $error = true; - } - - return $error; - } - - /** - * This method imports an OPML category element. - * - * @param array $cat_elt an OPML element (must be a category element). - * @param string $parent_cat the name of the parent category. - * @param boolean $cat_limit_reached indicates if category limit has been reached. - * if yes, category is not added (but we try for feeds!) - * @return boolean true if an error occured, false else. - */ - private function addCategoryOpml($cat_elt, $parent_cat, $cat_limit_reached) { - // Create a new Category object - $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text'])); - - $error = true; - if (!$cat_limit_reached) { - $id = $this->catDAO->addCategoryObject($cat); - $error = ($id === false); - } - - if (isset($cat_elt['@outlines'])) { - // Our cat_elt contains more categories or more feeds, so we - // add them recursively. - // Note: FreshRSS does not support yet category arborescence - $res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name()); - if (!$error && $res) { - $error = true; - } - } - - return $error; - } - - /** - * This method import a JSON-based file (Google Reader format). - * - * @param string $article_file the JSON file content. - * @param boolean $starred true if articles from the file must be starred. - * @return boolean true if an error occured, false else. - */ - private function importJson($article_file, $starred = false) { - $article_object = json_decode($article_file, true); - if (is_null($article_object)) { - Minz_Log::warning('Try to import a non-JSON file'); - return true; - } - - $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; - - $google_compliant = strpos($article_object['id'], 'com.google') !== false; - - $error = false; - $article_to_feed = array(); - - $nb_feeds = count($this->feedDAO->listFeeds()); - $limits = FreshRSS_Context::$system_conf->limits; - - // First, we check feeds of articles are in DB (and add them if needed). - foreach ($article_object['items'] as $item) { - $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; - $feed = new FreshRSS_Feed($item['origin'][$key]); - $feed = $this->feedDAO->searchByUrl($feed->url()); - - if (is_null($feed)) { - // Feed does not exist in DB,we should to try to add it. - if ($nb_feeds >= $limits['max_feeds']) { - // Oops, no more place! - Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds'])); - } else { - $feed = $this->addFeedJson($item['origin'], $google_compliant); - } - - if (is_null($feed)) { - // Still null? It means something went wrong. - $error = true; - } else { - // Nice! Increase the counter. - $nb_feeds += 1; - } - } - - if (!is_null($feed)) { - $article_to_feed[$item['id']] = $feed->id(); - } - } - - // Then, articles are imported. - $this->entryDAO->beginTransaction(); - foreach ($article_object['items'] as $item) { - if (!isset($article_to_feed[$item['id']])) { - // Related feed does not exist for this entry, do nothing. - continue; - } - - $feed_id = $article_to_feed[$item['id']]; - $author = isset($item['author']) ? $item['author'] : ''; - $key_content = ($google_compliant && !isset($item['content'])) ? - 'summary' : 'content'; - $tags = $item['categories']; - if ($google_compliant) { - // Remove tags containing "/state/com.google" which are useless. - $tags = array_filter($tags, function($var) { - return strpos($var, '/state/com.google') === false; - }); - } - - $entry = new FreshRSS_Entry( - $feed_id, $item['id'], $item['title'], $author, - $item[$key_content]['content'], $item['alternate'][0]['href'], - $item['published'], $is_read, $starred - ); - $entry->_id(min(time(), $entry->date(true)) . uSecString()); - $entry->_tags($tags); - - $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); - if (is_null($entry)) { - // An extension has returned a null value, there is nothing to insert. - continue; - } - - $values = $entry->toArray(); - $id = $this->entryDAO->addEntry($values); - - if (!$error && ($id === false)) { - $error = true; - } - } - $this->entryDAO->commit(); - - return $error; - } - - /** - * This method import a JSON-based feed (Google Reader format). - * - * @param array $origin represents a feed. - * @param boolean $google_compliant takes care of some specific values if true. - * @return FreshRSS_Feed if feed is in database at the end of the process, - * else null. - */ - private function addFeedJson($origin, $google_compliant) { - $default_cat = $this->catDAO->getDefault(); - - $return = null; - $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; - $url = $origin[$key]; - $name = $origin['title']; - $website = $origin['htmlUrl']; - - try { - // Create a Feed object and add it in database. - $feed = new FreshRSS_Feed($url); - $feed->_category($default_cat->id()); - $feed->_name($name); - $feed->_website($website); - - // Call the extension hook - $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); - if (!is_null($feed)) { - // addFeedObject checks if feed is already in DB so nothing else to - // check here. - $id = $this->feedDAO->addFeedObject($feed); - - if ($id !== false) { - $feed->_id($id); - $return = $feed; - } - } - } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::warning($e->getMessage()); - } - - return $return; - } - - /** - * This action handles export action. - * - * This action must be reached by a POST request. - * - * Parameters are: - * - export_opml (default: false) - * - export_starred (default: false) - * - export_feeds (default: array()) a list of feed ids - */ - public function exportAction() { - if (!Minz_Request::isPost()) { - Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); - } - - $this->view->_useLayout(false); - - $export_opml = Minz_Request::param('export_opml', false); - $export_starred = Minz_Request::param('export_starred', false); - $export_feeds = Minz_Request::param('export_feeds', array()); - - $export_files = array(); - if ($export_opml) { - $export_files['feeds.opml'] = $this->generateOpml(); - } - - if ($export_starred) { - $export_files['starred.json'] = $this->generateEntries('starred'); - } - - foreach ($export_feeds as $feed_id) { - $feed = $this->feedDAO->searchById($feed_id); - if ($feed) { - $filename = 'feed_' . $feed->category() . '_' - . $feed->id() . '.json'; - $export_files[$filename] = $this->generateEntries('feed', $feed); - } - } - - $nb_files = count($export_files); - if ($nb_files > 1) { - // If there are more than 1 file to export, we need a zip archive. - try { - $this->exportZip($export_files); - } catch (Exception $e) { - # Oops, there is no Zip extension! - Minz_Request::bad(_t('feedback.import_export.export_no_zip_extension'), - array('c' => 'importExport', 'a' => 'index')); - } - } elseif ($nb_files === 1) { - // Only one file? Guess its type and export it. - $filename = key($export_files); - $type = $this->guessFileType($filename); - $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type); - } else { - // Nothing to do... - Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); - } - } - - /** - * This method returns the OPML file based on user subscriptions. - * - * @return string the OPML file content. - */ - private function generateOpml() { - $list = array(); - foreach ($this->catDAO->listCategories() as $key => $cat) { - $list[$key]['name'] = $cat->name(); - $list[$key]['feeds'] = $this->feedDAO->listByCategory($cat->id()); - } - - $this->view->categories = $list; - return $this->view->helperToString('export/opml'); - } - - /** - * This method returns a JSON file content. - * - * @param string $type must be "starred" or "feed" - * @param FreshRSS_Feed $feed feed of which we want to get entries. - * @return string the JSON file content. - */ - private function generateEntries($type, $feed = NULL) { - $this->view->categories = $this->catDAO->listCategories(); - - if ($type == 'starred') { - $this->view->list_title = _t('sub.import_export.starred_list'); - $this->view->type = 'starred'; - $unread_fav = $this->entryDAO->countUnreadReadFavorites(); - $this->view->entries = $this->entryDAO->listWhere( - 's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all'] - ); - } elseif ($type == 'feed' && !is_null($feed)) { - $this->view->list_title = _t('sub.import_export.feed_list', $feed->name()); - $this->view->type = 'feed/' . $feed->id(); - $this->view->entries = $this->entryDAO->listWhere( - 'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC', - FreshRSS_Context::$user_conf->posts_per_page - ); - $this->view->feed = $feed; - } - - return $this->view->helperToString('export/articles'); - } - - /** - * This method zips a list of files and returns it by HTTP. - * - * @param array $files list of files where key is filename and value the content. - * @throws Exception if Zip extension is not loaded. - */ - private function exportZip($files) { - if (!extension_loaded('zip')) { - throw new Exception(); - } - - // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly - $zip_file = tempnam('tmp', 'zip'); - $zip = new ZipArchive(); - $zip->open($zip_file, ZipArchive::OVERWRITE); - - foreach ($files as $filename => $content) { - $zip->addFromString($filename, $content); - } - - // Close and send to user - $zip->close(); - header('Content-Type: application/zip'); - header('Content-Length: ' . filesize($zip_file)); - header('Content-Disposition: attachment; filename="freshrss_export.zip"'); - readfile($zip_file); - unlink($zip_file); - } - - /** - * This method returns a single file (OPML or JSON) by HTTP. - * - * @param string $filename - * @param string $content - * @param string $type the file type (opml, json_feed or json_starred). - * If equals to unknown, nothing happens. - */ - private function exportFile($filename, $content, $type) { - if ($type === 'unknown') { - return; - } - - $content_type = ''; - if ($type === 'opml') { - $content_type = "text/opml"; - } elseif ($type === 'json_feed' || $type === 'json_starred') { - $content_type = "text/json"; - } - - header('Content-Type: ' . $content_type . '; charset=utf-8'); - header('Content-disposition: attachment; filename=' . $filename); - print($content); - } -} diff --git a/sources/app/Controllers/indexController.php b/sources/app/Controllers/indexController.php deleted file mode 100755 index 7e62672..0000000 --- a/sources/app/Controllers/indexController.php +++ /dev/null @@ -1,240 +0,0 @@ -view_mode; - Minz_Request::forward(array( - 'c' => 'index', - 'a' => $prefered_output - )); - } - - /** - * This action displays the normal view of FreshRSS. - */ - public function normalAction() { - $allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous; - if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) { - Minz_Request::forward(array('c' => 'auth', 'a' => 'login')); - return; - } - - try { - $this->updateContext(); - } catch (FreshRSS_Context_Exception $e) { - Minz_Error::error(404); - } - - $this->view->callbackBeforeContent = function() { - try { - $entries = $this->listEntriesByContext(); - - $nb_entries = count($entries); - if ($nb_entries > FreshRSS_Context::$number) { - // We have more elements for pagination - $last_entry = array_pop($entries); - FreshRSS_Context::$next_id = $last_entry->id(); - } - - $first_entry = $nb_entries > 0 ? $entries[0] : null; - FreshRSS_Context::$id_max = $first_entry === null ? - (time() - 1) . '000000' : - $first_entry->id(); - if (FreshRSS_Context::$order === 'ASC') { - // In this case we do not know but we guess id_max - $id_max = (time() - 1) . '000000'; - if (strcmp($id_max, FreshRSS_Context::$id_max) > 0) { - FreshRSS_Context::$id_max = $id_max; - } - } - - $this->view->entries = $entries; - } catch (FreshRSS_EntriesGetter_Exception $e) { - Minz_Log::notice($e->getMessage()); - Minz_Error::error(404); - } - - $this->view->categories = FreshRSS_Context::$categories; - - $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); - $title = FreshRSS_Context::$name; - if (FreshRSS_Context::$get_unread > 0) { - $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title; - } - Minz_View::prependTitle($title . ' · '); - }; - } - - /** - * This action displays the reader view of FreshRSS. - * - * @todo: change this view into specific CSS rules? - */ - public function readerAction() { - $this->normalAction(); - } - - /** - * This action displays the global view of FreshRSS. - */ - public function globalAction() { - $allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous; - if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) { - Minz_Request::forward(array('c' => 'auth', 'a' => 'login')); - return; - } - - Minz_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); - - try { - $this->updateContext(); - } catch (FreshRSS_Context_Exception $e) { - Minz_Error::error(404); - } - - $this->view->categories = FreshRSS_Context::$categories; - - $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); - $title = _t('index.feed.title_global'); - if (FreshRSS_Context::$get_unread > 0) { - $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title; - } - Minz_View::prependTitle($title . ' · '); - } - - /** - * This action displays the RSS feed of FreshRSS. - */ - public function rssAction() { - $allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous; - $token = FreshRSS_Context::$user_conf->token; - $token_param = Minz_Request::param('token', ''); - $token_is_ok = ($token != '' && $token === $token_param); - - // Check if user has access. - if (!FreshRSS_Auth::hasAccess() && - !$allow_anonymous && - !$token_is_ok) { - Minz_Error::error(403); - } - - try { - $this->updateContext(); - } catch (FreshRSS_Context_Exception $e) { - Minz_Error::error(404); - } - - try { - $this->view->entries = $this->listEntriesByContext(); - } catch (FreshRSS_EntriesGetter_Exception $e) { - Minz_Log::notice($e->getMessage()); - Minz_Error::error(404); - } - - // No layout for RSS output. - $this->view->url = empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']; - $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); - $this->view->_useLayout(false); - header('Content-Type: application/rss+xml; charset=utf-8'); - } - - /** - * This action updates the Context object by using request parameters. - * - * Parameters are: - * - state (default: conf->default_view) - * - search (default: empty string) - * - order (default: conf->sort_order) - * - nb (default: conf->posts_per_page) - * - next (default: empty string) - */ - private function updateContext() { - // Update number of read / unread variables. - $entryDAO = FreshRSS_Factory::createEntryDao(); - FreshRSS_Context::$total_starred = $entryDAO->countUnreadReadFavorites(); - FreshRSS_Context::$total_unread = FreshRSS_CategoryDAO::CountUnreads( - FreshRSS_Context::$categories, 1 - ); - - FreshRSS_Context::_get(Minz_Request::param('get', 'a')); - - FreshRSS_Context::$state = Minz_Request::param( - 'state', FreshRSS_Context::$user_conf->default_state - ); - $state_forced_by_user = Minz_Request::param('state', false) !== false; - if (FreshRSS_Context::$user_conf->default_view === 'adaptive' && - FreshRSS_Context::$get_unread <= 0 && - !FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ) && - !$state_forced_by_user) { - FreshRSS_Context::$state |= FreshRSS_Entry::STATE_READ; - } - - FreshRSS_Context::$search = new FreshRSS_Search(Minz_Request::param('search', '')); - FreshRSS_Context::$order = Minz_Request::param( - 'order', FreshRSS_Context::$user_conf->sort_order - ); - FreshRSS_Context::$number = Minz_Request::param( - 'nb', FreshRSS_Context::$user_conf->posts_per_page - ); - FreshRSS_Context::$first_id = Minz_Request::param('next', ''); - } - - /** - * This method returns a list of entries based on the Context object. - */ - private function listEntriesByContext() { - $entryDAO = FreshRSS_Factory::createEntryDao(); - - $get = FreshRSS_Context::currentGet(true); - if (count($get) > 1) { - $type = $get[0]; - $id = $get[1]; - } else { - $type = $get; - $id = ''; - } - - return $entryDAO->listWhere( - $type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order, - FreshRSS_Context::$number + 1, FreshRSS_Context::$first_id, - FreshRSS_Context::$search - ); - } - - /** - * This action displays the about page of FreshRSS. - */ - public function aboutAction() { - Minz_View::prependTitle(_t('index.about.title') . ' · '); - } - - /** - * This action displays logs of FreshRSS for the current user. - */ - public function logsAction() { - if (!FreshRSS_Auth::hasAccess()) { - Minz_Error::error(403); - } - - Minz_View::prependTitle(_t('index.log.title') . ' · '); - - if (Minz_Request::isPost()) { - FreshRSS_LogDAO::truncate(); - } - - $logs = FreshRSS_LogDAO::lines(); //TODO: ask only the necessary lines - - //gestion pagination - $page = Minz_Request::param('page', 1); - $this->view->logsPaginator = new Minz_Paginator($logs); - $this->view->logsPaginator->_nbItemsPerPage(50); - $this->view->logsPaginator->_currentPage($page); - } -} diff --git a/sources/app/Controllers/javascriptController.php b/sources/app/Controllers/javascriptController.php deleted file mode 100755 index 00a7b5c..0000000 --- a/sources/app/Controllers/javascriptController.php +++ /dev/null @@ -1,54 +0,0 @@ -view->_useLayout(false); - } - - public function actualizeAction() { - header('Content-Type: application/json; charset=UTF-8'); - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default); - } - - public function nbUnreadsPerFeedAction() { - header('Content-Type: application/json; charset=UTF-8'); - $catDAO = new FreshRSS_CategoryDAO(); - $this->view->categories = $catDAO->listCategories(true, false); - } - - //For Web-form login - public function nonceAction() { - header('Content-Type: application/json; charset=UTF-8'); - header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T')); - header('Expires: 0'); - header('Cache-Control: private, no-cache, no-store, must-revalidate'); - header('Pragma: no-cache'); - - $user = isset($_GET['user']) ? $_GET['user'] : ''; - if (ctype_alnum($user)) { - try { - $salt = FreshRSS_Context::$system_conf->salt; - $conf = get_user_configuration($user); - $s = $conf->passwordHash; - if (strlen($s) >= 60) { - $this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". - $this->view->nonce = sha1($salt . uniqid(mt_rand(), true)); - Minz_Session::_param('nonce', $this->view->nonce); - return; //Success - } - } catch (Minz_Exception $me) { - Minz_Log::warning('Nonce failure: ' . $me->getMessage()); - } - } else { - Minz_Log::notice('Nonce failure due to invalid username!'); - } - //Failure: Return random data. - $this->view->salt1 = sprintf('$2a$%02d$', FreshRSS_user_Controller::BCRYPT_COST); - $alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for ($i = 22; $i > 0; $i--) { - $this->view->salt1 .= $alphabet[rand(0, 63)]; - } - $this->view->nonce = sha1(rand()); - } -} diff --git a/sources/app/Controllers/statsController.php b/sources/app/Controllers/statsController.php deleted file mode 100755 index 4a597ae..0000000 --- a/sources/app/Controllers/statsController.php +++ /dev/null @@ -1,128 +0,0 @@ -view->repartition = $statsDAO->calculateEntryRepartition(); - $this->view->count = $statsDAO->calculateEntryCount(); - $this->view->average = $statsDAO->calculateEntryAverage(); - $this->view->feedByCategory = $statsDAO->calculateFeedByCategory(); - $this->view->entryByCategory = $statsDAO->calculateEntryByCategory(); - $this->view->topFeed = $statsDAO->calculateTopFeed(); - } - - /** - * This action handles the idle feed statistic page. - * - * It displays the list of idle feed for different period. The supported - * periods are: - * - last year - * - last 6 months - * - last 3 months - * - last month - * - last week - */ - public function idleAction() { - $statsDAO = FreshRSS_Factory::createStatsDAO(); - $feeds = $statsDAO->calculateFeedLastDate(); - $idleFeeds = array( - 'last_year' => array(), - 'last_6_month' => array(), - 'last_3_month' => array(), - 'last_month' => array(), - 'last_week' => array(), - ); - $now = new \DateTime(); - $feedDate = clone $now; - $lastWeek = clone $now; - $lastWeek->modify('-1 week'); - $lastMonth = clone $now; - $lastMonth->modify('-1 month'); - $last3Month = clone $now; - $last3Month->modify('-3 month'); - $last6Month = clone $now; - $last6Month->modify('-6 month'); - $lastYear = clone $now; - $lastYear->modify('-1 year'); - - foreach ($feeds as $feed) { - $feedDate->setTimestamp($feed['last_date']); - if ($feedDate >= $lastWeek) { - continue; - } - if ($feedDate < $lastYear) { - $idleFeeds['last_year'][] = $feed; - } elseif ($feedDate < $last6Month) { - $idleFeeds['last_6_month'][] = $feed; - } elseif ($feedDate < $last3Month) { - $idleFeeds['last_3_month'][] = $feed; - } elseif ($feedDate < $lastMonth) { - $idleFeeds['last_month'][] = $feed; - } elseif ($feedDate < $lastWeek) { - $idleFeeds['last_week'][] = $feed; - } - } - - $this->view->idleFeeds = $idleFeeds; - } - - /** - * This action handles the article repartition statistic page. - * - * It displays the number of article and the average of article for the - * following periods: - * - hour of the day - * - day of the week - * - month - * - * @todo verify that the metrics used here make some sense. Especially - * for the average. - */ - public function repartitionAction() { - $statsDAO = FreshRSS_Factory::createStatsDAO(); - $categoryDAO = new FreshRSS_CategoryDAO(); - $feedDAO = FreshRSS_Factory::createFeedDao(); - Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); - $id = Minz_Request::param('id', null); - $this->view->categories = $categoryDAO->listCategories(); - $this->view->feed = $feedDAO->searchById($id); - $this->view->days = $statsDAO->getDays(); - $this->view->months = $statsDAO->getMonths(); - $this->view->repartition = $statsDAO->calculateEntryRepartitionPerFeed($id); - $this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id); - $this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id); - $this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id); - $this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id); - $this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id); - $this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id); - } -} diff --git a/sources/app/Controllers/subscriptionController.php b/sources/app/Controllers/subscriptionController.php deleted file mode 100755 index 03d3ee1..0000000 --- a/sources/app/Controllers/subscriptionController.php +++ /dev/null @@ -1,116 +0,0 @@ -checkDefault(); - $this->view->categories = $catDAO->listCategories(false); - $this->view->default_category = $catDAO->getDefault(); - } - - /** - * This action handles the main subscription page - * - * It displays categories and associated feeds. - */ - public function indexAction() { - Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' . - @filemtime(PUBLIC_PATH . '/scripts/category.js'))); - Minz_View::prependTitle(_t('sub.title') . ' · '); - - $id = Minz_Request::param('id'); - if ($id !== false) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->view->feed = $feedDAO->searchById($id); - } - } - - /** - * This action handles the feed configuration page. - * - * It displays the feed configuration page. - * If this action is reached through a POST request, it stores all new - * configuraiton values then sends a notification to the user. - * - * The options available on the page are: - * - name - * - description - * - website URL - * - feed URL - * - category id (default: default category id) - * - CSS path to article on website - * - display in main stream (default: 0) - * - HTTP authentication - * - number of article to retain (default: -2) - * - refresh frequency (default: -2) - * Default values are empty strings unless specified. - */ - public function feedAction() { - if (Minz_Request::param('ajax')) { - $this->view->_useLayout(false); - } - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->view->feeds = $feedDAO->listFeeds(); - - $id = Minz_Request::param('id'); - if ($id === false || !isset($this->view->feeds[$id])) { - Minz_Error::error(404); - return; - } - - $this->view->feed = $this->view->feeds[$id]; - - Minz_View::prependTitle(_t('sub.title.feed_management') . ' · ' . $this->view->feed->name() . ' · '); - - if (Minz_Request::isPost()) { - $user = trim(Minz_Request::param('http_user_feed' . $id, '')); - $pass = Minz_Request::param('http_pass_feed' . $id, ''); - - $httpAuth = ''; - if ($user != '' && $pass != '') { //TODO: Sanitize - $httpAuth = $user . ':' . $pass; - } - - $cat = intval(Minz_Request::param('category', 0)); - - $values = array( - 'name' => Minz_Request::param('name', ''), - 'description' => sanitizeHTML(Minz_Request::param('description', '', true)), - 'website' => Minz_Request::param('website', ''), - 'url' => Minz_Request::param('url', ''), - 'category' => $cat, - 'pathEntries' => Minz_Request::param('path_entries', ''), - 'priority' => intval(Minz_Request::param('priority', 0)), - 'httpAuth' => $httpAuth, - 'keep_history' => intval(Minz_Request::param('keep_history', -2)), - 'ttl' => intval(Minz_Request::param('ttl', -2)), - ); - - invalidateHttpCache(); - - $url_redirect = array('c' => 'subscription', 'params' => array('id' => $id)); - if ($feedDAO->updateFeed($id, $values) !== false) { - $this->view->feed->_category($cat); - $this->view->feed->faviconPrepare(); - - Minz_Request::good(_t('feedback.sub.feed.updated'), $url_redirect); - } else { - Minz_Request::bad(_t('feedback.sub.feed.error'), $url_redirect); - } - } - } -} diff --git a/sources/app/Controllers/updateController.php b/sources/app/Controllers/updateController.php deleted file mode 100755 index 64c984b..0000000 --- a/sources/app/Controllers/updateController.php +++ /dev/null @@ -1,161 +0,0 @@ -view->update_to_apply = false; - $this->view->last_update_time = 'unknown'; - $timestamp = @filemtime(join_path(DATA_PATH, 'last_update.txt')); - if ($timestamp !== false) { - $this->view->last_update_time = timestamptodate($timestamp); - } - } - - public function indexAction() { - Minz_View::prependTitle(_t('admin.update.title') . ' · '); - - if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.update.file_is_nok', FRESHRSS_PATH) - ); - } elseif (file_exists(UPDATE_FILENAME)) { - // There is an update file to apply! - $version = @file_get_contents(join_path(DATA_PATH, 'last_update.txt')); - if (empty($version)) { - $version = 'unknown'; - } - $this->view->update_to_apply = true; - $this->view->message = array( - 'status' => 'good', - 'title' => _t('gen.short.ok'), - 'body' => _t('feedback.update.can_apply', $version) - ); - } - } - - public function checkAction() { - $this->view->change_view('update', 'index'); - - if (file_exists(UPDATE_FILENAME)) { - // There is already an update file to apply: we don't need to check - // the webserver! - // Or if already check during the last hour, do nothing. - Minz_Request::forward(array('c' => 'update'), true); - - return; - } - - $auto_update_url = FreshRSS_Context::$system_conf->auto_update_url . '?v=' . FRESHRSS_VERSION; - $c = curl_init($auto_update_url); - curl_setopt($c, CURLOPT_RETURNTRANSFER, true); - curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2); - $result = curl_exec($c); - $c_status = curl_getinfo($c, CURLINFO_HTTP_CODE); - $c_error = curl_error($c); - curl_close($c); - - if ($c_status !== 200) { - Minz_Log::warning( - 'Error during update (HTTP code ' . $c_status . '): ' . $c_error - ); - - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.update.server_not_found', $auto_update_url) - ); - return; - } - - $res_array = explode("\n", $result, 2); - $status = $res_array[0]; - if (strpos($status, 'UPDATE') !== 0) { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.update.none') - ); - - @touch(join_path(DATA_PATH, 'last_update.txt')); - - return; - } - - $script = $res_array[1]; - if (file_put_contents(UPDATE_FILENAME, $script) !== false) { - $version = explode(' ', $status, 2); - $version = $version[1]; - @file_put_contents(join_path(DATA_PATH, 'last_update.txt'), $version); - - Minz_Request::forward(array('c' => 'update'), true); - } else { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('gen.short.damn'), - 'body' => _t('feedback.update.error', 'Cannot save the update script') - ); - } - } - - public function applyAction() { - if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH)) { - Minz_Request::forward(array('c' => 'update'), true); - } - - require(UPDATE_FILENAME); - - if (Minz_Request::param('post_conf', false)) { - $res = do_post_update(); - - Minz_ExtensionManager::callHook('post_update'); - - if ($res === true) { - @unlink(UPDATE_FILENAME); - @file_put_contents(join_path(DATA_PATH, 'last_update.txt'), ''); - Minz_Request::good(_t('feedback.update.finished')); - } else { - Minz_Request::bad(_t('feedback.update.error', $res), - array('c' => 'update', 'a' => 'index')); - } - } - - if (Minz_Request::isPost()) { - save_info_update(); - } - - if (!need_info_update()) { - $res = apply_update(); - - if ($res === true) { - Minz_Request::forward(array( - 'c' => 'update', - 'a' => 'apply', - 'params' => array('post_conf' => true) - ), true); - } else { - Minz_Request::bad(_t('feedback.update.error', $res), - array('c' => 'update', 'a' => 'index')); - } - } - } - - /** - * This action displays information about installation. - */ - public function checkInstallAction() { - Minz_View::prependTitle(_t('admin.check_install.title') . ' · '); - - $this->view->status_php = check_install_php(); - $this->view->status_files = check_install_files(); - $this->view->status_database = check_install_database(); - } -} diff --git a/sources/app/Controllers/userController.php b/sources/app/Controllers/userController.php deleted file mode 100755 index 1c7d621..0000000 --- a/sources/app/Controllers/userController.php +++ /dev/null @@ -1,275 +0,0 @@ - self::BCRYPT_COST)); - $passwordPlain = ''; - $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js - $ok &= ($passwordHash != ''); - FreshRSS_Context::$user_conf->passwordHash = $passwordHash; - } - Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash); - - $passwordPlain = Minz_Request::param('apiPasswordPlain', '', true); - if ($passwordPlain != '') { - if (!function_exists('password_hash')) { - include_once(LIB_PATH . '/password_compat.php'); - } - $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); - $passwordPlain = ''; - $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js - $ok &= ($passwordHash != ''); - FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash; - } - - // TODO: why do we need of hasAccess here? - if (FreshRSS_Auth::hasAccess('admin')) { - FreshRSS_Context::$user_conf->mail_login = Minz_Request::param('mail_login', '', true); - } - $email = FreshRSS_Context::$user_conf->mail_login; - Minz_Session::_param('mail', $email); - - $ok &= FreshRSS_Context::$user_conf->save(); - - if ($email != '') { - $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; - @unlink($personaFile); - $ok &= (file_put_contents($personaFile, Minz_Session::param('currentUser', '_')) !== false); - } - - if ($ok) { - Minz_Request::good(_t('feedback.profile.updated'), - array('c' => 'user', 'a' => 'profile')); - } else { - Minz_Request::bad(_t('feedback.profile.error'), - array('c' => 'user', 'a' => 'profile')); - } - } - } - - /** - * This action displays the user management page. - */ - public function manageAction() { - if (!FreshRSS_Auth::hasAccess('admin')) { - Minz_Error::error(403); - } - - Minz_View::prependTitle(_t('admin.user.title') . ' · '); - - // Get the correct current user. - $username = Minz_Request::param('u', Minz_Session::param('currentUser')); - if (!FreshRSS_UserDAO::exist($username)) { - $username = Minz_Session::param('currentUser'); - } - $this->view->current_user = $username; - - // Get information about the current user. - $entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user); - $this->view->nb_articles = $entryDAO->count(); - $this->view->size_user = $entryDAO->size(); - } - - /** - * This action creates a new user. - * - * Request parameters are: - * - new_user_language - * - new_user_name - * - new_user_passwordPlain - * - new_user_email - * - r (i.e. a redirection url, optional) - * - * @todo clean up this method. Idea: write a method to init a user with basic information. - * @todo handle r redirection in Minz_Request::forward directly? - */ - public function createAction() { - if (Minz_Request::isPost() && ( - FreshRSS_Auth::hasAccess('admin') || - !max_registrations_reached() - )) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - - $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language); - $languages = Minz_Translate::availableLanguages(); - if (!isset($languages[$new_user_language])) { - $new_user_language = FreshRSS_Context::$user_conf->language; - } - - $new_user_name = Minz_Request::param('new_user_name'); - $ok = ($new_user_name != '') && ctype_alnum($new_user_name); - - if ($ok) { - $default_user = FreshRSS_Context::$system_conf->default_user; - $ok &= (strcasecmp($new_user_name, $default_user) !== 0); //It is forbidden to alter the default user - - $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers())); //Not an existing user, case-insensitive - - $configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php'); - $ok &= !file_exists($configPath); - } - if ($ok) { - $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true); - $passwordHash = ''; - if ($passwordPlain != '') { - Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP - $_POST['new_user_passwordPlain'] = ''; - if (!function_exists('password_hash')) { - include_once(LIB_PATH . '/password_compat.php'); - } - $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); - $passwordPlain = ''; - $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js - $ok &= ($passwordHash != ''); - } - if (empty($passwordHash)) { - $passwordHash = ''; - } - - $new_user_email = filter_var($_POST['new_user_email'], FILTER_VALIDATE_EMAIL); - if (empty($new_user_email)) { - $new_user_email = ''; - } else { - $personaFile = join_path(DATA_PATH, 'persona', $new_user_email . '.txt'); - @unlink($personaFile); - $ok &= (file_put_contents($personaFile, $new_user_name) !== false); - } - } - if ($ok) { - mkdir(join_path(DATA_PATH, 'users', $new_user_name)); - $config_array = array( - 'language' => $new_user_language, - 'passwordHash' => $passwordHash, - 'mail_login' => $new_user_email, - ); - $ok &= (file_put_contents($configPath, "createUser($new_user_name); - } - invalidateHttpCache(); - - $notif = array( - 'type' => $ok ? 'good' : 'bad', - 'content' => _t('feedback.user.created' . (!$ok ? '.error' : ''), $new_user_name) - ); - Minz_Session::_param('notification', $notif); - } - - $redirect_url = urldecode(Minz_Request::param('r', false, true)); - if (!$redirect_url) { - $redirect_url = array('c' => 'user', 'a' => 'manage'); - } - Minz_Request::forward($redirect_url, true); - } - - /** - * This action delete an existing user. - * - * Request parameter is: - * - username - * - * @todo clean up this method. Idea: create a User->clean() method. - */ - public function deleteAction() { - $username = Minz_Request::param('username'); - $redirect_url = urldecode(Minz_Request::param('r', false, true)); - if (!$redirect_url) { - $redirect_url = array('c' => 'user', 'a' => 'manage'); - } - - $self_deletion = Minz_Session::param('currentUser', '_') === $username; - - if (Minz_Request::isPost() && ( - FreshRSS_Auth::hasAccess('admin') || - $self_deletion - )) { - $db = FreshRSS_Context::$system_conf->db; - require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - - $ok = ctype_alnum($username); - $user_data = join_path(DATA_PATH, 'users', $username); - - if ($ok) { - $default_user = FreshRSS_Context::$system_conf->default_user; - $ok &= (strcasecmp($username, $default_user) !== 0); //It is forbidden to delete the default user - } - if ($ok && $self_deletion) { - // We check the password if it's a self-destruction - $nonce = Minz_Session::param('nonce'); - $challenge = Minz_Request::param('challenge', ''); - - $ok &= FreshRSS_FormAuth::checkCredentials( - $username, FreshRSS_Context::$user_conf->passwordHash, - $nonce, $challenge - ); - } - if ($ok) { - $ok &= is_dir($user_data); - } - if ($ok) { - $userDAO = new FreshRSS_UserDAO(); - $ok &= $userDAO->deleteUser($username); - $ok &= recursive_unlink($user_data); - //TODO: delete Persona file - } - if ($ok && $self_deletion) { - FreshRSS_Auth::removeAccess(); - $redirect_url = array('c' => 'index', 'a' => 'index'); - } - invalidateHttpCache(); - - $notif = array( - 'type' => $ok ? 'good' : 'bad', - 'content' => _t('feedback.user.deleted' . (!$ok ? '.error' : ''), $username) - ); - Minz_Session::_param('notification', $notif); - } - - Minz_Request::forward($redirect_url, true); - } -} diff --git a/sources/app/Exceptions/BadUrlException.php b/sources/app/Exceptions/BadUrlException.php deleted file mode 100755 index d2509e4..0000000 --- a/sources/app/Exceptions/BadUrlException.php +++ /dev/null @@ -1,9 +0,0 @@ -_configurationSetter($configuration_setter); - - // Load list of extensions and enable the "system" ones. - Minz_ExtensionManager::init(); - - // Auth has to be initialized before using currentUser session parameter - // because it's this part which create this parameter. - self::initAuth(); - - // Then, register the user configuration and use the configuration setter - // created above. - $current_user = Minz_Session::param('currentUser', '_'); - Minz_Configuration::register('user', - join_path(USERS_PATH, $current_user, 'config.php'), - join_path(USERS_PATH, '_', 'config.default.php'), - $configuration_setter); - - // Finish to initialize the other FreshRSS / Minz components. - FreshRSS_Context::init(); - self::initI18n(); - self::loadNotifications(); - // Enable extensions for the current (logged) user. - if (FreshRSS_Auth::hasAccess()) { - $ext_list = FreshRSS_Context::$user_conf->extensions_enabled; - Minz_ExtensionManager::enableByList($ext_list); - } - } - - private static function initAuth() { - FreshRSS_Auth::init(); - if (Minz_Request::isPost() && !is_referer_from_same_domain()) { - // Basic protection against XSRF attacks - FreshRSS_Auth::removeAccess(); - $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; - Minz_Translate::init('en'); //TODO: Better choice of fallback language - Minz_Error::error( - 403, - array('error' => array( - _t('feedback.access.denied'), - ' [HTTP_REFERER=' . htmlspecialchars($http_referer) . ']' - )) - ); - } - } - - private static function initI18n() { - Minz_Session::_param('language', FreshRSS_Context::$user_conf->language); - Minz_Translate::init(FreshRSS_Context::$user_conf->language); - } - - public static function loadStylesAndScripts() { - $theme = FreshRSS_Themes::load(FreshRSS_Context::$user_conf->theme); - if ($theme) { - foreach($theme['files'] as $file) { - if ($file[0] === '_') { - $theme_id = 'base-theme'; - $filename = substr($file, 1); - } else { - $theme_id = $theme['id']; - $filename = $file; - } - $filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename); - $url = '/themes/' . $theme_id . '/' . $filename . '?' . $filetime; - header('Link: <' . Minz_Url::display($url, '', 'root') . '>;rel=preload', false); //HTTP2 - Minz_View::appendStyle(Minz_Url::display($url)); - } - } - - Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js'))); - Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); - Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); - - if (FreshRSS_Context::$system_conf->auth_type === 'persona') { - // TODO move it in a plugin - // Needed for login AND logout with Persona. - Minz_View::appendScript('https://login.persona.org/include.js'); - $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/persona.js'); - Minz_View::appendScript(Minz_Url::display('/scripts/persona.js?' . $file_mtime)); - } - } - - private static function loadNotifications() { - $notif = Minz_Session::param('notification'); - if ($notif) { - Minz_View::_param('notification', $notif); - Minz_Session::_param('notification'); - } - } - - public static function preLayout() { - switch (Minz_Request::controllerName()) { - case 'index': - header("Content-Security-Policy: default-src 'self'; child-src *; frame-src *; img-src * data:; media-src *"); - break; - case 'stats': - header("Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'"); - break; - default: - header("Content-Security-Policy: default-src 'self'"); - break; - } - header("X-Content-Type-Options: nosniff"); - - FreshRSS_Share::load(join_path(DATA_PATH, 'shares.php')); - self::loadStylesAndScripts(); - } -} diff --git a/sources/app/Models/Auth.php b/sources/app/Models/Auth.php deleted file mode 100755 index 4e7a719..0000000 --- a/sources/app/Models/Auth.php +++ /dev/null @@ -1,254 +0,0 @@ -default_user; - Minz_Session::_param('currentUser', $current_user); - } - - if (self::$login_ok) { - self::giveAccess(); - } elseif (self::accessControl()) { - self::giveAccess(); - FreshRSS_UserDAO::touch($current_user); - } else { - // Be sure all accesses are removed! - self::removeAccess(); - } - } - - /** - * This method checks if user is allowed to connect. - * - * Required session parameters are also set in this method (such as - * currentUser). - * - * @return boolean true if user can be connected, false else. - */ - private static function accessControl() { - $conf = Minz_Configuration::get('system'); - $auth_type = $conf->auth_type; - switch ($auth_type) { - case 'form': - $credentials = FreshRSS_FormAuth::getCredentialsFromCookie(); - $current_user = ''; - if (isset($credentials[1])) { - $current_user = trim($credentials[0]); - Minz_Session::_param('currentUser', $current_user); - Minz_Session::_param('passwordHash', trim($credentials[1])); - } - return $current_user != ''; - case 'http_auth': - $current_user = httpAuthUser(); - $login_ok = $current_user != ''; - if ($login_ok) { - Minz_Session::_param('currentUser', $current_user); - } - return $login_ok; - case 'persona': - $email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL); - $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; - if (($current_user = @file_get_contents($persona_file)) !== false) { - $current_user = trim($current_user); - Minz_Session::_param('currentUser', $current_user); - Minz_Session::_param('mail', $email); - return true; - } - return false; - case 'none': - return true; - default: - // TODO load extension - return false; - } - } - - /** - * Gives access to the current user. - */ - public static function giveAccess() { - $current_user = Minz_Session::param('currentUser'); - $user_conf = get_user_configuration($current_user); - $system_conf = Minz_Configuration::get('system'); - - switch ($system_conf->auth_type) { - case 'form': - self::$login_ok = Minz_Session::param('passwordHash') === $user_conf->passwordHash; - break; - case 'http_auth': - self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0; - break; - case 'persona': - self::$login_ok = strcasecmp(Minz_Session::param('mail'), $user_conf->mail_login) === 0; - break; - case 'none': - self::$login_ok = true; - break; - default: - // TODO: extensions - self::$login_ok = false; - } - - Minz_Session::_param('loginOk', self::$login_ok); - } - - /** - * Returns if current user has access to the given scope. - * - * @param string $scope general (default) or admin - * @return boolean true if user has corresponding access, false else. - */ - public static function hasAccess($scope = 'general') { - $conf = Minz_Configuration::get('system'); - $default_user = $conf->default_user; - $ok = self::$login_ok; - switch ($scope) { - case 'general': - break; - case 'admin': - $ok &= Minz_Session::param('currentUser') === $default_user; - break; - default: - $ok = false; - } - return $ok; - } - - /** - * Removes all accesses for the current user. - */ - public static function removeAccess() { - Minz_Session::_param('loginOk'); - self::$login_ok = false; - $conf = Minz_Configuration::get('system'); - Minz_Session::_param('currentUser', $conf->default_user); - - switch ($conf->auth_type) { - case 'form': - Minz_Session::_param('passwordHash'); - FreshRSS_FormAuth::deleteCookie(); - break; - case 'persona': - Minz_Session::_param('mail'); - break; - case 'http_auth': - case 'none': - // Nothing to do... - break; - default: - // TODO: extensions - } - } - - /** - * Return if authentication is enabled on this instance of FRSS. - */ - public static function accessNeedsLogin() { - $conf = Minz_Configuration::get('system'); - $auth_type = $conf->auth_type; - return $auth_type !== 'none'; - } - - /** - * Return if authentication requires a PHP action. - */ - public static function accessNeedsAction() { - $conf = Minz_Configuration::get('system'); - $auth_type = $conf->auth_type; - return $auth_type === 'form' || $auth_type === 'persona'; - } -} - - -class FreshRSS_FormAuth { - public static function checkCredentials($username, $hash, $nonce, $challenge) { - if (!ctype_alnum($username) || - !ctype_graph($challenge) || - !ctype_alnum($nonce)) { - Minz_Log::debug('Invalid credential parameters:' . - ' user=' . $username . - ' challenge=' . $challenge . - ' nonce=' . $nonce); - return false; - } - - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - - return password_verify($nonce . $hash, $challenge); - } - - public static function getCredentialsFromCookie() { - $token = Minz_Session::getLongTermCookie('FreshRSS_login'); - if (!ctype_alnum($token)) { - return array(); - } - - $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; - $mtime = @filemtime($token_file); - if ($mtime + 2629744 < time()) { - // Token has expired (> 1 month) or does not exist. - // TODO: 1 month -> use a configuration instead - @unlink($token_file); - return array(); - } - - $credentials = @file_get_contents($token_file); - return $credentials === false ? array() : explode("\t", $credentials, 2); - } - - public static function makeCookie($username, $password_hash) { - do { - $conf = Minz_Configuration::get('system'); - $token = sha1($conf->salt . $username . uniqid(mt_rand(), true)); - $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; - } while (file_exists($token_file)); - - if (@file_put_contents($token_file, $username . "\t" . $password_hash) === false) { - return false; - } - - $expire = time() + 2629744; //1 month //TODO: Use a configuration instead - Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); - return $token; - } - - public static function deleteCookie() { - $token = Minz_Session::getLongTermCookie('FreshRSS_login'); - Minz_Session::deleteLongTermCookie('FreshRSS_login'); - if (ctype_alnum($token)) { - @unlink(DATA_PATH . '/tokens/' . $token . '.txt'); - } - - if (rand(0, 10) === 1) { - self::purgeTokens(); - } - } - - public static function purgeTokens() { - $oldest = time() - 2629744; // 1 month // TODO: Use a configuration instead - foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) { - // $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7 - $extension = pathinfo($file_info->getFilename(), PATHINFO_EXTENSION); - if ($extension === 'txt' && $file_info->getMTime() < $oldest) { - @unlink($file_info->getPathname()); - } - } - } -} diff --git a/sources/app/Models/Category.php b/sources/app/Models/Category.php deleted file mode 100755 index 9a44a2d..0000000 --- a/sources/app/Models/Category.php +++ /dev/null @@ -1,80 +0,0 @@ -_name($name); - if (isset($feeds)) { - $this->_feeds($feeds); - $this->nbFeed = 0; - $this->nbNotRead = 0; - foreach ($feeds as $feed) { - $this->nbFeed++; - $this->nbNotRead += $feed->nbNotRead(); - $this->hasFeedsWithError |= $feed->inError(); - } - } - } - - public function id() { - return $this->id; - } - public function name() { - return $this->name; - } - public function nbFeed() { - if ($this->nbFeed < 0) { - $catDAO = new FreshRSS_CategoryDAO(); - $this->nbFeed = $catDAO->countFeed($this->id()); - } - - return $this->nbFeed; - } - public function nbNotRead() { - if ($this->nbNotRead < 0) { - $catDAO = new FreshRSS_CategoryDAO(); - $this->nbNotRead = $catDAO->countNotRead($this->id()); - } - - return $this->nbNotRead; - } - public function feeds() { - if ($this->feeds === null) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->feeds = $feedDAO->listByCategory($this->id()); - $this->nbFeed = 0; - $this->nbNotRead = 0; - foreach ($this->feeds as $feed) { - $this->nbFeed++; - $this->nbNotRead += $feed->nbNotRead(); - $this->hasFeedsWithError |= $feed->inError(); - } - } - - return $this->feeds; - } - - public function hasFeedsWithError() { - return $this->hasFeedsWithError; - } - - public function _id($value) { - $this->id = $value; - } - public function _name($value) { - $this->name = substr(trim($value), 0, 255); - } - public function _feeds($values) { - if (!is_array($values)) { - $values = array($values); - } - - $this->feeds = $values; - } -} diff --git a/sources/app/Models/CategoryDAO.php b/sources/app/Models/CategoryDAO.php deleted file mode 100755 index fc43155..0000000 --- a/sources/app/Models/CategoryDAO.php +++ /dev/null @@ -1,257 +0,0 @@ -prefix . 'category`(name) VALUES(?)'; - $stm = $this->bd->prepare($sql); - - $values = array( - substr($valuesTmp['name'], 0, 255), - ); - - if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error addCategory: ' . $info[2]); - return false; - } - } - - public function addCategoryObject($category) { - $cat = $this->searchByName($category->name()); - if (!$cat) { - // Category does not exist yet in DB so we add it before continue - $values = array( - 'name' => $category->name(), - ); - return $this->addCategory($values); - } - - return $cat->id(); - } - - public function updateCategory($id, $valuesTmp) { - $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array( - $valuesTmp['name'], - $id - ); - - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCategory: ' . $info[2]); - return false; - } - } - - public function deleteCategory($id) { - $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error deleteCategory: ' . $info[2]); - return false; - } - } - - public function searchById($id) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - $cat = self::daoToCategory($res); - - if (isset($cat[0])) { - return $cat[0]; - } else { - return null; - } - } - public function searchByName($name) { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; - $stm = $this->bd->prepare($sql); - - $values = array($name); - - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - $cat = self::daoToCategory($res); - - if (isset($cat[0])) { - return $cat[0]; - } else { - return null; - } - } - - public function listCategories($prePopulateFeeds = true, $details = false) { - if ($prePopulateFeeds) { - $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' - . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') - . 'FROM `' . $this->prefix . 'category` c ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' - . 'GROUP BY f.id, c_id ' - . 'ORDER BY c.name, f.name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); - } else { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC)); - } - } - - public function getDefault() { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1'; - $stm = $this->bd->prepare($sql); - - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - $cat = self::daoToCategory($res); - - if (isset($cat[0])) { - return $cat[0]; - } else { - return false; - } - } - public function checkDefault() { - $def_cat = $this->searchById(1); - - if ($def_cat == null) { - $cat = new FreshRSS_Category(_t('gen.short.default_category')); - $cat->_id(1); - - $values = array( - 'id' => $cat->id(), - 'name' => $cat->name(), - ); - - $this->addCategory($values); - } - } - - public function count() { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - return $res[0]['count']; - } - - public function countFeed($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - return $res[0]['count']; - } - - public function countNotRead($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0'; - $stm = $this->bd->prepare($sql); - $values = array($id); - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - return $res[0]['count']; - } - - public static function findFeed($categories, $feed_id) { - foreach ($categories as $category) { - foreach ($category->feeds() as $feed) { - if ($feed->id() === $feed_id) { - return $feed; - } - } - } - return null; - } - - public static function CountUnreads($categories, $minPriority = 0) { - $n = 0; - foreach ($categories as $category) { - foreach ($category->feeds() as $feed) { - if ($feed->priority() >= $minPriority) { - $n += $feed->nbNotRead(); - } - } - } - return $n; - } - - public static function daoToCategoryPrepopulated($listDAO) { - $list = array(); - - if (!is_array($listDAO)) { - $listDAO = array($listDAO); - } - - $previousLine = null; - $feedsDao = array(); - foreach ($listDAO as $line) { - if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) { - // End of the current category, we add it to the $list - $cat = new FreshRSS_Category( - $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) - ); - $cat->_id($previousLine['c_id']); - $list[$previousLine['c_id']] = $cat; - - $feedsDao = array(); //Prepare for next category - } - - $previousLine = $line; - $feedsDao[] = $line; - } - - // add the last category - if ($previousLine != null) { - $cat = new FreshRSS_Category( - $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) - ); - $cat->_id($previousLine['c_id']); - $list[$previousLine['c_id']] = $cat; - } - - return $list; - } - - public static function daoToCategory($listDAO) { - $list = array(); - - if (!is_array($listDAO)) { - $listDAO = array($listDAO); - } - - foreach ($listDAO as $key => $dao) { - $cat = new FreshRSS_Category( - $dao['name'] - ); - $cat->_id($dao['id']); - $list[$key] = $cat; - } - - return $list; - } -} diff --git a/sources/app/Models/ConfigurationSetter.php b/sources/app/Models/ConfigurationSetter.php deleted file mode 100755 index 250c14c..0000000 --- a/sources/app/Models/ConfigurationSetter.php +++ /dev/null @@ -1,389 +0,0 @@ -= 60) ? $value : ''; - } - - private function _content_width(&$data, $value) { - $value = strtolower($value); - if (!in_array($value, array('thin', 'medium', 'large', 'no_limit'))) { - $value = 'thin'; - } - - $data['content_width'] = $value; - } - - private function _default_state(&$data, $value) { - $data['default_state'] = (int)$value; - } - - private function _default_view(&$data, $value) { - switch ($value) { - case 'all': - $data['default_view'] = $value; - $data['default_state'] = (FreshRSS_Entry::STATE_READ + - FreshRSS_Entry::STATE_NOT_READ); - break; - case 'adaptive': - case 'unread': - default: - $data['default_view'] = $value; - $data['default_state'] = FreshRSS_Entry::STATE_NOT_READ; - } - } - - // It works for system config too! - private function _extensions_enabled(&$data, $value) { - if (!is_array($value)) { - $value = array($value); - } - $data['extensions_enabled'] = $value; - } - - private function _html5_notif_timeout(&$data, $value) { - $value = intval($value); - $data['html5_notif_timeout'] = $value >= 0 ? $value : 0; - } - - private function _keep_history_default(&$data, $value) { - $value = intval($value); - $data['keep_history_default'] = $value >= -1 ? $value : 0; - } - - // It works for system config too! - private function _language(&$data, $value) { - $value = strtolower($value); - $languages = Minz_Translate::availableLanguages(); - if (!in_array($value, $languages)) { - $value = 'en'; - } - $data['language'] = $value; - } - - private function _mail_login(&$data, $value) { - $value = filter_var($value, FILTER_VALIDATE_EMAIL); - $data['mail_login'] = $value ? $value : ''; - } - - private function _old_entries(&$data, $value) { - $value = intval($value); - $data['old_entries'] = $value > 0 ? $value : 3; - } - - private function _passwordHash(&$data, $value) { - $data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; - } - - private function _posts_per_page(&$data, $value) { - $value = intval($value); - $data['posts_per_page'] = $value > 0 ? $value : 10; - } - - private function _queries(&$data, $values) { - $data['queries'] = array(); - foreach ($values as $value) { - if ($value instanceof FreshRSS_UserQuery) { - $data['queries'][] = $value->toArray(); - } elseif (is_array($value)) { - $data['queries'][] = $value; - } - } - } - - private function _sharing(&$data, $values) { - $data['sharing'] = array(); - foreach ($values as $value) { - if (!is_array($value)) { - continue; - } - - // Verify URL and add default value when needed - if (isset($value['url'])) { - $is_url = ( - filter_var($value['url'], FILTER_VALIDATE_URL) || - (version_compare(PHP_VERSION, '5.3.3', '<') && - (strpos($value, '-') > 0) && - ($value === filter_var($value, FILTER_SANITIZE_URL))) - ); //PHP bug #51192 - if (!$is_url) { - continue; - } - } else { - $value['url'] = null; - } - - $data['sharing'][] = $value; - } - } - - private function _shortcuts(&$data, $values) { - if (!is_array($values)) { - return; - } - - $data['shortcuts'] = $values; - } - - private function _sort_order(&$data, $value) { - $data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC'; - } - - private function _ttl_default(&$data, $value) { - $value = intval($value); - $data['ttl_default'] = $value >= -1 ? $value : 3600; - } - - private function _view_mode(&$data, $value) { - $value = strtolower($value); - if (!in_array($value, array('global', 'normal', 'reader'))) { - $value = 'normal'; - } - $data['view_mode'] = $value; - } - - /** - * A list of boolean setters. - */ - private function _anon_access(&$data, $value) { - $data['anon_access'] = $this->handleBool($value); - } - - private function _auto_load_more(&$data, $value) { - $data['auto_load_more'] = $this->handleBool($value); - } - - private function _auto_remove_article(&$data, $value) { - $data['auto_remove_article'] = $this->handleBool($value); - } - - private function _mark_updated_article_unread(&$data, $value) { - $data['mark_updated_article_unread'] = $this->handleBool($value); - } - - private function _display_categories(&$data, $value) { - $data['display_categories'] = $this->handleBool($value); - } - - private function _display_posts(&$data, $value) { - $data['display_posts'] = $this->handleBool($value); - } - - private function _hide_read_feeds(&$data, $value) { - $data['hide_read_feeds'] = $this->handleBool($value); - } - - private function _lazyload(&$data, $value) { - $data['lazyload'] = $this->handleBool($value); - } - - private function _mark_when(&$data, $values) { - foreach ($values as $key => $value) { - $data['mark_when'][$key] = $this->handleBool($value); - } - } - - private function _onread_jump_next(&$data, $value) { - $data['onread_jump_next'] = $this->handleBool($value); - } - - private function _reading_confirm(&$data, $value) { - $data['reading_confirm'] = $this->handleBool($value); - } - - private function _sticky_post(&$data, $value) { - $data['sticky_post'] = $this->handleBool($value); - } - - private function _bottomline_date(&$data, $value) { - $data['bottomline_date'] = $this->handleBool($value); - } - private function _bottomline_favorite(&$data, $value) { - $data['bottomline_favorite'] = $this->handleBool($value); - } - private function _bottomline_link(&$data, $value) { - $data['bottomline_link'] = $this->handleBool($value); - } - private function _bottomline_read(&$data, $value) { - $data['bottomline_read'] = $this->handleBool($value); - } - private function _bottomline_sharing(&$data, $value) { - $data['bottomline_sharing'] = $this->handleBool($value); - } - private function _bottomline_tags(&$data, $value) { - $data['bottomline_tags'] = $this->handleBool($value); - } - - private function _topline_date(&$data, $value) { - $data['topline_date'] = $this->handleBool($value); - } - private function _topline_favorite(&$data, $value) { - $data['topline_favorite'] = $this->handleBool($value); - } - private function _topline_link(&$data, $value) { - $data['topline_link'] = $this->handleBool($value); - } - private function _topline_read(&$data, $value) { - $data['topline_read'] = $this->handleBool($value); - } - - /** - * The (not so long) list of setters for system configuration. - */ - private function _allow_anonymous(&$data, $value) { - $data['allow_anonymous'] = $this->handleBool($value) && FreshRSS_Auth::accessNeedsAction(); - } - - private function _allow_anonymous_refresh(&$data, $value) { - $data['allow_anonymous_refresh'] = $this->handleBool($value) && $data['allow_anonymous']; - } - - private function _api_enabled(&$data, $value) { - $data['api_enabled'] = $this->handleBool($value); - } - - private function _auth_type(&$data, $value) { - $value = strtolower($value); - if (!in_array($value, array('form', 'http_auth', 'persona', 'none'))) { - $value = 'none'; - } - $data['auth_type'] = $value; - $this->_allow_anonymous($data, $data['allow_anonymous']); - } - - private function _db(&$data, $value) { - if (!isset($value['type'])) { - return; - } - - switch ($value['type']) { - case 'mysql': - if (empty($value['host']) || - empty($value['user']) || - empty($value['base']) || - !isset($value['password'])) { - return; - } - - $data['db']['type'] = $value['type']; - $data['db']['host'] = $value['host']; - $data['db']['user'] = $value['user']; - $data['db']['base'] = $value['base']; - $data['db']['password'] = $value['password']; - $data['db']['prefix'] = isset($value['prefix']) ? $value['prefix'] : ''; - break; - case 'sqlite': - $data['db']['type'] = $value['type']; - $data['db']['host'] = ''; - $data['db']['user'] = ''; - $data['db']['base'] = ''; - $data['db']['password'] = ''; - $data['db']['prefix'] = ''; - break; - default: - return; - } - } - - private function _default_user(&$data, $value) { - $user_list = listUsers(); - if (in_array($value, $user_list)) { - $data['default_user'] = $value; - } - } - - private function _environment(&$data, $value) { - $value = strtolower($value); - if (!in_array($value, array('silent', 'development', 'production'))) { - $value = 'production'; - } - $data['environment'] = $value; - } - - private function _limits(&$data, $values) { - $max_small_int = 16384; - $limits_keys = array( - 'cache_duration' => array( - 'min' => 0, - ), - 'timeout' => array( - 'min' => 0, - ), - 'max_inactivity' => array( - 'min' => 0, - ), - 'max_feeds' => array( - 'min' => 0, - 'max' => $max_small_int, - ), - 'max_categories' => array( - 'min' => 0, - 'max' => $max_small_int, - ), - 'max_registrations' => array( - 'min' => 0, - ), - ); - - foreach ($values as $key => $value) { - if (!isset($limits_keys[$key])) { - continue; - } - - $value = intval($value); - $limits = $limits_keys[$key]; - if ( - (!isset($limits['min']) || $value >= $limits['min']) && - (!isset($limits['max']) || $value <= $limits['max']) - ) { - $data['limits'][$key] = $value; - } - } - } - - private function _unsafe_autologin_enabled(&$data, $value) { - $data['unsafe_autologin_enabled'] = $this->handleBool($value); - } - - private function _auto_update_url(&$data, $value) { - if (!$value) { - return; - } - - $data['auto_update_url'] = $value; - } -} diff --git a/sources/app/Models/Context.php b/sources/app/Models/Context.php deleted file mode 100755 index 2a58bd4..0000000 --- a/sources/app/Models/Context.php +++ /dev/null @@ -1,314 +0,0 @@ - 0, - 'read' => 0, - 'unread' => 0, - ); - - public static $get_unread = 0; - public static $current_get = array( - 'all' => false, - 'starred' => false, - 'feed' => false, - 'category' => false, - ); - public static $next_get = 'a'; - - public static $state = 0; - public static $order = 'DESC'; - public static $number = 0; - public static $search; - public static $first_id = ''; - public static $next_id = ''; - public static $id_max = ''; - - /** - * Initialize the context. - * - * Set the correct configurations and $categories variables. - */ - public static function init() { - // Init configuration. - self::$system_conf = Minz_Configuration::get('system'); - self::$user_conf = Minz_Configuration::get('user'); - - $catDAO = new FreshRSS_CategoryDAO(); - self::$categories = $catDAO->listCategories(); - } - - /** - * Returns if the current state includes $state parameter. - */ - public static function isStateEnabled($state) { - return self::$state & $state; - } - - /** - * Returns the current state with or without $state parameter. - */ - public static function getRevertState($state) { - if (self::$state & $state) { - return self::$state & ~$state; - } else { - return self::$state | $state; - } - } - - /** - * Return the current get as a string or an array. - * - * If $array is true, the first item of the returned value is 'f' or 'c' and - * the second is the id. - */ - public static function currentGet($array = false) { - if (self::$current_get['all']) { - return 'a'; - } elseif (self::$current_get['starred']) { - return 's'; - } elseif (self::$current_get['feed']) { - if ($array) { - return array('f', self::$current_get['feed']); - } else { - return 'f_' . self::$current_get['feed']; - } - } elseif (self::$current_get['category']) { - if ($array) { - return array('c', self::$current_get['category']); - } else { - return 'c_' . self::$current_get['category']; - } - } - } - - /** - * Return true if the current request targets a feed (and not a category or all articles), false otherwise. - */ - public static function isFeed() { - return self::$current_get['feed'] != false; - } - - /** - * Return true if $get parameter correspond to the $current_get attribute. - */ - public static function isCurrentGet($get) { - $type = $get[0]; - $id = substr($get, 2); - - switch($type) { - case 'a': - return self::$current_get['all']; - case 's': - return self::$current_get['starred']; - case 'f': - return self::$current_get['feed'] == $id; - case 'c': - return self::$current_get['category'] == $id; - default: - return false; - } - } - - /** - * Set the current $get attribute. - * - * Valid $get parameter are: - * - a - * - s - * - f_ - * - c_ - * - * $name and $get_unread attributes are also updated as $next_get - * Raise an exception if id or $get is invalid. - */ - public static function _get($get) { - $type = $get[0]; - $id = substr($get, 2); - $nb_unread = 0; - - switch($type) { - case 'a': - self::$current_get['all'] = true; - self::$name = _t('index.feed.title'); - self::$get_unread = self::$total_unread; - break; - case 's': - self::$current_get['starred'] = true; - self::$name = _t('index.feed.title_fav'); - self::$get_unread = self::$total_starred['unread']; - - // Update state if favorite is not yet enabled. - self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE; - break; - case 'f': - // We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description - $feed = FreshRSS_Context::$system_conf->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id); - if ($feed === null) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $feed = $feedDAO->searchById($id); - - if (!$feed) { - throw new FreshRSS_Context_Exception('Invalid feed: ' . $id); - } - } - - self::$current_get['feed'] = $id; - self::$current_get['category'] = $feed->category(); - self::$name = $feed->name(); - self::$description = $feed->description(); - self::$get_unread = $feed->nbNotRead(); - break; - case 'c': - // We try to find the corresponding category. - self::$current_get['category'] = $id; - if (!isset(self::$categories[$id])) { - $catDAO = new FreshRSS_CategoryDAO(); - $cat = $catDAO->searchById($id); - - if (!$cat) { - throw new FreshRSS_Context_Exception('Invalid category: ' . $id); - } - } else { - $cat = self::$categories[$id]; - } - - self::$name = $cat->name(); - self::$get_unread = $cat->nbNotRead(); - break; - default: - throw new FreshRSS_Context_Exception('Invalid getter: ' . $get); - } - - self::_nextGet(); - } - - /** - * Set the value of $next_get attribute. - */ - public static function _nextGet() { - $get = self::currentGet(); - // By default, $next_get == $get - self::$next_get = $get; - - if (self::$user_conf->onread_jump_next && strlen($get) > 2) { - $another_unread_id = ''; - $found_current_get = false; - switch ($get[0]) { - case 'f': - // We search the next feed with at least one unread article in - // same category as the currend feed. - foreach (self::$categories as $cat) { - if ($cat->id() != self::$current_get['category']) { - // We look into the category of the current feed! - continue; - } - - foreach ($cat->feeds() as $feed) { - if ($feed->id() == self::$current_get['feed']) { - // Here is our current feed! Fine, the next one will - // be a potential candidate. - $found_current_get = true; - continue; - } - - if ($feed->nbNotRead() > 0) { - $another_unread_id = $feed->id(); - if ($found_current_get) { - // We have found our current feed and now we - // have an feed with unread articles. Leave the - // loop! - break; - } - } - } - break; - } - - // If no feed have been found, next_get is the current category. - self::$next_get = empty($another_unread_id) ? - 'c_' . self::$current_get['category'] : - 'f_' . $another_unread_id; - break; - case 'c': - // We search the next category with at least one unread article. - foreach (self::$categories as $cat) { - if ($cat->id() == self::$current_get['category']) { - // Here is our current category! Next one could be our - // champion if it has unread articles. - $found_current_get = true; - continue; - } - - if ($cat->nbNotRead() > 0) { - $another_unread_id = $cat->id(); - if ($found_current_get) { - // Unread articles and the current category has - // already been found? Leave the loop! - break; - } - } - } - - // No unread category? The main stream will be our destination! - self::$next_get = empty($another_unread_id) ? - 'a' : - 'c_' . $another_unread_id; - break; - } - } - } - - /** - * Determine if the auto remove is available in the current context. - * This feature is available if: - * - it is activated in the configuration - * - the "read" state is not enable - * - the "unread" state is enable - * - * @return boolean - */ - public static function isAutoRemoveAvailable() { - if (!self::$user_conf->auto_remove_article) { - return false; - } - if (self::isStateEnabled(FreshRSS_Entry::STATE_READ)) { - return false; - } - if (!self::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ)) { - return false; - } - return true; - } - - /** - * Determine if the "sticky post" option is enabled. It can be enable - * by the user when it is selected in the configuration page or by the - * application when the context allows to auto-remove articles when they - * are read. - * - * @return boolean - */ - public static function isStickyPostEnabled() { - if (self::$user_conf->sticky_post) { - return true; - } - if (self::isAutoRemoveAvailable()) { - return true; - } - return false; - } - -} diff --git a/sources/app/Models/DatabaseDAO.php b/sources/app/Models/DatabaseDAO.php deleted file mode 100755 index 0d85718..0000000 --- a/sources/app/Models/DatabaseDAO.php +++ /dev/null @@ -1,83 +0,0 @@ -bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - $tables = array( - $this->prefix . 'category' => false, - $this->prefix . 'feed' => false, - $this->prefix . 'entry' => false, - ); - foreach ($res as $value) { - $tables[array_pop($value)] = true; - } - - return count(array_keys($tables, true, true)) == count($tables); - } - - public function getSchema($table) { - $sql = 'DESC ' . $this->prefix . $table; - $stm = $this->bd->prepare($sql); - $stm->execute(); - - return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); - } - - public function checkTable($table, $schema) { - $columns = $this->getSchema($table); - - $ok = (count($columns) == count($schema)); - foreach ($columns as $c) { - $ok &= in_array($c['name'], $schema); - } - - return $ok; - } - - public function categoryIsCorrect() { - return $this->checkTable('category', array( - 'id', 'name' - )); - } - - public function feedIsCorrect() { - return $this->checkTable('feed', array( - 'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate', - 'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', - 'cache_nbEntries', 'cache_nbUnreads' - )); - } - - public function entryIsCorrect() { - return $this->checkTable('entry', array( - 'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'is_read', - 'is_favorite', 'id_feed', 'tags' - )); - } - - public function daoToSchema($dao) { - return array( - 'name' => $dao['Field'], - 'type' => strtolower($dao['Type']), - 'notnull' => (bool)$dao['Null'], - 'default' => $dao['Default'], - ); - } - - public function listDaoToSchema($listDAO) { - $list = array(); - - foreach ($listDAO as $dao) { - $list[] = $this->daoToSchema($dao); - } - - return $list; - } -} diff --git a/sources/app/Models/DatabaseDAOSQLite.php b/sources/app/Models/DatabaseDAOSQLite.php deleted file mode 100755 index 7f53f96..0000000 --- a/sources/app/Models/DatabaseDAOSQLite.php +++ /dev/null @@ -1,48 +0,0 @@ -bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - - $tables = array( - 'category' => false, - 'feed' => false, - 'entry' => false, - ); - foreach ($res as $value) { - $tables[$value['name']] = true; - } - - return count(array_keys($tables, true, true)) == count($tables); - } - - public function getSchema($table) { - $sql = 'PRAGMA table_info(' . $table . ')'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - - return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); - } - - public function entryIsCorrect() { - return $this->checkTable('entry', array( - 'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'is_read', - 'is_favorite', 'id_feed', 'tags' - )); - } - - public function daoToSchema($dao) { - return array( - 'name' => $dao['name'], - 'type' => strtolower($dao['type']), - 'notnull' => $dao['notnull'] === '1' ? true : false, - 'default' => $dao['dflt_value'], - ); - } -} diff --git a/sources/app/Models/Days.php b/sources/app/Models/Days.php deleted file mode 100755 index 2d770c3..0000000 --- a/sources/app/Models/Days.php +++ /dev/null @@ -1,7 +0,0 @@ -_guid($guid); - $this->_title($title); - $this->_author($author); - $this->_content($content); - $this->_link($link); - $this->_date($pubdate); - $this->_isRead($is_read); - $this->_isFavorite($is_favorite); - $this->_feed($feed); - $this->_tags(preg_split('/[\s#]/', $tags)); - } - - public function id() { - return $this->id; - } - public function guid() { - return $this->guid; - } - public function title() { - return $this->title; - } - public function author() { - return $this->author === null ? '' : $this->author; - } - public function content() { - return $this->content; - } - public function link() { - return $this->link; - } - public function date($raw = false) { - if ($raw) { - return $this->date; - } else { - return timestamptodate($this->date); - } - } - public function dateAdded($raw = false) { - $date = intval(substr($this->id, 0, -6)); - if ($raw) { - return $date; - } else { - return timestamptodate($date); - } - } - public function isRead() { - return $this->is_read; - } - public function isFavorite() { - return $this->is_favorite; - } - public function feed($object = false) { - if ($object) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - return $feedDAO->searchById($this->feed); - } else { - return $this->feed; - } - } - public function tags($inString = false) { - if ($inString) { - return empty($this->tags) ? '' : '#' . implode(' #', $this->tags); - } else { - return $this->tags; - } - } - - public function hash() { - if ($this->hash === null) { - //Do not include $this->date because it may be automatically generated when lacking - $this->hash = md5($this->link . $this->title . $this->author . $this->content . $this->tags(true)); - } - return $this->hash; - } - - public function _id($value) { - $this->id = $value; - } - public function _guid($value) { - $this->guid = $value; - } - public function _title($value) { - $this->hash = null; - $this->title = $value; - } - public function _author($value) { - $this->hash = null; - $this->author = $value; - } - public function _content($value) { - $this->hash = null; - $this->content = $value; - } - public function _link($value) { - $this->hash = null; - $this->link = $value; - } - public function _date($value) { - $this->hash = null; - $value = intval($value); - $this->date = $value > 1 ? $value : time(); - } - public function _isRead($value) { - $this->is_read = $value === null ? null : (bool)$value; - } - public function _isFavorite($value) { - $this->is_favorite = $value; - } - public function _feed($value) { - $this->feed = $value; - } - public function _tags($value) { - $this->hash = null; - if (!is_array($value)) { - $value = array($value); - } - - foreach ($value as $key => $t) { - if (!$t) { - unset($value[$key]); - } - } - - $this->tags = $value; - } - - public function isDay($day, $today) { - $date = $this->dateAdded(true); - switch ($day) { - case FreshRSS_Days::TODAY: - $tomorrow = $today + 86400; - return $date >= $today && $date < $tomorrow; - case FreshRSS_Days::YESTERDAY: - $yesterday = $today - 86400; - return $date >= $yesterday && $date < $today; - case FreshRSS_Days::BEFORE_YESTERDAY: - $yesterday = $today - 86400; - return $date < $yesterday; - default: - return false; - } - } - - public function loadCompleteContent($pathEntries) { - // Gestion du contenu - // On cherche à récupérer les articles en entier... même si le flux ne le propose pas - if ($pathEntries) { - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entry = $entryDAO->searchByGuid($this->feed, $this->guid); - - if ($entry) { - // l'article existe déjà en BDD, en se contente de recharger ce contenu - $this->content = $entry->content(); - } else { - try { - // l'article n'est pas en BDD, on va le chercher sur le site - $this->content = get_content_by_parsing( - htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries - ); - } catch (Exception $e) { - // rien à faire, on garde l'ancien contenu(requête a échoué) - } - } - } - } - - public function toArray() { - return array( - 'id' => $this->id(), - 'guid' => $this->guid(), - 'title' => $this->title(), - 'author' => $this->author(), - 'content' => $this->content(), - 'link' => $this->link(), - 'date' => $this->date(true), - 'hash' => $this->hash(), - 'is_read' => $this->isRead(), - 'is_favorite' => $this->isFavorite(), - 'id_feed' => $this->feed(), - 'tags' => $this->tags(true), - ); - } -} diff --git a/sources/app/Models/EntryDAO.php b/sources/app/Models/EntryDAO.php deleted file mode 100755 index f740558..0000000 --- a/sources/app/Models/EntryDAO.php +++ /dev/null @@ -1,742 +0,0 @@ -bd->inTransaction()) { - $this->bd->beginTransaction(); - $hasTransaction = true; - } - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0'); - if ($stm && $stm->execute()) { - $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 - if ($stm && $stm->execute()) { - if ($hasTransaction) { - $this->bd->commit(); - } - return true; - } - } - if ($hasTransaction) { - $this->bd->rollBack(); - } - } elseif ($name === 'hash') { //v1.1.1 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)'); - return $stm && $stm->execute(); - } - } catch (Exception $e) { - Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage()); - if ($hasTransaction) { - $this->bd->rollBack(); - } - } - return false; - } - - protected function autoAddColumn($errorInfo) { - if (isset($errorInfo[0])) { - if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($errorInfo[2], $column) !== false) { - return $this->addColumn($column); - } - } - } - } - return false; - } - - private $addEntryPrepared = null; - - public function addEntry($valuesTmp) { - if ($this->addEntryPrepared === null) { - $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, ' - . ($this->isCompressed() ? 'content_bin' : 'content') - . ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) ' - . 'VALUES(?, ?, ?, ?, ' - . ($this->isCompressed() ? 'COMPRESS(?)' : '?') - . ', ?, ?, ?, ' - . ($this->hasNativeHex() ? 'X?' : '?') - . ', ?, ?, ?, ?)'; - $this->addEntryPrepared = $this->bd->prepare($sql); - } - - $values = array( - $valuesTmp['id'], - substr($valuesTmp['guid'], 0, 760), - substr($valuesTmp['title'], 0, 255), - substr($valuesTmp['author'], 0, 255), - $valuesTmp['content'], - substr($valuesTmp['link'], 0, 1023), - $valuesTmp['date'], - time(), - $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+ - $valuesTmp['is_read'] ? 1 : 0, - $valuesTmp['is_favorite'] ? 1 : 0, - $valuesTmp['id_feed'], - substr($valuesTmp['tags'], 0, 1023), - ); - - if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) { - return $this->bd->lastInsertId(); - } else { - $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); - if ($this->autoAddColumn($info)) { - return $this->addEntry($valuesTmp); - } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries - Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); - } - return false; - } - } - - private $updateEntryPrepared = null; - - public function updateEntry($valuesTmp) { - if (!isset($valuesTmp['is_read'])) { - $valuesTmp['is_read'] = null; - } - - if ($this->updateEntryPrepared === null) { - $sql = 'UPDATE `' . $this->prefix . 'entry` ' - . 'SET title=?, author=?, ' - . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?') - . ', link=?, date=?, lastSeen=?, hash=' - . ($this->hasNativeHex() ? 'X?' : '?') - . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ') - . 'tags=? ' - . 'WHERE id_feed=? AND guid=?'; - $this->updateEntryPrepared = $this->bd->prepare($sql); - } - - $values = array( - substr($valuesTmp['title'], 0, 255), - substr($valuesTmp['author'], 0, 255), - $valuesTmp['content'], - substr($valuesTmp['link'], 0, 1023), - $valuesTmp['date'], - time(), - $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), - ); - if ($valuesTmp['is_read'] !== null) { - $values[] = $valuesTmp['is_read'] ? 1 : 0; - } - $values = array_merge($values, array( - substr($valuesTmp['tags'], 0, 1023), - $valuesTmp['id_feed'], - substr($valuesTmp['guid'], 0, 760), - )); - - if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) { - return $this->bd->lastInsertId(); - } else { - $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo(); - if ($this->autoAddColumn($info)) { - return $this->updateEntry($valuesTmp); - } - Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while updating entry with GUID ' . $valuesTmp['guid'] . ' in feed ' . $valuesTmp['id_feed']); - return false; - } - } - - /** - * Toggle favorite marker on one or more article - * - * @todo simplify the query by removing the str_repeat. I am pretty sure - * there is an other way to do that. - * - * @param integer|array $ids - * @param boolean $is_favorite - * @return false|integer - */ - public function markFavorite($ids, $is_favorite = true) { - if (!is_array($ids)) { - $ids = array($ids); - } - if (count($ids) < 1) { - return 0; - } - $sql = 'UPDATE `' . $this->prefix . 'entry` ' - . 'SET is_favorite=? ' - . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; - $values = array($is_favorite ? 1 : 0); - $values = array_merge($values, $ids); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markFavorite: ' . $info[2]); - return false; - } - } - - /** - * Update the unread article cache held on every feed details. - * Depending on the parameters, it updates the cache on one feed, on all - * feeds from one category or on all feeds. - * - * @todo It can use the query builder refactoring to build that query - * - * @param false|integer $catId category ID - * @param false|integer $feedId feed ID - * @return boolean - */ - protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'LEFT OUTER JOIN (' - . 'SELECT e.id_feed, ' - . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.is_read=0 ' - . 'GROUP BY e.id_feed' - . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' - . 'WHERE 1'; - $values = array(); - if ($feedId !== false) { - $sql .= ' AND f.id=?'; - $values[] = $id; - } - if ($catId !== false) { - $sql .= ' AND f.category=?'; - $values[] = $catId; - } - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute($values)) { - return true; - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); - return false; - } - } - - /** - * Toggle the read marker on one or more article. - * Then the cache is updated. - * - * @todo change the way the query is build because it seems there is - * unnecessary code in here. For instance, the part with the str_repeat. - * @todo remove code duplication. It seems the code is basically the - * same if it is an array or not. - * - * @param integer|array $ids - * @param boolean $is_read - * @return integer affected rows - */ - public function markRead($ids, $is_read = true) { - if (is_array($ids)) { //Many IDs at once (used by API) - if (count($ids) < 6) { //Speed heuristics - $affected = 0; - foreach ($ids as $id) { - $affected += $this->markRead($id, $is_read); - } - return $affected; - } - - $sql = 'UPDATE `' . $this->prefix . 'entry` ' - . 'SET is_read=? ' - . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; - $values = array($is_read ? 1 : 0); - $values = array_merge($values, $ids); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markRead: ' . $info[2]); - return false; - } - $affected = $stm->rowCount(); - if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { - return false; - } - return $affected; - } else { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' - . 'SET e.is_read=?,' - . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' - . 'WHERE e.id=? AND e.is_read=?'; - $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markRead: ' . $info[2]); - return false; - } - } - } - - /** - * Mark all entries as read depending on parameters. - * If $onlyFavorites is true, it is used when the user mark as read in - * the favorite pseudo-category. - * If $priorityMin is greater than 0, it is used when the user mark as - * read in the main feed pseudo-category. - * Then the cache is updated. - * - * If $idMax equals 0, a deprecated debug message is logged - * - * @todo refactor this method along with markReadCat and markReadFeed - * since they are all doing the same thing. I think we need to build a - * tool to generate the query instead of having queries all over the - * place. It will be reused also for the filtering making every thing - * separated. - * - * @param integer $idMax fail safe article ID - * @param boolean $onlyFavorites - * @param integer $priorityMin - * @return integer affected rows - */ - public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { - if ($idMax == 0) { - $idMax = time() . '000000'; - Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); - } - - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' - . 'SET e.is_read=1 ' - . 'WHERE e.is_read=0 AND e.id <= ?'; - if ($onlyFavorites) { - $sql .= ' AND e.is_favorite=1'; - } elseif ($priorityMin >= 0) { - $sql .= ' AND f.priority > ' . intval($priorityMin); - } - $values = array($idMax); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadEntries: ' . $info[2]); - return false; - } - $affected = $stm->rowCount(); - if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { - return false; - } - return $affected; - } - - /** - * Mark all the articles in a category as read. - * There is a fail safe to prevent to mark as read articles that are - * loaded during the mark as read action. Then the cache is updated. - * - * If $idMax equals 0, a deprecated debug message is logged - * - * @param integer $id category ID - * @param integer $idMax fail safe article ID - * @return integer affected rows - */ - public function markReadCat($id, $idMax = 0) { - if ($idMax == 0) { - $idMax = time() . '000000'; - Minz_Log::debug('Calling markReadCat(0) is deprecated!'); - } - - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' - . 'SET e.is_read=1 ' - . 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?'; - $values = array($id, $idMax); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadCat: ' . $info[2]); - return false; - } - $affected = $stm->rowCount(); - if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) { - return false; - } - return $affected; - } - - /** - * Mark all the articles in a feed as read. - * There is a fail safe to prevent to mark as read articles that are - * loaded during the mark as read action. Then the cache is updated. - * - * If $idMax equals 0, a deprecated debug message is logged - * - * @param integer $id_feed feed ID - * @param integer $idMax fail safe article ID - * @return integer affected rows - */ - public function markReadFeed($id_feed, $idMax = 0) { - if ($idMax == 0) { - $idMax = time() . '000000'; - Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); - } - $this->bd->beginTransaction(); - - $sql = 'UPDATE `' . $this->prefix . 'entry` ' - . 'SET is_read=1 ' - . 'WHERE id_feed=? AND is_read=0 AND id <= ?'; - $values = array($id_feed, $idMax); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadFeed: ' . $info[2]); - $this->bd->rollBack(); - return false; - } - $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected - . ' WHERE id=?'; - $values = array($id_feed); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadFeed: ' . $info[2]); - $this->bd->rollBack(); - return false; - } - } - - $this->bd->commit(); - return $affected; - } - - public function searchByGuid($id_feed, $guid) { - // un guid est unique pour un flux donné - $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') - . ', link, date, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; - $stm = $this->bd->prepare($sql); - - $values = array( - $id_feed, - $guid, - ); - - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - $entries = self::daoToEntry($res); - return isset($entries[0]) ? $entries[0] : null; - } - - public function searchById($id) { - $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') - . ', link, date, is_read, is_favorite, id_feed, tags ' - . 'FROM `' . $this->prefix . 'entry` WHERE id=?'; - $stm = $this->bd->prepare($sql); - - $values = array($id); - - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - $entries = self::daoToEntry($res); - return isset($entries[0]) ? $entries[0] : null; - } - - protected function sqlConcat($s1, $s2) { - return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL - } - - private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { - if (!$state) { - $state = FreshRSS_Entry::STATE_ALL; - } - $where = ''; - $joinFeed = false; - $values = array(); - switch ($type) { - case 'a': - $where .= 'f.priority > 0 '; - $joinFeed = true; - break; - case 's': //Deprecated: use $state instead - $where .= 'e1.is_favorite=1 '; - break; - case 'c': - $where .= 'f.category=? '; - $values[] = intval($id); - $joinFeed = true; - break; - case 'f': - $where .= 'e1.id_feed=? '; - $values[] = intval($id); - break; - case 'A': - $where .= '1 '; - break; - default: - throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); - } - - if ($state & FreshRSS_Entry::STATE_NOT_READ) { - if (!($state & FreshRSS_Entry::STATE_READ)) { - $where .= 'AND e1.is_read=0 '; - } - } - elseif ($state & FreshRSS_Entry::STATE_READ) { - $where .= 'AND e1.is_read=1 '; - } - if ($state & FreshRSS_Entry::STATE_FAVORITE) { - if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) { - $where .= 'AND e1.is_favorite=1 '; - } - } - elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) { - $where .= 'AND e1.is_favorite=0 '; - } - - switch ($order) { - case 'DESC': - case 'ASC': - break; - default: - throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!'); - } - /*if ($firstId === '' && parent::$sharedDbType === 'mysql') { - $firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. TODO: check if this is needed again, after the filtering for old articles has been removed in 0.9-dev - }*/ - if ($firstId !== '') { - $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; - } - if ($date_min > 0) { - $where .= 'AND e1.id >= ' . $date_min . '000000 '; - } - $search = ''; - if ($filter) { - if ($filter->getIntitle()) { - $search .= 'AND e1.title LIKE ? '; - $values[] = "%{$filter->getIntitle()}%"; - } - if ($filter->getInurl()) { - $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? '; - $values[] = "%{$filter->getInurl()}%"; - } - if ($filter->getAuthor()) { - $search .= 'AND e1.author LIKE ? '; - $values[] = "%{$filter->getAuthor()}%"; - } - if ($filter->getMinDate()) { - $search .= 'AND e1.id >= ? '; - $values[] = "{$filter->getMinDate()}000000"; - } - if ($filter->getMaxDate()) { - $search .= 'AND e1.id <= ? '; - $values[] = "{$filter->getMaxDate()}000000"; - } - if ($filter->getMinPubdate()) { - $search .= 'AND e1.date >= ? '; - $values[] = $filter->getMinPubdate(); - } - if ($filter->getMaxPubdate()) { - $search .= 'AND e1.date <= ? '; - $values[] = $filter->getMaxPubdate(); - } - if ($filter->getTags()) { - $tags = $filter->getTags(); - foreach ($tags as $tag) { - $search .= 'AND e1.tags LIKE ? '; - $values[] = "%{$tag}%"; - } - } - if ($filter->getSearch()) { - $search_values = $filter->getSearch(); - foreach ($search_values as $search_value) { - $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? '; - $values[] = "%{$search_value}%"; - } - } - } - return array($values, - 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' - . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '') - . 'WHERE ' . $where - . $search - . 'ORDER BY e1.id ' . $order - . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ - } - - public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min); - - $sql = 'SELECT e.id, e.guid, e.title, e.author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') - . ', e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN (' - . $sql - . ') e2 ON e2.id=e.id ' - . 'ORDER BY e.id ' . $order; - - $stm = $this->bd->prepare($sql); - $stm->execute($values); - - return self::daoToEntry($stm->fetchAll(PDO::FETCH_ASSOC)); - } - - public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min); - - $stm = $this->bd->prepare($sql); - $stm->execute($values); - - return $stm->fetchAll(PDO::FETCH_COLUMN, 0); - } - - public function listHashForFeedGuids($id_feed, $guids) { - if (count($guids) < 1) { - return array(); - } - $sql = 'SELECT guid, hex(hash) AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); - $values = array($id_feed); - $values = array_merge($values, $guids); - if ($stm && $stm->execute($values)) { - $result = array(); - $rows = $stm->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - $result[$row['guid']] = $row['hexHash']; - } - return $result; - } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); - if ($this->autoAddColumn($info)) { - return $this->listHashForFeedGuids($id_feed, $guids); - } - Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while querying feed ' . $id_feed); - return false; - } - } - - public function updateLastSeen($id_feed, $guids) { - if (count($guids) < 1) { - return 0; - } - $sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); - $values = array(time(), $id_feed); - $values = array_merge($values, $guids); - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); - if ($this->autoAddColumn($info)) { - return $this->updateLastSeen($id_feed, $guids); - } - Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while updating feed ' . $id_feed); - return false; - } - } - - public function countUnreadRead() { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0' - . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0 AND is_read=0'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - $all = empty($res[0]) ? 0 : $res[0]; - $unread = empty($res[1]) ? 0 : $res[1]; - return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); - } - public function count($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; - if ($minPriority !== null) { - $sql = ' WHERE priority > ' . intval($minPriority); - } - $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - return $res[0]; - } - public function countNotRead($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE is_read=0'; - if ($minPriority !== null) { - $sql = ' AND priority > ' . intval($minPriority); - } - $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - return $res[0]; - } - - public function countUnreadReadFavorites() { - $sql = 'SELECT c FROM (' - . 'SELECT COUNT(id) AS c, 1 as o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 ' - . 'UNION SELECT COUNT(id) AS c, 2 AS o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read=0' - . ') u ORDER BY o'; - $stm = $this->bd->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - $all = empty($res[0]) ? 0 : $res[0]; - $unread = empty($res[1]) ? 0 : $res[1]; - return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); - } - - public function optimizeTable() { - $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL - $stm = $this->bd->prepare($sql); - $stm->execute(); - } - - public function size($all = false) { - $db = FreshRSS_Context::$system_conf->db; - $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?'; //MySQL - $values = array($db['base']); - if (!$all) { - $sql .= ' AND table_name LIKE ?'; - $values[] = $this->prefix . '%'; - } - $stm = $this->bd->prepare($sql); - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - return $res[0]; - } - - public static function daoToEntry($listDAO) { - $list = array(); - - if (!is_array($listDAO)) { - $listDAO = array($listDAO); - } - - foreach ($listDAO as $key => $dao) { - $entry = new FreshRSS_Entry( - $dao['id_feed'], - $dao['guid'], - $dao['title'], - $dao['author'], - $dao['content'], - $dao['link'], - $dao['date'], - $dao['is_read'], - $dao['is_favorite'], - $dao['tags'] - ); - if (isset($dao['id'])) { - $entry->_id($dao['id']); - } - $list[] = $entry; - } - - unset($listDAO); - - return $list; - } -} diff --git a/sources/app/Models/EntryDAOSQLite.php b/sources/app/Models/EntryDAOSQLite.php deleted file mode 100755 index ff049d8..0000000 --- a/sources/app/Models/EntryDAOSQLite.php +++ /dev/null @@ -1,189 +0,0 @@ -bd->query("SELECT sql FROM sqlite_master where name='entry'")) { - $showCreate = $tableInfo->fetchColumn(); - Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoAddColumn: ' . $showCreate); - foreach (array('lastSeen', 'hash') as $column) { - if (stripos($showCreate, $column) === false) { - return $this->addColumn($column); - } - } - } - } - return false; - } - - protected function sqlConcat($s1, $s2) { - return $s1 . '||' . $s2; - } - - protected function updateCacheUnreads($catId = false, $feedId = false) { - $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbUnreads=(' - . 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0) ' - . 'WHERE 1'; - $values = array(); - if ($feedId !== false) { - $sql .= ' AND id=?'; - $values[] = $feedId; - } - if ($catId !== false) { - $sql .= ' AND category=?'; - $values[] = $catId; - } - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute($values)) { - return true; - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); - return false; - } - } - - /** - * Toggle the read marker on one or more article. - * Then the cache is updated. - * - * @todo change the way the query is build because it seems there is - * unnecessary code in here. For instance, the part with the str_repeat. - * @todo remove code duplication. It seems the code is basically the - * same if it is an array or not. - * - * @param integer|array $ids - * @param boolean $is_read - * @return integer affected rows - */ - public function markRead($ids, $is_read = true) { - if (is_array($ids)) { //Many IDs at once (used by API) - if (true) { //Speed heuristics //TODO: Not implemented yet for SQLite (so always call IDs one by one) - $affected = 0; - foreach ($ids as $id) { - $affected += $this->markRead($id, $is_read); - } - return $affected; - } - } else { - $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?'; - $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markRead 1: ' . $info[2]); - $this->bd->rollBack(); - return false; - } - $affected = $stm->rowCount(); - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` SET cache_nbUnreads=cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' - . 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)'; - $values = array($ids); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markRead 2: ' . $info[2]); - $this->bd->rollBack(); - return false; - } - } - $this->bd->commit(); - return $affected; - } - } - - /** - * Mark all entries as read depending on parameters. - * If $onlyFavorites is true, it is used when the user mark as read in - * the favorite pseudo-category. - * If $priorityMin is greater than 0, it is used when the user mark as - * read in the main feed pseudo-category. - * Then the cache is updated. - * - * If $idMax equals 0, a deprecated debug message is logged - * - * @todo refactor this method along with markReadCat and markReadFeed - * since they are all doing the same thing. I think we need to build a - * tool to generate the query instead of having queries all over the - * place. It will be reused also for the filtering making every thing - * separated. - * - * @param integer $idMax fail safe article ID - * @param boolean $onlyFavorites - * @param integer $priorityMin - * @return integer affected rows - */ - public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { - if ($idMax == 0) { - $idMax = time() . '000000'; - Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); - } - - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?'; - if ($onlyFavorites) { - $sql .= ' AND is_favorite=1'; - } elseif ($priorityMin >= 0) { - $sql .= ' AND id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.priority > ' . intval($priorityMin) . ')'; - } - $values = array($idMax); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadEntries: ' . $info[2]); - return false; - } - $affected = $stm->rowCount(); - if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { - return false; - } - return $affected; - } - - /** - * Mark all the articles in a category as read. - * There is a fail safe to prevent to mark as read articles that are - * loaded during the mark as read action. Then the cache is updated. - * - * If $idMax equals 0, a deprecated debug message is logged - * - * @param integer $id category ID - * @param integer $idMax fail safe article ID - * @return integer affected rows - */ - public function markReadCat($id, $idMax = 0) { - if ($idMax == 0) { - $idMax = time() . '000000'; - Minz_Log::debug('Calling markReadCat(0) is deprecated!'); - } - - $sql = 'UPDATE `' . $this->prefix . 'entry` ' - . 'SET is_read=1 ' - . 'WHERE is_read=0 AND id <= ? AND ' - . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)'; - $values = array($idMax, $id); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadCat: ' . $info[2]); - return false; - } - $affected = $stm->rowCount(); - if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) { - return false; - } - return $affected; - } - - public function optimizeTable() { - //TODO: Search for an equivalent in SQLite - } - - public function size($all = false) { - return @filesize(join_path(DATA_PATH, 'users', $this->current_user, 'db.sqlite')); - } -} diff --git a/sources/app/Models/Factory.php b/sources/app/Models/Factory.php deleted file mode 100755 index db09d15..0000000 --- a/sources/app/Models/Factory.php +++ /dev/null @@ -1,41 +0,0 @@ -db['type'] === 'sqlite') { - return new FreshRSS_FeedDAOSQLite($username); - } else { - return new FreshRSS_FeedDAO($username); - } - } - - public static function createEntryDao($username = null) { - $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_EntryDAOSQLite($username); - } else { - return new FreshRSS_EntryDAO($username); - } - } - - public static function createStatsDAO($username = null) { - $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_StatsDAOSQLite($username); - } else { - return new FreshRSS_StatsDAO($username); - } - } - - public static function createDatabaseDAO($username = null) { - $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_DatabaseDAOSQLite($username); - } else { - return new FreshRSS_DatabaseDAO($username); - } - } - -} diff --git a/sources/app/Models/Feed.php b/sources/app/Models/Feed.php deleted file mode 100755 index 986cc50..0000000 --- a/sources/app/Models/Feed.php +++ /dev/null @@ -1,490 +0,0 @@ -_url($url); - } else { - $this->url = $url; - } - } - - public static function example() { - $f = new FreshRSS_Feed('http://example.net/', false); - $f->faviconPrepare(); - return $f; - } - - public function id() { - return $this->id; - } - - public function hash() { - if ($this->hash === null) { - $salt = FreshRSS_Context::$system_conf->salt; - $this->hash = hash('crc32b', $salt . $this->url); - } - return $this->hash; - } - - public function url() { - return $this->url; - } - public function selfUrl() { - return $this->selfUrl; - } - public function hubUrl() { - return $this->hubUrl; - } - public function category() { - return $this->category; - } - public function entries() { - return $this->entries === null ? array() : $this->entries; - } - public function name() { - return $this->name; - } - public function website() { - return $this->website; - } - public function description() { - return $this->description; - } - public function lastUpdate() { - return $this->lastUpdate; - } - public function priority() { - return $this->priority; - } - public function pathEntries() { - return $this->pathEntries; - } - public function httpAuth($raw = true) { - if ($raw) { - return $this->httpAuth; - } else { - $pos_colon = strpos($this->httpAuth, ':'); - $user = substr($this->httpAuth, 0, $pos_colon); - $pass = substr($this->httpAuth, $pos_colon + 1); - - return array( - 'username' => $user, - 'password' => $pass - ); - } - } - public function inError() { - return $this->error; - } - public function keepHistory() { - return $this->keep_history; - } - public function ttl() { - return $this->ttl; - } - // public function ttlExpire() { - // $ttl = $this->ttl; - // if ($ttl == -2) { //Default - // $ttl = FreshRSS_Context::$user_conf->ttl_default; - // } - // if ($ttl == -1) { //Never - // $ttl = 64000000; //~2 years. Good enough for PubSubHubbub logic - // } - // return $this->lastUpdate + $ttl; - // } - public function nbEntries() { - if ($this->nbEntries < 0) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->nbEntries = $feedDAO->countEntries($this->id()); - } - - return $this->nbEntries; - } - public function nbNotRead() { - if ($this->nbNotRead < 0) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->nbNotRead = $feedDAO->countNotRead($this->id()); - } - - return $this->nbNotRead; - } - public function faviconPrepare() { - $file = DATA_PATH . '/favicons/' . $this->hash() . '.txt'; - if (!file_exists($file)) { - $t = $this->website; - if ($t == '') { - $t = $this->url; - } - file_put_contents($file, $t); - } - } - public static function faviconDelete($hash) { - $path = DATA_PATH . '/favicons/' . $hash; - @unlink($path . '.ico'); - @unlink($path . '.txt'); - } - public function favicon() { - return Minz_Url::display('/f.php?' . $this->hash()); - } - - public function _id($value) { - $this->id = $value; - } - public function _url($value, $validate=true) { - $this->hash = null; - if ($validate) { - $value = checkUrl($value); - } - if (empty($value)) { - throw new FreshRSS_BadUrl_Exception($value); - } - $this->url = $value; - } - public function _category($value) { - $value = intval($value); - $this->category = $value >= 0 ? $value : 0; - } - public function _name($value) { - $this->name = $value === null ? '' : $value; - } - public function _website($value, $validate=true) { - if ($validate) { - $value = checkUrl($value); - } - if (empty($value)) { - $value = ''; - } - $this->website = $value; - } - public function _description($value) { - $this->description = $value === null ? '' : $value; - } - public function _lastUpdate($value) { - $this->lastUpdate = $value; - } - public function _priority($value) { - $value = intval($value); - $this->priority = $value >= 0 ? $value : 10; - } - public function _pathEntries($value) { - $this->pathEntries = $value; - } - public function _httpAuth($value) { - $this->httpAuth = $value; - } - public function _error($value) { - $this->error = (bool)$value; - } - public function _keepHistory($value) { - $value = intval($value); - $value = min($value, 1000000); - $value = max($value, -2); - $this->keep_history = $value; - } - public function _ttl($value) { - $value = intval($value); - $value = min($value, 100000000); - $value = max($value, -2); - $this->ttl = $value; - } - public function _nbNotRead($value) { - $this->nbNotRead = intval($value); - } - public function _nbEntries($value) { - $this->nbEntries = intval($value); - } - - public function load($loadDetails = false) { - if ($this->url !== null) { - if (CACHE_PATH === false) { - throw new Minz_FileNotExistException( - 'CACHE_PATH', - Minz_Exception::ERROR - ); - } else { - $url = htmlspecialchars_decode($this->url, ENT_QUOTES); - if ($this->httpAuth != '') { - $url = preg_replace('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url); - } - $feed = customSimplePie(); - if (substr($url, -11) === '#force_feed') { - $feed->force_feed(true); - $url = substr($url, 0, -11); - } - $feed->set_feed_url($url); - if (!$loadDetails) { //Only activates auto-discovery when adding a new feed - $feed->set_autodiscovery_level(SIMPLEPIE_LOCATOR_NONE); - } - $mtime = $feed->init(); - - if ((!$mtime) || $feed->error()) { - $errorMessage = $feed->error(); - throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']'); - } - - $links = $feed->get_links('self'); - $this->selfUrl = isset($links[0]) ? $links[0] : null; - $links = $feed->get_links('hub'); - $this->hubUrl = isset($links[0]) ? $links[0] : null; - - if ($loadDetails) { - // si on a utilisé l'auto-discover, notre url va avoir changé - $subscribe_url = $feed->subscribe_url(false); - - $title = strtr(html_only_entity_decode($feed->get_title()), array('<' => '<', '>' => '>', '"' => '"')); //HTML to HTML-PRE //ENT_COMPAT except & - $this->_name($title == '' ? $url : $title); - - $this->_website(html_only_entity_decode($feed->get_link())); - $this->_description(html_only_entity_decode($feed->get_description())); - } else { - //The case of HTTP 301 Moved Permanently - $subscribe_url = $feed->subscribe_url(true); - } - - $clean_url = SimplePie_Misc::url_remove_credentials($subscribe_url); - if ($subscribe_url !== null && $subscribe_url !== $url) { - $this->_url($clean_url); - } - - if (($mtime === true) || ($mtime > $this->lastUpdate)) { - //Minz_Log::debug('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url); - $this->loadEntries($feed); // et on charge les articles du flux - } else { - //Minz_Log::debug('FreshRSS use cache for ' . $clean_url); - $this->entries = array(); - } - - $feed->__destruct(); //http://simplepie.org/wiki/faq/i_m_getting_memory_leaks - unset($feed); - } - } - } - - public function loadEntries($feed) { - $entries = array(); - - foreach ($feed->get_items() as $item) { - $title = html_only_entity_decode(strip_tags($item->get_title())); - $author = $item->get_author(); - $link = $item->get_permalink(); - $date = @strtotime($item->get_date()); - - // gestion des tags (catégorie == tag) - $tags_tmp = $item->get_categories(); - $tags = array(); - if ($tags_tmp !== null) { - foreach ($tags_tmp as $tag) { - $tags[] = html_only_entity_decode($tag->get_label()); - } - } - - $content = html_only_entity_decode($item->get_content()); - - $elinks = array(); - foreach ($item->get_enclosures() as $enclosure) { - $elink = $enclosure->get_link(); - if (empty($elinks[$elink])) { - $elinks[$elink] = '1'; - $mime = strtolower($enclosure->get_type()); - if (strpos($mime, 'image/') === 0) { - $content .= '
'; - } elseif (strpos($mime, 'audio/') === 0) { - $content .= '